Update WinBoard translation template
[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       AnalyzeFileEvent();
12344     }
12345
12346     if(creatingBook) return TRUE;
12347     if (!matchMode && pos > 0) {
12348         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12349     } else
12350     if (matchMode || appData.timeDelay == 0) {
12351       ToEndEvent();
12352     } else if (appData.timeDelay > 0) {
12353       AutoPlayGameLoop();
12354     }
12355
12356     if (appData.debugMode)
12357         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12358
12359     loadFlag = 0; /* [HGM] true game starts */
12360     return TRUE;
12361 }
12362
12363 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12364 int
12365 ReloadPosition (int offset)
12366 {
12367     int positionNumber = lastLoadPositionNumber + offset;
12368     if (lastLoadPositionFP == NULL) {
12369         DisplayError(_("No position has been loaded yet"), 0);
12370         return FALSE;
12371     }
12372     if (positionNumber <= 0) {
12373         DisplayError(_("Can't back up any further"), 0);
12374         return FALSE;
12375     }
12376     return LoadPosition(lastLoadPositionFP, positionNumber,
12377                         lastLoadPositionTitle);
12378 }
12379
12380 /* Load the nth position from the given file */
12381 int
12382 LoadPositionFromFile (char *filename, int n, char *title)
12383 {
12384     FILE *f;
12385     char buf[MSG_SIZ];
12386
12387     if (strcmp(filename, "-") == 0) {
12388         return LoadPosition(stdin, n, "stdin");
12389     } else {
12390         f = fopen(filename, "rb");
12391         if (f == NULL) {
12392             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12393             DisplayError(buf, errno);
12394             return FALSE;
12395         } else {
12396             return LoadPosition(f, n, title);
12397         }
12398     }
12399 }
12400
12401 /* Load the nth position from the given open file, and close it */
12402 int
12403 LoadPosition (FILE *f, int positionNumber, char *title)
12404 {
12405     char *p, line[MSG_SIZ];
12406     Board initial_position;
12407     int i, j, fenMode, pn;
12408
12409     if (gameMode == Training )
12410         SetTrainingModeOff();
12411
12412     if (gameMode != BeginningOfGame) {
12413         Reset(FALSE, TRUE);
12414     }
12415     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12416         fclose(lastLoadPositionFP);
12417     }
12418     if (positionNumber == 0) positionNumber = 1;
12419     lastLoadPositionFP = f;
12420     lastLoadPositionNumber = positionNumber;
12421     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12422     if (first.pr == NoProc && !appData.noChessProgram) {
12423       StartChessProgram(&first);
12424       InitChessProgram(&first, FALSE);
12425     }
12426     pn = positionNumber;
12427     if (positionNumber < 0) {
12428         /* Negative position number means to seek to that byte offset */
12429         if (fseek(f, -positionNumber, 0) == -1) {
12430             DisplayError(_("Can't seek on position file"), 0);
12431             return FALSE;
12432         };
12433         pn = 1;
12434     } else {
12435         if (fseek(f, 0, 0) == -1) {
12436             if (f == lastLoadPositionFP ?
12437                 positionNumber == lastLoadPositionNumber + 1 :
12438                 positionNumber == 1) {
12439                 pn = 1;
12440             } else {
12441                 DisplayError(_("Can't seek on position file"), 0);
12442                 return FALSE;
12443             }
12444         }
12445     }
12446     /* See if this file is FEN or old-style xboard */
12447     if (fgets(line, MSG_SIZ, f) == NULL) {
12448         DisplayError(_("Position not found in file"), 0);
12449         return FALSE;
12450     }
12451     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12452     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12453
12454     if (pn >= 2) {
12455         if (fenMode || line[0] == '#') pn--;
12456         while (pn > 0) {
12457             /* skip positions before number pn */
12458             if (fgets(line, MSG_SIZ, f) == NULL) {
12459                 Reset(TRUE, TRUE);
12460                 DisplayError(_("Position not found in file"), 0);
12461                 return FALSE;
12462             }
12463             if (fenMode || line[0] == '#') pn--;
12464         }
12465     }
12466
12467     if (fenMode) {
12468         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12469             DisplayError(_("Bad FEN position in file"), 0);
12470             return FALSE;
12471         }
12472     } else {
12473         (void) fgets(line, MSG_SIZ, f);
12474         (void) fgets(line, MSG_SIZ, f);
12475
12476         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12477             (void) fgets(line, MSG_SIZ, f);
12478             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12479                 if (*p == ' ')
12480                   continue;
12481                 initial_position[i][j++] = CharToPiece(*p);
12482             }
12483         }
12484
12485         blackPlaysFirst = FALSE;
12486         if (!feof(f)) {
12487             (void) fgets(line, MSG_SIZ, f);
12488             if (strncmp(line, "black", strlen("black"))==0)
12489               blackPlaysFirst = TRUE;
12490         }
12491     }
12492     startedFromSetupPosition = TRUE;
12493
12494     CopyBoard(boards[0], initial_position);
12495     if (blackPlaysFirst) {
12496         currentMove = forwardMostMove = backwardMostMove = 1;
12497         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12498         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12499         CopyBoard(boards[1], initial_position);
12500         DisplayMessage("", _("Black to play"));
12501     } else {
12502         currentMove = forwardMostMove = backwardMostMove = 0;
12503         DisplayMessage("", _("White to play"));
12504     }
12505     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12506     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12507         SendToProgram("force\n", &first);
12508         SendBoard(&first, forwardMostMove);
12509     }
12510     if (appData.debugMode) {
12511 int i, j;
12512   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12513   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12514         fprintf(debugFP, "Load Position\n");
12515     }
12516
12517     if (positionNumber > 1) {
12518       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12519         DisplayTitle(line);
12520     } else {
12521         DisplayTitle(title);
12522     }
12523     gameMode = EditGame;
12524     ModeHighlight();
12525     ResetClocks();
12526     timeRemaining[0][1] = whiteTimeRemaining;
12527     timeRemaining[1][1] = blackTimeRemaining;
12528     DrawPosition(FALSE, boards[currentMove]);
12529
12530     return TRUE;
12531 }
12532
12533
12534 void
12535 CopyPlayerNameIntoFileName (char **dest, char *src)
12536 {
12537     while (*src != NULLCHAR && *src != ',') {
12538         if (*src == ' ') {
12539             *(*dest)++ = '_';
12540             src++;
12541         } else {
12542             *(*dest)++ = *src++;
12543         }
12544     }
12545 }
12546
12547 char *
12548 DefaultFileName (char *ext)
12549 {
12550     static char def[MSG_SIZ];
12551     char *p;
12552
12553     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12554         p = def;
12555         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12556         *p++ = '-';
12557         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12558         *p++ = '.';
12559         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12560     } else {
12561         def[0] = NULLCHAR;
12562     }
12563     return def;
12564 }
12565
12566 /* Save the current game to the given file */
12567 int
12568 SaveGameToFile (char *filename, int append)
12569 {
12570     FILE *f;
12571     char buf[MSG_SIZ];
12572     int result, i, t,tot=0;
12573
12574     if (strcmp(filename, "-") == 0) {
12575         return SaveGame(stdout, 0, NULL);
12576     } else {
12577         for(i=0; i<10; i++) { // upto 10 tries
12578              f = fopen(filename, append ? "a" : "w");
12579              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12580              if(f || errno != 13) break;
12581              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12582              tot += t;
12583         }
12584         if (f == NULL) {
12585             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12586             DisplayError(buf, errno);
12587             return FALSE;
12588         } else {
12589             safeStrCpy(buf, lastMsg, MSG_SIZ);
12590             DisplayMessage(_("Waiting for access to save file"), "");
12591             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12592             DisplayMessage(_("Saving game"), "");
12593             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12594             result = SaveGame(f, 0, NULL);
12595             DisplayMessage(buf, "");
12596             return result;
12597         }
12598     }
12599 }
12600
12601 char *
12602 SavePart (char *str)
12603 {
12604     static char buf[MSG_SIZ];
12605     char *p;
12606
12607     p = strchr(str, ' ');
12608     if (p == NULL) return str;
12609     strncpy(buf, str, p - str);
12610     buf[p - str] = NULLCHAR;
12611     return buf;
12612 }
12613
12614 #define PGN_MAX_LINE 75
12615
12616 #define PGN_SIDE_WHITE  0
12617 #define PGN_SIDE_BLACK  1
12618
12619 static int
12620 FindFirstMoveOutOfBook (int side)
12621 {
12622     int result = -1;
12623
12624     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12625         int index = backwardMostMove;
12626         int has_book_hit = 0;
12627
12628         if( (index % 2) != side ) {
12629             index++;
12630         }
12631
12632         while( index < forwardMostMove ) {
12633             /* Check to see if engine is in book */
12634             int depth = pvInfoList[index].depth;
12635             int score = pvInfoList[index].score;
12636             int in_book = 0;
12637
12638             if( depth <= 2 ) {
12639                 in_book = 1;
12640             }
12641             else if( score == 0 && depth == 63 ) {
12642                 in_book = 1; /* Zappa */
12643             }
12644             else if( score == 2 && depth == 99 ) {
12645                 in_book = 1; /* Abrok */
12646             }
12647
12648             has_book_hit += in_book;
12649
12650             if( ! in_book ) {
12651                 result = index;
12652
12653                 break;
12654             }
12655
12656             index += 2;
12657         }
12658     }
12659
12660     return result;
12661 }
12662
12663 void
12664 GetOutOfBookInfo (char * buf)
12665 {
12666     int oob[2];
12667     int i;
12668     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12669
12670     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12671     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12672
12673     *buf = '\0';
12674
12675     if( oob[0] >= 0 || oob[1] >= 0 ) {
12676         for( i=0; i<2; i++ ) {
12677             int idx = oob[i];
12678
12679             if( idx >= 0 ) {
12680                 if( i > 0 && oob[0] >= 0 ) {
12681                     strcat( buf, "   " );
12682                 }
12683
12684                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12685                 sprintf( buf+strlen(buf), "%s%.2f",
12686                     pvInfoList[idx].score >= 0 ? "+" : "",
12687                     pvInfoList[idx].score / 100.0 );
12688             }
12689         }
12690     }
12691 }
12692
12693 /* Save game in PGN style and close the file */
12694 int
12695 SaveGamePGN (FILE *f)
12696 {
12697     int i, offset, linelen, newblock;
12698 //    char *movetext;
12699     char numtext[32];
12700     int movelen, numlen, blank;
12701     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12702
12703     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12704
12705     PrintPGNTags(f, &gameInfo);
12706
12707     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12708
12709     if (backwardMostMove > 0 || startedFromSetupPosition) {
12710         char *fen = PositionToFEN(backwardMostMove, NULL);
12711         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12712         fprintf(f, "\n{--------------\n");
12713         PrintPosition(f, backwardMostMove);
12714         fprintf(f, "--------------}\n");
12715         free(fen);
12716     }
12717     else {
12718         /* [AS] Out of book annotation */
12719         if( appData.saveOutOfBookInfo ) {
12720             char buf[64];
12721
12722             GetOutOfBookInfo( buf );
12723
12724             if( buf[0] != '\0' ) {
12725                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12726             }
12727         }
12728
12729         fprintf(f, "\n");
12730     }
12731
12732     i = backwardMostMove;
12733     linelen = 0;
12734     newblock = TRUE;
12735
12736     while (i < forwardMostMove) {
12737         /* Print comments preceding this move */
12738         if (commentList[i] != NULL) {
12739             if (linelen > 0) fprintf(f, "\n");
12740             fprintf(f, "%s", commentList[i]);
12741             linelen = 0;
12742             newblock = TRUE;
12743         }
12744
12745         /* Format move number */
12746         if ((i % 2) == 0)
12747           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12748         else
12749           if (newblock)
12750             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12751           else
12752             numtext[0] = NULLCHAR;
12753
12754         numlen = strlen(numtext);
12755         newblock = FALSE;
12756
12757         /* Print move number */
12758         blank = linelen > 0 && numlen > 0;
12759         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12760             fprintf(f, "\n");
12761             linelen = 0;
12762             blank = 0;
12763         }
12764         if (blank) {
12765             fprintf(f, " ");
12766             linelen++;
12767         }
12768         fprintf(f, "%s", numtext);
12769         linelen += numlen;
12770
12771         /* Get move */
12772         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12773         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12774
12775         /* Print move */
12776         blank = linelen > 0 && movelen > 0;
12777         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12778             fprintf(f, "\n");
12779             linelen = 0;
12780             blank = 0;
12781         }
12782         if (blank) {
12783             fprintf(f, " ");
12784             linelen++;
12785         }
12786         fprintf(f, "%s", move_buffer);
12787         linelen += movelen;
12788
12789         /* [AS] Add PV info if present */
12790         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12791             /* [HGM] add time */
12792             char buf[MSG_SIZ]; int seconds;
12793
12794             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12795
12796             if( seconds <= 0)
12797               buf[0] = 0;
12798             else
12799               if( seconds < 30 )
12800                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12801               else
12802                 {
12803                   seconds = (seconds + 4)/10; // round to full seconds
12804                   if( seconds < 60 )
12805                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12806                   else
12807                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12808                 }
12809
12810             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12811                       pvInfoList[i].score >= 0 ? "+" : "",
12812                       pvInfoList[i].score / 100.0,
12813                       pvInfoList[i].depth,
12814                       buf );
12815
12816             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12817
12818             /* Print score/depth */
12819             blank = linelen > 0 && movelen > 0;
12820             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12821                 fprintf(f, "\n");
12822                 linelen = 0;
12823                 blank = 0;
12824             }
12825             if (blank) {
12826                 fprintf(f, " ");
12827                 linelen++;
12828             }
12829             fprintf(f, "%s", move_buffer);
12830             linelen += movelen;
12831         }
12832
12833         i++;
12834     }
12835
12836     /* Start a new line */
12837     if (linelen > 0) fprintf(f, "\n");
12838
12839     /* Print comments after last move */
12840     if (commentList[i] != NULL) {
12841         fprintf(f, "%s\n", commentList[i]);
12842     }
12843
12844     /* Print result */
12845     if (gameInfo.resultDetails != NULL &&
12846         gameInfo.resultDetails[0] != NULLCHAR) {
12847         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12848                 PGNResult(gameInfo.result));
12849     } else {
12850         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12851     }
12852
12853     fclose(f);
12854     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12855     return TRUE;
12856 }
12857
12858 /* Save game in old style and close the file */
12859 int
12860 SaveGameOldStyle (FILE *f)
12861 {
12862     int i, offset;
12863     time_t tm;
12864
12865     tm = time((time_t *) NULL);
12866
12867     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12868     PrintOpponents(f);
12869
12870     if (backwardMostMove > 0 || startedFromSetupPosition) {
12871         fprintf(f, "\n[--------------\n");
12872         PrintPosition(f, backwardMostMove);
12873         fprintf(f, "--------------]\n");
12874     } else {
12875         fprintf(f, "\n");
12876     }
12877
12878     i = backwardMostMove;
12879     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12880
12881     while (i < forwardMostMove) {
12882         if (commentList[i] != NULL) {
12883             fprintf(f, "[%s]\n", commentList[i]);
12884         }
12885
12886         if ((i % 2) == 1) {
12887             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12888             i++;
12889         } else {
12890             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12891             i++;
12892             if (commentList[i] != NULL) {
12893                 fprintf(f, "\n");
12894                 continue;
12895             }
12896             if (i >= forwardMostMove) {
12897                 fprintf(f, "\n");
12898                 break;
12899             }
12900             fprintf(f, "%s\n", parseList[i]);
12901             i++;
12902         }
12903     }
12904
12905     if (commentList[i] != NULL) {
12906         fprintf(f, "[%s]\n", commentList[i]);
12907     }
12908
12909     /* This isn't really the old style, but it's close enough */
12910     if (gameInfo.resultDetails != NULL &&
12911         gameInfo.resultDetails[0] != NULLCHAR) {
12912         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12913                 gameInfo.resultDetails);
12914     } else {
12915         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12916     }
12917
12918     fclose(f);
12919     return TRUE;
12920 }
12921
12922 /* Save the current game to open file f and close the file */
12923 int
12924 SaveGame (FILE *f, int dummy, char *dummy2)
12925 {
12926     if (gameMode == EditPosition) EditPositionDone(TRUE);
12927     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12928     if (appData.oldSaveStyle)
12929       return SaveGameOldStyle(f);
12930     else
12931       return SaveGamePGN(f);
12932 }
12933
12934 /* Save the current position to the given file */
12935 int
12936 SavePositionToFile (char *filename)
12937 {
12938     FILE *f;
12939     char buf[MSG_SIZ];
12940
12941     if (strcmp(filename, "-") == 0) {
12942         return SavePosition(stdout, 0, NULL);
12943     } else {
12944         f = fopen(filename, "a");
12945         if (f == NULL) {
12946             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12947             DisplayError(buf, errno);
12948             return FALSE;
12949         } else {
12950             safeStrCpy(buf, lastMsg, MSG_SIZ);
12951             DisplayMessage(_("Waiting for access to save file"), "");
12952             flock(fileno(f), LOCK_EX); // [HGM] lock
12953             DisplayMessage(_("Saving position"), "");
12954             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12955             SavePosition(f, 0, NULL);
12956             DisplayMessage(buf, "");
12957             return TRUE;
12958         }
12959     }
12960 }
12961
12962 /* Save the current position to the given open file and close the file */
12963 int
12964 SavePosition (FILE *f, int dummy, char *dummy2)
12965 {
12966     time_t tm;
12967     char *fen;
12968
12969     if (gameMode == EditPosition) EditPositionDone(TRUE);
12970     if (appData.oldSaveStyle) {
12971         tm = time((time_t *) NULL);
12972
12973         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12974         PrintOpponents(f);
12975         fprintf(f, "[--------------\n");
12976         PrintPosition(f, currentMove);
12977         fprintf(f, "--------------]\n");
12978     } else {
12979         fen = PositionToFEN(currentMove, NULL);
12980         fprintf(f, "%s\n", fen);
12981         free(fen);
12982     }
12983     fclose(f);
12984     return TRUE;
12985 }
12986
12987 void
12988 ReloadCmailMsgEvent (int unregister)
12989 {
12990 #if !WIN32
12991     static char *inFilename = NULL;
12992     static char *outFilename;
12993     int i;
12994     struct stat inbuf, outbuf;
12995     int status;
12996
12997     /* Any registered moves are unregistered if unregister is set, */
12998     /* i.e. invoked by the signal handler */
12999     if (unregister) {
13000         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13001             cmailMoveRegistered[i] = FALSE;
13002             if (cmailCommentList[i] != NULL) {
13003                 free(cmailCommentList[i]);
13004                 cmailCommentList[i] = NULL;
13005             }
13006         }
13007         nCmailMovesRegistered = 0;
13008     }
13009
13010     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13011         cmailResult[i] = CMAIL_NOT_RESULT;
13012     }
13013     nCmailResults = 0;
13014
13015     if (inFilename == NULL) {
13016         /* Because the filenames are static they only get malloced once  */
13017         /* and they never get freed                                      */
13018         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13019         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13020
13021         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13022         sprintf(outFilename, "%s.out", appData.cmailGameName);
13023     }
13024
13025     status = stat(outFilename, &outbuf);
13026     if (status < 0) {
13027         cmailMailedMove = FALSE;
13028     } else {
13029         status = stat(inFilename, &inbuf);
13030         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13031     }
13032
13033     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13034        counts the games, notes how each one terminated, etc.
13035
13036        It would be nice to remove this kludge and instead gather all
13037        the information while building the game list.  (And to keep it
13038        in the game list nodes instead of having a bunch of fixed-size
13039        parallel arrays.)  Note this will require getting each game's
13040        termination from the PGN tags, as the game list builder does
13041        not process the game moves.  --mann
13042        */
13043     cmailMsgLoaded = TRUE;
13044     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13045
13046     /* Load first game in the file or popup game menu */
13047     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13048
13049 #endif /* !WIN32 */
13050     return;
13051 }
13052
13053 int
13054 RegisterMove ()
13055 {
13056     FILE *f;
13057     char string[MSG_SIZ];
13058
13059     if (   cmailMailedMove
13060         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13061         return TRUE;            /* Allow free viewing  */
13062     }
13063
13064     /* Unregister move to ensure that we don't leave RegisterMove        */
13065     /* with the move registered when the conditions for registering no   */
13066     /* longer hold                                                       */
13067     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13068         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13069         nCmailMovesRegistered --;
13070
13071         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13072           {
13073               free(cmailCommentList[lastLoadGameNumber - 1]);
13074               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13075           }
13076     }
13077
13078     if (cmailOldMove == -1) {
13079         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13080         return FALSE;
13081     }
13082
13083     if (currentMove > cmailOldMove + 1) {
13084         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13085         return FALSE;
13086     }
13087
13088     if (currentMove < cmailOldMove) {
13089         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13090         return FALSE;
13091     }
13092
13093     if (forwardMostMove > currentMove) {
13094         /* Silently truncate extra moves */
13095         TruncateGame();
13096     }
13097
13098     if (   (currentMove == cmailOldMove + 1)
13099         || (   (currentMove == cmailOldMove)
13100             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13101                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13102         if (gameInfo.result != GameUnfinished) {
13103             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13104         }
13105
13106         if (commentList[currentMove] != NULL) {
13107             cmailCommentList[lastLoadGameNumber - 1]
13108               = StrSave(commentList[currentMove]);
13109         }
13110         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13111
13112         if (appData.debugMode)
13113           fprintf(debugFP, "Saving %s for game %d\n",
13114                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13115
13116         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13117
13118         f = fopen(string, "w");
13119         if (appData.oldSaveStyle) {
13120             SaveGameOldStyle(f); /* also closes the file */
13121
13122             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13123             f = fopen(string, "w");
13124             SavePosition(f, 0, NULL); /* also closes the file */
13125         } else {
13126             fprintf(f, "{--------------\n");
13127             PrintPosition(f, currentMove);
13128             fprintf(f, "--------------}\n\n");
13129
13130             SaveGame(f, 0, NULL); /* also closes the file*/
13131         }
13132
13133         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13134         nCmailMovesRegistered ++;
13135     } else if (nCmailGames == 1) {
13136         DisplayError(_("You have not made a move yet"), 0);
13137         return FALSE;
13138     }
13139
13140     return TRUE;
13141 }
13142
13143 void
13144 MailMoveEvent ()
13145 {
13146 #if !WIN32
13147     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13148     FILE *commandOutput;
13149     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13150     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13151     int nBuffers;
13152     int i;
13153     int archived;
13154     char *arcDir;
13155
13156     if (! cmailMsgLoaded) {
13157         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13158         return;
13159     }
13160
13161     if (nCmailGames == nCmailResults) {
13162         DisplayError(_("No unfinished games"), 0);
13163         return;
13164     }
13165
13166 #if CMAIL_PROHIBIT_REMAIL
13167     if (cmailMailedMove) {
13168       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);
13169         DisplayError(msg, 0);
13170         return;
13171     }
13172 #endif
13173
13174     if (! (cmailMailedMove || RegisterMove())) return;
13175
13176     if (   cmailMailedMove
13177         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13178       snprintf(string, MSG_SIZ, partCommandString,
13179                appData.debugMode ? " -v" : "", appData.cmailGameName);
13180         commandOutput = popen(string, "r");
13181
13182         if (commandOutput == NULL) {
13183             DisplayError(_("Failed to invoke cmail"), 0);
13184         } else {
13185             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13186                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13187             }
13188             if (nBuffers > 1) {
13189                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13190                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13191                 nBytes = MSG_SIZ - 1;
13192             } else {
13193                 (void) memcpy(msg, buffer, nBytes);
13194             }
13195             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13196
13197             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13198                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13199
13200                 archived = TRUE;
13201                 for (i = 0; i < nCmailGames; i ++) {
13202                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13203                         archived = FALSE;
13204                     }
13205                 }
13206                 if (   archived
13207                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13208                         != NULL)) {
13209                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13210                            arcDir,
13211                            appData.cmailGameName,
13212                            gameInfo.date);
13213                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13214                     cmailMsgLoaded = FALSE;
13215                 }
13216             }
13217
13218             DisplayInformation(msg);
13219             pclose(commandOutput);
13220         }
13221     } else {
13222         if ((*cmailMsg) != '\0') {
13223             DisplayInformation(cmailMsg);
13224         }
13225     }
13226
13227     return;
13228 #endif /* !WIN32 */
13229 }
13230
13231 char *
13232 CmailMsg ()
13233 {
13234 #if WIN32
13235     return NULL;
13236 #else
13237     int  prependComma = 0;
13238     char number[5];
13239     char string[MSG_SIZ];       /* Space for game-list */
13240     int  i;
13241
13242     if (!cmailMsgLoaded) return "";
13243
13244     if (cmailMailedMove) {
13245       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13246     } else {
13247         /* Create a list of games left */
13248       snprintf(string, MSG_SIZ, "[");
13249         for (i = 0; i < nCmailGames; i ++) {
13250             if (! (   cmailMoveRegistered[i]
13251                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13252                 if (prependComma) {
13253                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13254                 } else {
13255                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13256                     prependComma = 1;
13257                 }
13258
13259                 strcat(string, number);
13260             }
13261         }
13262         strcat(string, "]");
13263
13264         if (nCmailMovesRegistered + nCmailResults == 0) {
13265             switch (nCmailGames) {
13266               case 1:
13267                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13268                 break;
13269
13270               case 2:
13271                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13272                 break;
13273
13274               default:
13275                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13276                          nCmailGames);
13277                 break;
13278             }
13279         } else {
13280             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13281               case 1:
13282                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13283                          string);
13284                 break;
13285
13286               case 0:
13287                 if (nCmailResults == nCmailGames) {
13288                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13289                 } else {
13290                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13291                 }
13292                 break;
13293
13294               default:
13295                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13296                          string);
13297             }
13298         }
13299     }
13300     return cmailMsg;
13301 #endif /* WIN32 */
13302 }
13303
13304 void
13305 ResetGameEvent ()
13306 {
13307     if (gameMode == Training)
13308       SetTrainingModeOff();
13309
13310     Reset(TRUE, TRUE);
13311     cmailMsgLoaded = FALSE;
13312     if (appData.icsActive) {
13313       SendToICS(ics_prefix);
13314       SendToICS("refresh\n");
13315     }
13316 }
13317
13318 void
13319 ExitEvent (int status)
13320 {
13321     exiting++;
13322     if (exiting > 2) {
13323       /* Give up on clean exit */
13324       exit(status);
13325     }
13326     if (exiting > 1) {
13327       /* Keep trying for clean exit */
13328       return;
13329     }
13330
13331     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13332
13333     if (telnetISR != NULL) {
13334       RemoveInputSource(telnetISR);
13335     }
13336     if (icsPR != NoProc) {
13337       DestroyChildProcess(icsPR, TRUE);
13338     }
13339
13340     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13341     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13342
13343     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13344     /* make sure this other one finishes before killing it!                  */
13345     if(endingGame) { int count = 0;
13346         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13347         while(endingGame && count++ < 10) DoSleep(1);
13348         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13349     }
13350
13351     /* Kill off chess programs */
13352     if (first.pr != NoProc) {
13353         ExitAnalyzeMode();
13354
13355         DoSleep( appData.delayBeforeQuit );
13356         SendToProgram("quit\n", &first);
13357         DoSleep( appData.delayAfterQuit );
13358         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13359     }
13360     if (second.pr != NoProc) {
13361         DoSleep( appData.delayBeforeQuit );
13362         SendToProgram("quit\n", &second);
13363         DoSleep( appData.delayAfterQuit );
13364         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13365     }
13366     if (first.isr != NULL) {
13367         RemoveInputSource(first.isr);
13368     }
13369     if (second.isr != NULL) {
13370         RemoveInputSource(second.isr);
13371     }
13372
13373     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13374     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13375
13376     ShutDownFrontEnd();
13377     exit(status);
13378 }
13379
13380 void
13381 PauseEngine (ChessProgramState *cps)
13382 {
13383     SendToProgram("pause\n", cps);
13384     cps->pause = 2;
13385 }
13386
13387 void
13388 UnPauseEngine (ChessProgramState *cps)
13389 {
13390     SendToProgram("resume\n", cps);
13391     cps->pause = 1;
13392 }
13393
13394 void
13395 PauseEvent ()
13396 {
13397     if (appData.debugMode)
13398         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13399     if (pausing) {
13400         pausing = FALSE;
13401         ModeHighlight();
13402         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13403             StartClocks();
13404             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13405                 if(stalledEngine->other->pause) UnPauseEngine(stalledEngine->other);
13406                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13407             }
13408             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13409             HandleMachineMove(stashedInputMove, stalledEngine);
13410             stalledEngine = NULL;
13411             return;
13412         }
13413         if (gameMode == MachinePlaysWhite ||
13414             gameMode == TwoMachinesPlay   ||
13415             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13416             if(first.pause)  UnPauseEngine(&first);
13417             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13418             if(second.pause) UnPauseEngine(&second);
13419             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13420             StartClocks();
13421         } else {
13422             DisplayBothClocks();
13423         }
13424         if (gameMode == PlayFromGameFile) {
13425             if (appData.timeDelay >= 0)
13426                 AutoPlayGameLoop();
13427         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13428             Reset(FALSE, TRUE);
13429             SendToICS(ics_prefix);
13430             SendToICS("refresh\n");
13431         } else if (currentMove < forwardMostMove) {
13432             ForwardInner(forwardMostMove);
13433         }
13434         pauseExamInvalid = FALSE;
13435     } else {
13436         switch (gameMode) {
13437           default:
13438             return;
13439           case IcsExamining:
13440             pauseExamForwardMostMove = forwardMostMove;
13441             pauseExamInvalid = FALSE;
13442             /* fall through */
13443           case IcsObserving:
13444           case IcsPlayingWhite:
13445           case IcsPlayingBlack:
13446             pausing = TRUE;
13447             ModeHighlight();
13448             return;
13449           case PlayFromGameFile:
13450             (void) StopLoadGameTimer();
13451             pausing = TRUE;
13452             ModeHighlight();
13453             break;
13454           case BeginningOfGame:
13455             if (appData.icsActive) return;
13456             /* else fall through */
13457           case MachinePlaysWhite:
13458           case MachinePlaysBlack:
13459           case TwoMachinesPlay:
13460             if (forwardMostMove == 0)
13461               return;           /* don't pause if no one has moved */
13462             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13463                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13464                 if(onMove->pause) {           // thinking engine can be paused
13465                     PauseEngine(onMove);      // do it
13466                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13467                         PauseEngine(onMove->other);
13468                     else
13469                         SendToProgram("easy\n", onMove->other);
13470                     StopClocks();
13471                 }
13472             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13473                 if(first.pause) {
13474                     PauseEngine(&first);
13475                     StopClocks();
13476                 }
13477             } else { // human on move, pause pondering by either method
13478                 if(first.pause) 
13479                     PauseEngine(&first);
13480                 else
13481                     SendToProgram("easy\n", &first);
13482                 StopClocks();
13483             }
13484             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13485           case AnalyzeMode:
13486             pausing = TRUE;
13487             ModeHighlight();
13488             break;
13489         }
13490     }
13491 }
13492
13493 void
13494 EditCommentEvent ()
13495 {
13496     char title[MSG_SIZ];
13497
13498     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13499       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13500     } else {
13501       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13502                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13503                parseList[currentMove - 1]);
13504     }
13505
13506     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13507 }
13508
13509
13510 void
13511 EditTagsEvent ()
13512 {
13513     char *tags = PGNTags(&gameInfo);
13514     bookUp = FALSE;
13515     EditTagsPopUp(tags, NULL);
13516     free(tags);
13517 }
13518
13519 void
13520 ToggleSecond ()
13521 {
13522   if(second.analyzing) {
13523     SendToProgram("exit\n", &second);
13524     second.analyzing = FALSE;
13525   } else {
13526     if (second.pr == NoProc) StartChessProgram(&second);
13527     InitChessProgram(&second, FALSE);
13528     FeedMovesToProgram(&second, currentMove);
13529
13530     SendToProgram("analyze\n", &second);
13531     second.analyzing = TRUE;
13532   }
13533 }
13534
13535 /* Toggle ShowThinking */
13536 void
13537 ToggleShowThinking()
13538 {
13539   appData.showThinking = !appData.showThinking;
13540   ShowThinkingEvent();
13541 }
13542
13543 int
13544 AnalyzeModeEvent ()
13545 {
13546     char buf[MSG_SIZ];
13547
13548     if (!first.analysisSupport) {
13549       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13550       DisplayError(buf, 0);
13551       return 0;
13552     }
13553     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13554     if (appData.icsActive) {
13555         if (gameMode != IcsObserving) {
13556           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13557             DisplayError(buf, 0);
13558             /* secure check */
13559             if (appData.icsEngineAnalyze) {
13560                 if (appData.debugMode)
13561                     fprintf(debugFP, _("Found unexpected active ICS engine analyze \n"));
13562                 ExitAnalyzeMode();
13563                 ModeHighlight();
13564             }
13565             return 0;
13566         }
13567         /* if enable, user wants to disable icsEngineAnalyze */
13568         if (appData.icsEngineAnalyze) {
13569                 ExitAnalyzeMode();
13570                 ModeHighlight();
13571                 return 0;
13572         }
13573         appData.icsEngineAnalyze = TRUE;
13574         if (appData.debugMode)
13575             fprintf(debugFP, _("ICS engine analyze starting... \n"));
13576     }
13577
13578     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13579     if (appData.noChessProgram || gameMode == AnalyzeMode)
13580       return 0;
13581
13582     if (gameMode != AnalyzeFile) {
13583         if (!appData.icsEngineAnalyze) {
13584                EditGameEvent();
13585                if (gameMode != EditGame) return 0;
13586         }
13587         if (!appData.showThinking) ToggleShowThinking();
13588         ResurrectChessProgram();
13589         SendToProgram("analyze\n", &first);
13590         first.analyzing = TRUE;
13591         /*first.maybeThinking = TRUE;*/
13592         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13593         EngineOutputPopUp();
13594     }
13595     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13596     pausing = FALSE;
13597     ModeHighlight();
13598     SetGameInfo();
13599
13600     StartAnalysisClock();
13601     GetTimeMark(&lastNodeCountTime);
13602     lastNodeCount = 0;
13603     return 1;
13604 }
13605
13606 void
13607 AnalyzeFileEvent ()
13608 {
13609     if (appData.noChessProgram || gameMode == AnalyzeFile)
13610       return;
13611
13612     if (!first.analysisSupport) {
13613       char buf[MSG_SIZ];
13614       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13615       DisplayError(buf, 0);
13616       return;
13617     }
13618
13619     if (gameMode != AnalyzeMode) {
13620         keepInfo = 1; // mere annotating should not alter PGN tags
13621         EditGameEvent();
13622         keepInfo = 0;
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
13636     StartAnalysisClock();
13637     GetTimeMark(&lastNodeCountTime);
13638     lastNodeCount = 0;
13639     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13640     AnalysisPeriodicEvent(1);
13641 }
13642
13643 void
13644 MachineWhiteEvent ()
13645 {
13646     char buf[MSG_SIZ];
13647     char *bookHit = NULL;
13648
13649     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13650       return;
13651
13652
13653     if (gameMode == PlayFromGameFile ||
13654         gameMode == TwoMachinesPlay  ||
13655         gameMode == Training         ||
13656         gameMode == AnalyzeMode      ||
13657         gameMode == EndOfGame)
13658         EditGameEvent();
13659
13660     if (gameMode == EditPosition)
13661         EditPositionDone(TRUE);
13662
13663     if (!WhiteOnMove(currentMove)) {
13664         DisplayError(_("It is not White's turn"), 0);
13665         return;
13666     }
13667
13668     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13669       ExitAnalyzeMode();
13670
13671     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13672         gameMode == AnalyzeFile)
13673         TruncateGame();
13674
13675     ResurrectChessProgram();    /* in case it isn't running */
13676     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13677         gameMode = MachinePlaysWhite;
13678         ResetClocks();
13679     } else
13680     gameMode = MachinePlaysWhite;
13681     pausing = FALSE;
13682     ModeHighlight();
13683     SetGameInfo();
13684     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13685     DisplayTitle(buf);
13686     if (first.sendName) {
13687       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13688       SendToProgram(buf, &first);
13689     }
13690     if (first.sendTime) {
13691       if (first.useColors) {
13692         SendToProgram("black\n", &first); /*gnu kludge*/
13693       }
13694       SendTimeRemaining(&first, TRUE);
13695     }
13696     if (first.useColors) {
13697       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13698     }
13699     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13700     SetMachineThinkingEnables();
13701     first.maybeThinking = TRUE;
13702     StartClocks();
13703     firstMove = FALSE;
13704
13705     if (appData.autoFlipView && !flipView) {
13706       flipView = !flipView;
13707       DrawPosition(FALSE, NULL);
13708       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13709     }
13710
13711     if(bookHit) { // [HGM] book: simulate book reply
13712         static char bookMove[MSG_SIZ]; // a bit generous?
13713
13714         programStats.nodes = programStats.depth = programStats.time =
13715         programStats.score = programStats.got_only_move = 0;
13716         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13717
13718         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13719         strcat(bookMove, bookHit);
13720         HandleMachineMove(bookMove, &first);
13721     }
13722 }
13723
13724 void
13725 MachineBlackEvent ()
13726 {
13727   char buf[MSG_SIZ];
13728   char *bookHit = NULL;
13729
13730     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13731         return;
13732
13733
13734     if (gameMode == PlayFromGameFile ||
13735         gameMode == TwoMachinesPlay  ||
13736         gameMode == Training         ||
13737         gameMode == AnalyzeMode      ||
13738         gameMode == EndOfGame)
13739         EditGameEvent();
13740
13741     if (gameMode == EditPosition)
13742         EditPositionDone(TRUE);
13743
13744     if (WhiteOnMove(currentMove)) {
13745         DisplayError(_("It is not Black's turn"), 0);
13746         return;
13747     }
13748
13749     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13750       ExitAnalyzeMode();
13751
13752     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13753         gameMode == AnalyzeFile)
13754         TruncateGame();
13755
13756     ResurrectChessProgram();    /* in case it isn't running */
13757     gameMode = MachinePlaysBlack;
13758     pausing = FALSE;
13759     ModeHighlight();
13760     SetGameInfo();
13761     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13762     DisplayTitle(buf);
13763     if (first.sendName) {
13764       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13765       SendToProgram(buf, &first);
13766     }
13767     if (first.sendTime) {
13768       if (first.useColors) {
13769         SendToProgram("white\n", &first); /*gnu kludge*/
13770       }
13771       SendTimeRemaining(&first, FALSE);
13772     }
13773     if (first.useColors) {
13774       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13775     }
13776     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13777     SetMachineThinkingEnables();
13778     first.maybeThinking = TRUE;
13779     StartClocks();
13780
13781     if (appData.autoFlipView && flipView) {
13782       flipView = !flipView;
13783       DrawPosition(FALSE, NULL);
13784       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13785     }
13786     if(bookHit) { // [HGM] book: simulate book reply
13787         static char bookMove[MSG_SIZ]; // a bit generous?
13788
13789         programStats.nodes = programStats.depth = programStats.time =
13790         programStats.score = programStats.got_only_move = 0;
13791         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13792
13793         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13794         strcat(bookMove, bookHit);
13795         HandleMachineMove(bookMove, &first);
13796     }
13797 }
13798
13799
13800 void
13801 DisplayTwoMachinesTitle ()
13802 {
13803     char buf[MSG_SIZ];
13804     if (appData.matchGames > 0) {
13805         if(appData.tourneyFile[0]) {
13806           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13807                    gameInfo.white, _("vs."), gameInfo.black,
13808                    nextGame+1, appData.matchGames+1,
13809                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13810         } else 
13811         if (first.twoMachinesColor[0] == 'w') {
13812           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13813                    gameInfo.white, _("vs."),  gameInfo.black,
13814                    first.matchWins, second.matchWins,
13815                    matchGame - 1 - (first.matchWins + second.matchWins));
13816         } else {
13817           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13818                    gameInfo.white, _("vs."), gameInfo.black,
13819                    second.matchWins, first.matchWins,
13820                    matchGame - 1 - (first.matchWins + second.matchWins));
13821         }
13822     } else {
13823       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13824     }
13825     DisplayTitle(buf);
13826 }
13827
13828 void
13829 SettingsMenuIfReady ()
13830 {
13831   if (second.lastPing != second.lastPong) {
13832     DisplayMessage("", _("Waiting for second chess program"));
13833     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13834     return;
13835   }
13836   ThawUI();
13837   DisplayMessage("", "");
13838   SettingsPopUp(&second);
13839 }
13840
13841 int
13842 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13843 {
13844     char buf[MSG_SIZ];
13845     if (cps->pr == NoProc) {
13846         StartChessProgram(cps);
13847         if (cps->protocolVersion == 1) {
13848           retry();
13849         } else {
13850           /* kludge: allow timeout for initial "feature" command */
13851           FreezeUI();
13852           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13853           DisplayMessage("", buf);
13854           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13855         }
13856         return 1;
13857     }
13858     return 0;
13859 }
13860
13861 void
13862 TwoMachinesEvent P((void))
13863 {
13864     int i;
13865     char buf[MSG_SIZ];
13866     ChessProgramState *onmove;
13867     char *bookHit = NULL;
13868     static int stalling = 0;
13869     TimeMark now;
13870     long wait;
13871
13872     if (appData.noChessProgram) return;
13873
13874     switch (gameMode) {
13875       case TwoMachinesPlay:
13876         return;
13877       case MachinePlaysWhite:
13878       case MachinePlaysBlack:
13879         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13880             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13881             return;
13882         }
13883         /* fall through */
13884       case BeginningOfGame:
13885       case PlayFromGameFile:
13886       case EndOfGame:
13887         EditGameEvent();
13888         if (gameMode != EditGame) return;
13889         break;
13890       case EditPosition:
13891         EditPositionDone(TRUE);
13892         break;
13893       case AnalyzeMode:
13894       case AnalyzeFile:
13895         ExitAnalyzeMode();
13896         break;
13897       case EditGame:
13898       default:
13899         break;
13900     }
13901
13902 //    forwardMostMove = currentMove;
13903     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13904
13905     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13906
13907     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13908     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13909       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13910       return;
13911     }
13912
13913     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13914         DisplayError("second engine does not play this", 0);
13915         return;
13916     }
13917
13918     if(!stalling) {
13919       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13920       SendToProgram("force\n", &second);
13921       stalling = 1;
13922       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13923       return;
13924     }
13925     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13926     if(appData.matchPause>10000 || appData.matchPause<10)
13927                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13928     wait = SubtractTimeMarks(&now, &pauseStart);
13929     if(wait < appData.matchPause) {
13930         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13931         return;
13932     }
13933     // we are now committed to starting the game
13934     stalling = 0;
13935     DisplayMessage("", "");
13936     if (startedFromSetupPosition) {
13937         SendBoard(&second, backwardMostMove);
13938     if (appData.debugMode) {
13939         fprintf(debugFP, "Two Machines\n");
13940     }
13941     }
13942     for (i = backwardMostMove; i < forwardMostMove; i++) {
13943         SendMoveToProgram(i, &second);
13944     }
13945
13946     gameMode = TwoMachinesPlay;
13947     pausing = FALSE;
13948     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13949     SetGameInfo();
13950     DisplayTwoMachinesTitle();
13951     firstMove = TRUE;
13952     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13953         onmove = &first;
13954     } else {
13955         onmove = &second;
13956     }
13957     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13958     SendToProgram(first.computerString, &first);
13959     if (first.sendName) {
13960       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13961       SendToProgram(buf, &first);
13962     }
13963     SendToProgram(second.computerString, &second);
13964     if (second.sendName) {
13965       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13966       SendToProgram(buf, &second);
13967     }
13968
13969     ResetClocks();
13970     if (!first.sendTime || !second.sendTime) {
13971         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13972         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13973     }
13974     if (onmove->sendTime) {
13975       if (onmove->useColors) {
13976         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13977       }
13978       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13979     }
13980     if (onmove->useColors) {
13981       SendToProgram(onmove->twoMachinesColor, onmove);
13982     }
13983     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13984 //    SendToProgram("go\n", onmove);
13985     onmove->maybeThinking = TRUE;
13986     SetMachineThinkingEnables();
13987
13988     StartClocks();
13989
13990     if(bookHit) { // [HGM] book: simulate book reply
13991         static char bookMove[MSG_SIZ]; // a bit generous?
13992
13993         programStats.nodes = programStats.depth = programStats.time =
13994         programStats.score = programStats.got_only_move = 0;
13995         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13996
13997         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13998         strcat(bookMove, bookHit);
13999         savedMessage = bookMove; // args for deferred call
14000         savedState = onmove;
14001         ScheduleDelayedEvent(DeferredBookMove, 1);
14002     }
14003 }
14004
14005 void
14006 TrainingEvent ()
14007 {
14008     if (gameMode == Training) {
14009       SetTrainingModeOff();
14010       gameMode = PlayFromGameFile;
14011       DisplayMessage("", _("Training mode off"));
14012     } else {
14013       gameMode = Training;
14014       animateTraining = appData.animate;
14015
14016       /* make sure we are not already at the end of the game */
14017       if (currentMove < forwardMostMove) {
14018         SetTrainingModeOn();
14019         DisplayMessage("", _("Training mode on"));
14020       } else {
14021         gameMode = PlayFromGameFile;
14022         DisplayError(_("Already at end of game"), 0);
14023       }
14024     }
14025     ModeHighlight();
14026 }
14027
14028 void
14029 IcsClientEvent ()
14030 {
14031     if (!appData.icsActive) return;
14032     switch (gameMode) {
14033       case IcsPlayingWhite:
14034       case IcsPlayingBlack:
14035       case IcsObserving:
14036       case IcsIdle:
14037       case BeginningOfGame:
14038       case IcsExamining:
14039         return;
14040
14041       case EditGame:
14042         break;
14043
14044       case EditPosition:
14045         EditPositionDone(TRUE);
14046         break;
14047
14048       case AnalyzeMode:
14049       case AnalyzeFile:
14050         ExitAnalyzeMode();
14051         break;
14052
14053       default:
14054         EditGameEvent();
14055         break;
14056     }
14057
14058     gameMode = IcsIdle;
14059     ModeHighlight();
14060     return;
14061 }
14062
14063 void
14064 EditGameEvent ()
14065 {
14066     int i;
14067
14068     switch (gameMode) {
14069       case Training:
14070         SetTrainingModeOff();
14071         break;
14072       case MachinePlaysWhite:
14073       case MachinePlaysBlack:
14074       case BeginningOfGame:
14075         SendToProgram("force\n", &first);
14076         SetUserThinkingEnables();
14077         break;
14078       case PlayFromGameFile:
14079         (void) StopLoadGameTimer();
14080         if (gameFileFP != NULL) {
14081             gameFileFP = NULL;
14082         }
14083         break;
14084       case EditPosition:
14085         EditPositionDone(TRUE);
14086         break;
14087       case AnalyzeMode:
14088       case AnalyzeFile:
14089         ExitAnalyzeMode();
14090         SendToProgram("force\n", &first);
14091         break;
14092       case TwoMachinesPlay:
14093         GameEnds(EndOfFile, NULL, GE_PLAYER);
14094         ResurrectChessProgram();
14095         SetUserThinkingEnables();
14096         break;
14097       case EndOfGame:
14098         ResurrectChessProgram();
14099         break;
14100       case IcsPlayingBlack:
14101       case IcsPlayingWhite:
14102         DisplayError(_("Warning: You are still playing a game"), 0);
14103         break;
14104       case IcsObserving:
14105         DisplayError(_("Warning: You are still observing a game"), 0);
14106         break;
14107       case IcsExamining:
14108         DisplayError(_("Warning: You are still examining a game"), 0);
14109         break;
14110       case IcsIdle:
14111         break;
14112       case EditGame:
14113       default:
14114         return;
14115     }
14116
14117     pausing = FALSE;
14118     StopClocks();
14119     first.offeredDraw = second.offeredDraw = 0;
14120
14121     if (gameMode == PlayFromGameFile) {
14122         whiteTimeRemaining = timeRemaining[0][currentMove];
14123         blackTimeRemaining = timeRemaining[1][currentMove];
14124         DisplayTitle("");
14125     }
14126
14127     if (gameMode == MachinePlaysWhite ||
14128         gameMode == MachinePlaysBlack ||
14129         gameMode == TwoMachinesPlay ||
14130         gameMode == EndOfGame) {
14131         i = forwardMostMove;
14132         while (i > currentMove) {
14133             SendToProgram("undo\n", &first);
14134             i--;
14135         }
14136         if(!adjustedClock) {
14137         whiteTimeRemaining = timeRemaining[0][currentMove];
14138         blackTimeRemaining = timeRemaining[1][currentMove];
14139         DisplayBothClocks();
14140         }
14141         if (whiteFlag || blackFlag) {
14142             whiteFlag = blackFlag = 0;
14143         }
14144         DisplayTitle("");
14145     }
14146
14147     gameMode = EditGame;
14148     ModeHighlight();
14149     SetGameInfo();
14150 }
14151
14152
14153 void
14154 EditPositionEvent ()
14155 {
14156     if (gameMode == EditPosition) {
14157         EditGameEvent();
14158         return;
14159     }
14160
14161     EditGameEvent();
14162     if (gameMode != EditGame) return;
14163
14164     gameMode = EditPosition;
14165     ModeHighlight();
14166     SetGameInfo();
14167     if (currentMove > 0)
14168       CopyBoard(boards[0], boards[currentMove]);
14169
14170     blackPlaysFirst = !WhiteOnMove(currentMove);
14171     ResetClocks();
14172     currentMove = forwardMostMove = backwardMostMove = 0;
14173     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14174     DisplayMove(-1);
14175     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14176 }
14177
14178 void
14179 ExitAnalyzeMode ()
14180 {
14181     /* [DM] icsEngineAnalyze - possible call from other functions */
14182     if (appData.icsEngineAnalyze) {
14183         appData.icsEngineAnalyze = FALSE;
14184
14185         DisplayMessage("",_("Close ICS engine analyze..."));
14186     }
14187     if (first.analysisSupport && first.analyzing) {
14188       SendToBoth("exit\n");
14189       first.analyzing = second.analyzing = FALSE;
14190     }
14191     thinkOutput[0] = NULLCHAR;
14192 }
14193
14194 void
14195 EditPositionDone (Boolean fakeRights)
14196 {
14197     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14198
14199     startedFromSetupPosition = TRUE;
14200     InitChessProgram(&first, FALSE);
14201     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14202       boards[0][EP_STATUS] = EP_NONE;
14203       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14204       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14205         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14206         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14207       } else boards[0][CASTLING][2] = NoRights;
14208       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14209         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14210         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14211       } else boards[0][CASTLING][5] = NoRights;
14212       if(gameInfo.variant == VariantSChess) {
14213         int i;
14214         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14215           boards[0][VIRGIN][i] = 0;
14216           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14217           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14218         }
14219       }
14220     }
14221     SendToProgram("force\n", &first);
14222     if (blackPlaysFirst) {
14223         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14224         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14225         currentMove = forwardMostMove = backwardMostMove = 1;
14226         CopyBoard(boards[1], boards[0]);
14227     } else {
14228         currentMove = forwardMostMove = backwardMostMove = 0;
14229     }
14230     SendBoard(&first, forwardMostMove);
14231     if (appData.debugMode) {
14232         fprintf(debugFP, "EditPosDone\n");
14233     }
14234     DisplayTitle("");
14235     DisplayMessage("", "");
14236     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14237     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14238     gameMode = EditGame;
14239     ModeHighlight();
14240     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14241     ClearHighlights(); /* [AS] */
14242 }
14243
14244 /* Pause for `ms' milliseconds */
14245 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14246 void
14247 TimeDelay (long ms)
14248 {
14249     TimeMark m1, m2;
14250
14251     GetTimeMark(&m1);
14252     do {
14253         GetTimeMark(&m2);
14254     } while (SubtractTimeMarks(&m2, &m1) < ms);
14255 }
14256
14257 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14258 void
14259 SendMultiLineToICS (char *buf)
14260 {
14261     char temp[MSG_SIZ+1], *p;
14262     int len;
14263
14264     len = strlen(buf);
14265     if (len > MSG_SIZ)
14266       len = MSG_SIZ;
14267
14268     strncpy(temp, buf, len);
14269     temp[len] = 0;
14270
14271     p = temp;
14272     while (*p) {
14273         if (*p == '\n' || *p == '\r')
14274           *p = ' ';
14275         ++p;
14276     }
14277
14278     strcat(temp, "\n");
14279     SendToICS(temp);
14280     SendToPlayer(temp, strlen(temp));
14281 }
14282
14283 void
14284 SetWhiteToPlayEvent ()
14285 {
14286     if (gameMode == EditPosition) {
14287         blackPlaysFirst = FALSE;
14288         DisplayBothClocks();    /* works because currentMove is 0 */
14289     } else if (gameMode == IcsExamining) {
14290         SendToICS(ics_prefix);
14291         SendToICS("tomove white\n");
14292     }
14293 }
14294
14295 void
14296 SetBlackToPlayEvent ()
14297 {
14298     if (gameMode == EditPosition) {
14299         blackPlaysFirst = TRUE;
14300         currentMove = 1;        /* kludge */
14301         DisplayBothClocks();
14302         currentMove = 0;
14303     } else if (gameMode == IcsExamining) {
14304         SendToICS(ics_prefix);
14305         SendToICS("tomove black\n");
14306     }
14307 }
14308
14309 void
14310 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14311 {
14312     char buf[MSG_SIZ];
14313     ChessSquare piece = boards[0][y][x];
14314
14315     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14316
14317     switch (selection) {
14318       case ClearBoard:
14319         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14320             SendToICS(ics_prefix);
14321             SendToICS("bsetup clear\n");
14322         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14323             SendToICS(ics_prefix);
14324             SendToICS("clearboard\n");
14325         } else {
14326             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14327                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14328                 for (y = 0; y < BOARD_HEIGHT; y++) {
14329                     if (gameMode == IcsExamining) {
14330                         if (boards[currentMove][y][x] != EmptySquare) {
14331                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14332                                     AAA + x, ONE + y);
14333                             SendToICS(buf);
14334                         }
14335                     } else {
14336                         boards[0][y][x] = p;
14337                     }
14338                 }
14339             }
14340         }
14341         if (gameMode == EditPosition) {
14342             DrawPosition(FALSE, boards[0]);
14343         }
14344         break;
14345
14346       case WhitePlay:
14347         SetWhiteToPlayEvent();
14348         break;
14349
14350       case BlackPlay:
14351         SetBlackToPlayEvent();
14352         break;
14353
14354       case EmptySquare:
14355         if (gameMode == IcsExamining) {
14356             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14357             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14358             SendToICS(buf);
14359         } else {
14360             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14361                 if(x == BOARD_LEFT-2) {
14362                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14363                     boards[0][y][1] = 0;
14364                 } else
14365                 if(x == BOARD_RGHT+1) {
14366                     if(y >= gameInfo.holdingsSize) break;
14367                     boards[0][y][BOARD_WIDTH-2] = 0;
14368                 } else break;
14369             }
14370             boards[0][y][x] = EmptySquare;
14371             DrawPosition(FALSE, boards[0]);
14372         }
14373         break;
14374
14375       case PromotePiece:
14376         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14377            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14378             selection = (ChessSquare) (PROMOTED piece);
14379         } else if(piece == EmptySquare) selection = WhiteSilver;
14380         else selection = (ChessSquare)((int)piece - 1);
14381         goto defaultlabel;
14382
14383       case DemotePiece:
14384         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14385            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14386             selection = (ChessSquare) (DEMOTED piece);
14387         } else if(piece == EmptySquare) selection = BlackSilver;
14388         else selection = (ChessSquare)((int)piece + 1);
14389         goto defaultlabel;
14390
14391       case WhiteQueen:
14392       case BlackQueen:
14393         if(gameInfo.variant == VariantShatranj ||
14394            gameInfo.variant == VariantXiangqi  ||
14395            gameInfo.variant == VariantCourier  ||
14396            gameInfo.variant == VariantMakruk     )
14397             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14398         goto defaultlabel;
14399
14400       case WhiteKing:
14401       case BlackKing:
14402         if(gameInfo.variant == VariantXiangqi)
14403             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14404         if(gameInfo.variant == VariantKnightmate)
14405             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14406       default:
14407         defaultlabel:
14408         if (gameMode == IcsExamining) {
14409             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14410             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14411                      PieceToChar(selection), AAA + x, ONE + y);
14412             SendToICS(buf);
14413         } else {
14414             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14415                 int n;
14416                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14417                     n = PieceToNumber(selection - BlackPawn);
14418                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14419                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14420                     boards[0][BOARD_HEIGHT-1-n][1]++;
14421                 } else
14422                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14423                     n = PieceToNumber(selection);
14424                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14425                     boards[0][n][BOARD_WIDTH-1] = selection;
14426                     boards[0][n][BOARD_WIDTH-2]++;
14427                 }
14428             } else
14429             boards[0][y][x] = selection;
14430             DrawPosition(TRUE, boards[0]);
14431             ClearHighlights();
14432             fromX = fromY = -1;
14433         }
14434         break;
14435     }
14436 }
14437
14438
14439 void
14440 DropMenuEvent (ChessSquare selection, int x, int y)
14441 {
14442     ChessMove moveType;
14443
14444     switch (gameMode) {
14445       case IcsPlayingWhite:
14446       case MachinePlaysBlack:
14447         if (!WhiteOnMove(currentMove)) {
14448             DisplayMoveError(_("It is Black's turn"));
14449             return;
14450         }
14451         moveType = WhiteDrop;
14452         break;
14453       case IcsPlayingBlack:
14454       case MachinePlaysWhite:
14455         if (WhiteOnMove(currentMove)) {
14456             DisplayMoveError(_("It is White's turn"));
14457             return;
14458         }
14459         moveType = BlackDrop;
14460         break;
14461       case EditGame:
14462         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14463         break;
14464       default:
14465         return;
14466     }
14467
14468     if (moveType == BlackDrop && selection < BlackPawn) {
14469       selection = (ChessSquare) ((int) selection
14470                                  + (int) BlackPawn - (int) WhitePawn);
14471     }
14472     if (boards[currentMove][y][x] != EmptySquare) {
14473         DisplayMoveError(_("That square is occupied"));
14474         return;
14475     }
14476
14477     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14478 }
14479
14480 void
14481 AcceptEvent ()
14482 {
14483     /* Accept a pending offer of any kind from opponent */
14484
14485     if (appData.icsActive) {
14486         SendToICS(ics_prefix);
14487         SendToICS("accept\n");
14488     } else if (cmailMsgLoaded) {
14489         if (currentMove == cmailOldMove &&
14490             commentList[cmailOldMove] != NULL &&
14491             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14492                    "Black offers a draw" : "White offers a draw")) {
14493             TruncateGame();
14494             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14495             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14496         } else {
14497             DisplayError(_("There is no pending offer on this move"), 0);
14498             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14499         }
14500     } else {
14501         /* Not used for offers from chess program */
14502     }
14503 }
14504
14505 void
14506 DeclineEvent ()
14507 {
14508     /* Decline a pending offer of any kind from opponent */
14509
14510     if (appData.icsActive) {
14511         SendToICS(ics_prefix);
14512         SendToICS("decline\n");
14513     } else if (cmailMsgLoaded) {
14514         if (currentMove == cmailOldMove &&
14515             commentList[cmailOldMove] != NULL &&
14516             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14517                    "Black offers a draw" : "White offers a draw")) {
14518 #ifdef NOTDEF
14519             AppendComment(cmailOldMove, "Draw declined", TRUE);
14520             DisplayComment(cmailOldMove - 1, "Draw declined");
14521 #endif /*NOTDEF*/
14522         } else {
14523             DisplayError(_("There is no pending offer on this move"), 0);
14524         }
14525     } else {
14526         /* Not used for offers from chess program */
14527     }
14528 }
14529
14530 void
14531 RematchEvent ()
14532 {
14533     /* Issue ICS rematch command */
14534     if (appData.icsActive) {
14535         SendToICS(ics_prefix);
14536         SendToICS("rematch\n");
14537     }
14538 }
14539
14540 void
14541 CallFlagEvent ()
14542 {
14543     /* Call your opponent's flag (claim a win on time) */
14544     if (appData.icsActive) {
14545         SendToICS(ics_prefix);
14546         SendToICS("flag\n");
14547     } else {
14548         switch (gameMode) {
14549           default:
14550             return;
14551           case MachinePlaysWhite:
14552             if (whiteFlag) {
14553                 if (blackFlag)
14554                   GameEnds(GameIsDrawn, "Both players ran out of time",
14555                            GE_PLAYER);
14556                 else
14557                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14558             } else {
14559                 DisplayError(_("Your opponent is not out of time"), 0);
14560             }
14561             break;
14562           case MachinePlaysBlack:
14563             if (blackFlag) {
14564                 if (whiteFlag)
14565                   GameEnds(GameIsDrawn, "Both players ran out of time",
14566                            GE_PLAYER);
14567                 else
14568                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14569             } else {
14570                 DisplayError(_("Your opponent is not out of time"), 0);
14571             }
14572             break;
14573         }
14574     }
14575 }
14576
14577 void
14578 ClockClick (int which)
14579 {       // [HGM] code moved to back-end from winboard.c
14580         if(which) { // black clock
14581           if (gameMode == EditPosition || gameMode == IcsExamining) {
14582             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14583             SetBlackToPlayEvent();
14584           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14585           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14586           } else if (shiftKey) {
14587             AdjustClock(which, -1);
14588           } else if (gameMode == IcsPlayingWhite ||
14589                      gameMode == MachinePlaysBlack) {
14590             CallFlagEvent();
14591           }
14592         } else { // white clock
14593           if (gameMode == EditPosition || gameMode == IcsExamining) {
14594             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14595             SetWhiteToPlayEvent();
14596           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14597           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14598           } else if (shiftKey) {
14599             AdjustClock(which, -1);
14600           } else if (gameMode == IcsPlayingBlack ||
14601                    gameMode == MachinePlaysWhite) {
14602             CallFlagEvent();
14603           }
14604         }
14605 }
14606
14607 void
14608 DrawEvent ()
14609 {
14610     /* Offer draw or accept pending draw offer from opponent */
14611
14612     if (appData.icsActive) {
14613         /* Note: tournament rules require draw offers to be
14614            made after you make your move but before you punch
14615            your clock.  Currently ICS doesn't let you do that;
14616            instead, you immediately punch your clock after making
14617            a move, but you can offer a draw at any time. */
14618
14619         SendToICS(ics_prefix);
14620         SendToICS("draw\n");
14621         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14622     } else if (cmailMsgLoaded) {
14623         if (currentMove == cmailOldMove &&
14624             commentList[cmailOldMove] != NULL &&
14625             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14626                    "Black offers a draw" : "White offers a draw")) {
14627             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14628             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14629         } else if (currentMove == cmailOldMove + 1) {
14630             char *offer = WhiteOnMove(cmailOldMove) ?
14631               "White offers a draw" : "Black offers a draw";
14632             AppendComment(currentMove, offer, TRUE);
14633             DisplayComment(currentMove - 1, offer);
14634             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14635         } else {
14636             DisplayError(_("You must make your move before offering a draw"), 0);
14637             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14638         }
14639     } else if (first.offeredDraw) {
14640         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14641     } else {
14642         if (first.sendDrawOffers) {
14643             SendToProgram("draw\n", &first);
14644             userOfferedDraw = TRUE;
14645         }
14646     }
14647 }
14648
14649 void
14650 AdjournEvent ()
14651 {
14652     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14653
14654     if (appData.icsActive) {
14655         SendToICS(ics_prefix);
14656         SendToICS("adjourn\n");
14657     } else {
14658         /* Currently GNU Chess doesn't offer or accept Adjourns */
14659     }
14660 }
14661
14662
14663 void
14664 AbortEvent ()
14665 {
14666     /* Offer Abort or accept pending Abort offer from opponent */
14667
14668     if (appData.icsActive) {
14669         SendToICS(ics_prefix);
14670         SendToICS("abort\n");
14671     } else {
14672         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14673     }
14674 }
14675
14676 void
14677 ResignEvent ()
14678 {
14679     /* Resign.  You can do this even if it's not your turn. */
14680
14681     if (appData.icsActive) {
14682         SendToICS(ics_prefix);
14683         SendToICS("resign\n");
14684     } else {
14685         switch (gameMode) {
14686           case MachinePlaysWhite:
14687             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14688             break;
14689           case MachinePlaysBlack:
14690             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14691             break;
14692           case EditGame:
14693             if (cmailMsgLoaded) {
14694                 TruncateGame();
14695                 if (WhiteOnMove(cmailOldMove)) {
14696                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14697                 } else {
14698                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14699                 }
14700                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14701             }
14702             break;
14703           default:
14704             break;
14705         }
14706     }
14707 }
14708
14709
14710 void
14711 StopObservingEvent ()
14712 {
14713     /* Stop observing current games */
14714     SendToICS(ics_prefix);
14715     SendToICS("unobserve\n");
14716 }
14717
14718 void
14719 StopExaminingEvent ()
14720 {
14721     /* Stop observing current game */
14722     SendToICS(ics_prefix);
14723     SendToICS("unexamine\n");
14724 }
14725
14726 void
14727 ForwardInner (int target)
14728 {
14729     int limit; int oldSeekGraphUp = seekGraphUp;
14730
14731     if (appData.debugMode)
14732         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14733                 target, currentMove, forwardMostMove);
14734
14735     if (gameMode == EditPosition)
14736       return;
14737
14738     seekGraphUp = FALSE;
14739     MarkTargetSquares(1);
14740
14741     if (gameMode == PlayFromGameFile && !pausing)
14742       PauseEvent();
14743
14744     if (gameMode == IcsExamining && pausing)
14745       limit = pauseExamForwardMostMove;
14746     else
14747       limit = forwardMostMove;
14748
14749     if (target > limit) target = limit;
14750
14751     if (target > 0 && moveList[target - 1][0]) {
14752         int fromX, fromY, toX, toY;
14753         toX = moveList[target - 1][2] - AAA;
14754         toY = moveList[target - 1][3] - ONE;
14755         if (moveList[target - 1][1] == '@') {
14756             if (appData.highlightLastMove) {
14757                 SetHighlights(-1, -1, toX, toY);
14758             }
14759         } else {
14760             fromX = moveList[target - 1][0] - AAA;
14761             fromY = moveList[target - 1][1] - ONE;
14762             if (target == currentMove + 1) {
14763                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14764             }
14765             if (appData.highlightLastMove) {
14766                 SetHighlights(fromX, fromY, toX, toY);
14767             }
14768         }
14769     }
14770     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14771         gameMode == Training || gameMode == PlayFromGameFile ||
14772         gameMode == AnalyzeFile) {
14773         while (currentMove < target) {
14774             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14775             SendMoveToProgram(currentMove++, &first);
14776         }
14777     } else {
14778         currentMove = target;
14779     }
14780
14781     if (gameMode == EditGame || gameMode == EndOfGame) {
14782         whiteTimeRemaining = timeRemaining[0][currentMove];
14783         blackTimeRemaining = timeRemaining[1][currentMove];
14784     }
14785     DisplayBothClocks();
14786     DisplayMove(currentMove - 1);
14787     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14788     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14789     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14790         DisplayComment(currentMove - 1, commentList[currentMove]);
14791     }
14792     ClearMap(); // [HGM] exclude: invalidate map
14793 }
14794
14795
14796 void
14797 ForwardEvent ()
14798 {
14799     if (gameMode == IcsExamining && !pausing) {
14800         SendToICS(ics_prefix);
14801         SendToICS("forward\n");
14802     } else {
14803         ForwardInner(currentMove + 1);
14804     }
14805 }
14806
14807 void
14808 ToEndEvent ()
14809 {
14810     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14811         /* to optimze, we temporarily turn off analysis mode while we feed
14812          * the remaining moves to the engine. Otherwise we get analysis output
14813          * after each move.
14814          */
14815         if (first.analysisSupport) {
14816           SendToProgram("exit\nforce\n", &first);
14817           first.analyzing = FALSE;
14818         }
14819     }
14820
14821     if (gameMode == IcsExamining && !pausing) {
14822         SendToICS(ics_prefix);
14823         SendToICS("forward 999999\n");
14824     } else {
14825         ForwardInner(forwardMostMove);
14826     }
14827
14828     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14829         /* we have fed all the moves, so reactivate analysis mode */
14830         SendToProgram("analyze\n", &first);
14831         first.analyzing = TRUE;
14832         /*first.maybeThinking = TRUE;*/
14833         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14834     }
14835 }
14836
14837 void
14838 BackwardInner (int target)
14839 {
14840     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14841
14842     if (appData.debugMode)
14843         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14844                 target, currentMove, forwardMostMove);
14845
14846     if (gameMode == EditPosition) return;
14847     seekGraphUp = FALSE;
14848     MarkTargetSquares(1);
14849     if (currentMove <= backwardMostMove) {
14850         ClearHighlights();
14851         DrawPosition(full_redraw, boards[currentMove]);
14852         return;
14853     }
14854     if (gameMode == PlayFromGameFile && !pausing)
14855       PauseEvent();
14856
14857     if (moveList[target][0]) {
14858         int fromX, fromY, toX, toY;
14859         toX = moveList[target][2] - AAA;
14860         toY = moveList[target][3] - ONE;
14861         if (moveList[target][1] == '@') {
14862             if (appData.highlightLastMove) {
14863                 SetHighlights(-1, -1, toX, toY);
14864             }
14865         } else {
14866             fromX = moveList[target][0] - AAA;
14867             fromY = moveList[target][1] - ONE;
14868             if (target == currentMove - 1) {
14869                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14870             }
14871             if (appData.highlightLastMove) {
14872                 SetHighlights(fromX, fromY, toX, toY);
14873             }
14874         }
14875     }
14876     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14877         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14878         while (currentMove > target) {
14879             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14880                 // null move cannot be undone. Reload program with move history before it.
14881                 int i;
14882                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14883                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14884                 }
14885                 SendBoard(&first, i); 
14886               if(second.analyzing) SendBoard(&second, i);
14887                 for(currentMove=i; currentMove<target; currentMove++) {
14888                     SendMoveToProgram(currentMove, &first);
14889                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
14890                 }
14891                 break;
14892             }
14893             SendToBoth("undo\n");
14894             currentMove--;
14895         }
14896     } else {
14897         currentMove = target;
14898     }
14899
14900     if (gameMode == EditGame || gameMode == EndOfGame) {
14901         whiteTimeRemaining = timeRemaining[0][currentMove];
14902         blackTimeRemaining = timeRemaining[1][currentMove];
14903     }
14904     DisplayBothClocks();
14905     DisplayMove(currentMove - 1);
14906     DrawPosition(full_redraw, boards[currentMove]);
14907     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14908     // [HGM] PV info: routine tests if comment empty
14909     DisplayComment(currentMove - 1, commentList[currentMove]);
14910     ClearMap(); // [HGM] exclude: invalidate map
14911 }
14912
14913 void
14914 BackwardEvent ()
14915 {
14916     if (gameMode == IcsExamining && !pausing) {
14917         SendToICS(ics_prefix);
14918         SendToICS("backward\n");
14919     } else {
14920         BackwardInner(currentMove - 1);
14921     }
14922 }
14923
14924 void
14925 ToStartEvent ()
14926 {
14927     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14928         /* to optimize, we temporarily turn off analysis mode while we undo
14929          * all the moves. Otherwise we get analysis output after each undo.
14930          */
14931         if (first.analysisSupport) {
14932           SendToProgram("exit\nforce\n", &first);
14933           first.analyzing = FALSE;
14934         }
14935     }
14936
14937     if (gameMode == IcsExamining && !pausing) {
14938         SendToICS(ics_prefix);
14939         SendToICS("backward 999999\n");
14940     } else {
14941         BackwardInner(backwardMostMove);
14942     }
14943
14944     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14945         /* we have fed all the moves, so reactivate analysis mode */
14946         SendToProgram("analyze\n", &first);
14947         first.analyzing = TRUE;
14948         /*first.maybeThinking = TRUE;*/
14949         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14950     }
14951 }
14952
14953 void
14954 ToNrEvent (int to)
14955 {
14956   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14957   if (to >= forwardMostMove) to = forwardMostMove;
14958   if (to <= backwardMostMove) to = backwardMostMove;
14959   if (to < currentMove) {
14960     BackwardInner(to);
14961   } else {
14962     ForwardInner(to);
14963   }
14964 }
14965
14966 void
14967 RevertEvent (Boolean annotate)
14968 {
14969     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14970         return;
14971     }
14972     if (gameMode != IcsExamining) {
14973         DisplayError(_("You are not examining a game"), 0);
14974         return;
14975     }
14976     if (pausing) {
14977         DisplayError(_("You can't revert while pausing"), 0);
14978         return;
14979     }
14980     SendToICS(ics_prefix);
14981     SendToICS("revert\n");
14982 }
14983
14984 void
14985 RetractMoveEvent ()
14986 {
14987     switch (gameMode) {
14988       case MachinePlaysWhite:
14989       case MachinePlaysBlack:
14990         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14991             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14992             return;
14993         }
14994         if (forwardMostMove < 2) return;
14995         currentMove = forwardMostMove = forwardMostMove - 2;
14996         whiteTimeRemaining = timeRemaining[0][currentMove];
14997         blackTimeRemaining = timeRemaining[1][currentMove];
14998         DisplayBothClocks();
14999         DisplayMove(currentMove - 1);
15000         ClearHighlights();/*!! could figure this out*/
15001         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15002         SendToProgram("remove\n", &first);
15003         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15004         break;
15005
15006       case BeginningOfGame:
15007       default:
15008         break;
15009
15010       case IcsPlayingWhite:
15011       case IcsPlayingBlack:
15012         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15013             SendToICS(ics_prefix);
15014             SendToICS("takeback 2\n");
15015         } else {
15016             SendToICS(ics_prefix);
15017             SendToICS("takeback 1\n");
15018         }
15019         break;
15020     }
15021 }
15022
15023 void
15024 MoveNowEvent ()
15025 {
15026     ChessProgramState *cps;
15027
15028     switch (gameMode) {
15029       case MachinePlaysWhite:
15030         if (!WhiteOnMove(forwardMostMove)) {
15031             DisplayError(_("It is your turn"), 0);
15032             return;
15033         }
15034         cps = &first;
15035         break;
15036       case MachinePlaysBlack:
15037         if (WhiteOnMove(forwardMostMove)) {
15038             DisplayError(_("It is your turn"), 0);
15039             return;
15040         }
15041         cps = &first;
15042         break;
15043       case TwoMachinesPlay:
15044         if (WhiteOnMove(forwardMostMove) ==
15045             (first.twoMachinesColor[0] == 'w')) {
15046             cps = &first;
15047         } else {
15048             cps = &second;
15049         }
15050         break;
15051       case BeginningOfGame:
15052       default:
15053         return;
15054     }
15055     SendToProgram("?\n", cps);
15056 }
15057
15058 void
15059 TruncateGameEvent ()
15060 {
15061     EditGameEvent();
15062     if (gameMode != EditGame) return;
15063     TruncateGame();
15064 }
15065
15066 void
15067 TruncateGame ()
15068 {
15069     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15070     if (forwardMostMove > currentMove) {
15071         if (gameInfo.resultDetails != NULL) {
15072             free(gameInfo.resultDetails);
15073             gameInfo.resultDetails = NULL;
15074             gameInfo.result = GameUnfinished;
15075         }
15076         forwardMostMove = currentMove;
15077         HistorySet(parseList, backwardMostMove, forwardMostMove,
15078                    currentMove-1);
15079     }
15080 }
15081
15082 void
15083 HintEvent ()
15084 {
15085     if (appData.noChessProgram) return;
15086     switch (gameMode) {
15087       case MachinePlaysWhite:
15088         if (WhiteOnMove(forwardMostMove)) {
15089             DisplayError(_("Wait until your turn"), 0);
15090             return;
15091         }
15092         break;
15093       case BeginningOfGame:
15094       case MachinePlaysBlack:
15095         if (!WhiteOnMove(forwardMostMove)) {
15096             DisplayError(_("Wait until your turn"), 0);
15097             return;
15098         }
15099         break;
15100       default:
15101         DisplayError(_("No hint available"), 0);
15102         return;
15103     }
15104     SendToProgram("hint\n", &first);
15105     hintRequested = TRUE;
15106 }
15107
15108 void
15109 CreateBookEvent ()
15110 {
15111     ListGame * lg = (ListGame *) gameList.head;
15112     FILE *f;
15113     int nItem;
15114     static int secondTime = FALSE;
15115
15116     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15117         DisplayError(_("Game list not loaded or empty"), 0);
15118         return;
15119     }
15120
15121     if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) {
15122         fclose(f);
15123         secondTime++;
15124         DisplayNote(_("Book file exists! Try again for overwrite."));
15125         return;
15126     }
15127
15128     creatingBook = TRUE;
15129     secondTime = FALSE;
15130
15131     /* Get list size */
15132     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15133         LoadGame(f, nItem, "", TRUE);
15134         AddGameToBook(TRUE);
15135         lg = (ListGame *) lg->node.succ;
15136     }
15137
15138     creatingBook = FALSE;
15139     FlushBook();
15140 }
15141
15142 void
15143 BookEvent ()
15144 {
15145     if (appData.noChessProgram) return;
15146     switch (gameMode) {
15147       case MachinePlaysWhite:
15148         if (WhiteOnMove(forwardMostMove)) {
15149             DisplayError(_("Wait until your turn"), 0);
15150             return;
15151         }
15152         break;
15153       case BeginningOfGame:
15154       case MachinePlaysBlack:
15155         if (!WhiteOnMove(forwardMostMove)) {
15156             DisplayError(_("Wait until your turn"), 0);
15157             return;
15158         }
15159         break;
15160       case EditPosition:
15161         EditPositionDone(TRUE);
15162         break;
15163       case TwoMachinesPlay:
15164         return;
15165       default:
15166         break;
15167     }
15168     SendToProgram("bk\n", &first);
15169     bookOutput[0] = NULLCHAR;
15170     bookRequested = TRUE;
15171 }
15172
15173 void
15174 AboutGameEvent ()
15175 {
15176     char *tags = PGNTags(&gameInfo);
15177     TagsPopUp(tags, CmailMsg());
15178     free(tags);
15179 }
15180
15181 /* end button procedures */
15182
15183 void
15184 PrintPosition (FILE *fp, int move)
15185 {
15186     int i, j;
15187
15188     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15189         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15190             char c = PieceToChar(boards[move][i][j]);
15191             fputc(c == 'x' ? '.' : c, fp);
15192             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15193         }
15194     }
15195     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15196       fprintf(fp, "white to play\n");
15197     else
15198       fprintf(fp, "black to play\n");
15199 }
15200
15201 void
15202 PrintOpponents (FILE *fp)
15203 {
15204     if (gameInfo.white != NULL) {
15205         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15206     } else {
15207         fprintf(fp, "\n");
15208     }
15209 }
15210
15211 /* Find last component of program's own name, using some heuristics */
15212 void
15213 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15214 {
15215     char *p, *q, c;
15216     int local = (strcmp(host, "localhost") == 0);
15217     while (!local && (p = strchr(prog, ';')) != NULL) {
15218         p++;
15219         while (*p == ' ') p++;
15220         prog = p;
15221     }
15222     if (*prog == '"' || *prog == '\'') {
15223         q = strchr(prog + 1, *prog);
15224     } else {
15225         q = strchr(prog, ' ');
15226     }
15227     if (q == NULL) q = prog + strlen(prog);
15228     p = q;
15229     while (p >= prog && *p != '/' && *p != '\\') p--;
15230     p++;
15231     if(p == prog && *p == '"') p++;
15232     c = *q; *q = 0;
15233     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15234     memcpy(buf, p, q - p);
15235     buf[q - p] = NULLCHAR;
15236     if (!local) {
15237         strcat(buf, "@");
15238         strcat(buf, host);
15239     }
15240 }
15241
15242 char *
15243 TimeControlTagValue ()
15244 {
15245     char buf[MSG_SIZ];
15246     if (!appData.clockMode) {
15247       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15248     } else if (movesPerSession > 0) {
15249       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15250     } else if (timeIncrement == 0) {
15251       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15252     } else {
15253       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15254     }
15255     return StrSave(buf);
15256 }
15257
15258 void
15259 SetGameInfo ()
15260 {
15261     /* This routine is used only for certain modes */
15262     VariantClass v = gameInfo.variant;
15263     ChessMove r = GameUnfinished;
15264     char *p = NULL;
15265
15266     if(keepInfo) return;
15267
15268     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15269         r = gameInfo.result;
15270         p = gameInfo.resultDetails;
15271         gameInfo.resultDetails = NULL;
15272     }
15273     ClearGameInfo(&gameInfo);
15274     gameInfo.variant = v;
15275
15276     switch (gameMode) {
15277       case MachinePlaysWhite:
15278         gameInfo.event = StrSave( appData.pgnEventHeader );
15279         gameInfo.site = StrSave(HostName());
15280         gameInfo.date = PGNDate();
15281         gameInfo.round = StrSave("-");
15282         gameInfo.white = StrSave(first.tidy);
15283         gameInfo.black = StrSave(UserName());
15284         gameInfo.timeControl = TimeControlTagValue();
15285         break;
15286
15287       case MachinePlaysBlack:
15288         gameInfo.event = StrSave( appData.pgnEventHeader );
15289         gameInfo.site = StrSave(HostName());
15290         gameInfo.date = PGNDate();
15291         gameInfo.round = StrSave("-");
15292         gameInfo.white = StrSave(UserName());
15293         gameInfo.black = StrSave(first.tidy);
15294         gameInfo.timeControl = TimeControlTagValue();
15295         break;
15296
15297       case TwoMachinesPlay:
15298         gameInfo.event = StrSave( appData.pgnEventHeader );
15299         gameInfo.site = StrSave(HostName());
15300         gameInfo.date = PGNDate();
15301         if (roundNr > 0) {
15302             char buf[MSG_SIZ];
15303             snprintf(buf, MSG_SIZ, "%d", roundNr);
15304             gameInfo.round = StrSave(buf);
15305         } else {
15306             gameInfo.round = StrSave("-");
15307         }
15308         if (first.twoMachinesColor[0] == 'w') {
15309             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15310             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15311         } else {
15312             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15313             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15314         }
15315         gameInfo.timeControl = TimeControlTagValue();
15316         break;
15317
15318       case EditGame:
15319         gameInfo.event = StrSave("Edited game");
15320         gameInfo.site = StrSave(HostName());
15321         gameInfo.date = PGNDate();
15322         gameInfo.round = StrSave("-");
15323         gameInfo.white = StrSave("-");
15324         gameInfo.black = StrSave("-");
15325         gameInfo.result = r;
15326         gameInfo.resultDetails = p;
15327         break;
15328
15329       case EditPosition:
15330         gameInfo.event = StrSave("Edited position");
15331         gameInfo.site = StrSave(HostName());
15332         gameInfo.date = PGNDate();
15333         gameInfo.round = StrSave("-");
15334         gameInfo.white = StrSave("-");
15335         gameInfo.black = StrSave("-");
15336         break;
15337
15338       case IcsPlayingWhite:
15339       case IcsPlayingBlack:
15340       case IcsObserving:
15341       case IcsExamining:
15342         break;
15343
15344       case PlayFromGameFile:
15345         gameInfo.event = StrSave("Game from non-PGN file");
15346         gameInfo.site = StrSave(HostName());
15347         gameInfo.date = PGNDate();
15348         gameInfo.round = StrSave("-");
15349         gameInfo.white = StrSave("?");
15350         gameInfo.black = StrSave("?");
15351         break;
15352
15353       default:
15354         break;
15355     }
15356 }
15357
15358 void
15359 ReplaceComment (int index, char *text)
15360 {
15361     int len;
15362     char *p;
15363     float score;
15364
15365     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15366        pvInfoList[index-1].depth == len &&
15367        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15368        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15369     while (*text == '\n') text++;
15370     len = strlen(text);
15371     while (len > 0 && text[len - 1] == '\n') len--;
15372
15373     if (commentList[index] != NULL)
15374       free(commentList[index]);
15375
15376     if (len == 0) {
15377         commentList[index] = NULL;
15378         return;
15379     }
15380   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15381       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15382       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15383     commentList[index] = (char *) malloc(len + 2);
15384     strncpy(commentList[index], text, len);
15385     commentList[index][len] = '\n';
15386     commentList[index][len + 1] = NULLCHAR;
15387   } else {
15388     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15389     char *p;
15390     commentList[index] = (char *) malloc(len + 7);
15391     safeStrCpy(commentList[index], "{\n", 3);
15392     safeStrCpy(commentList[index]+2, text, len+1);
15393     commentList[index][len+2] = NULLCHAR;
15394     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15395     strcat(commentList[index], "\n}\n");
15396   }
15397 }
15398
15399 void
15400 CrushCRs (char *text)
15401 {
15402   char *p = text;
15403   char *q = text;
15404   char ch;
15405
15406   do {
15407     ch = *p++;
15408     if (ch == '\r') continue;
15409     *q++ = ch;
15410   } while (ch != '\0');
15411 }
15412
15413 void
15414 AppendComment (int index, char *text, Boolean addBraces)
15415 /* addBraces  tells if we should add {} */
15416 {
15417     int oldlen, len;
15418     char *old;
15419
15420 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15421     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15422
15423     CrushCRs(text);
15424     while (*text == '\n') text++;
15425     len = strlen(text);
15426     while (len > 0 && text[len - 1] == '\n') len--;
15427     text[len] = NULLCHAR;
15428
15429     if (len == 0) return;
15430
15431     if (commentList[index] != NULL) {
15432       Boolean addClosingBrace = addBraces;
15433         old = commentList[index];
15434         oldlen = strlen(old);
15435         while(commentList[index][oldlen-1] ==  '\n')
15436           commentList[index][--oldlen] = NULLCHAR;
15437         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15438         safeStrCpy(commentList[index], old, oldlen + len + 6);
15439         free(old);
15440         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15441         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15442           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15443           while (*text == '\n') { text++; len--; }
15444           commentList[index][--oldlen] = NULLCHAR;
15445       }
15446         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15447         else          strcat(commentList[index], "\n");
15448         strcat(commentList[index], text);
15449         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15450         else          strcat(commentList[index], "\n");
15451     } else {
15452         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15453         if(addBraces)
15454           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15455         else commentList[index][0] = NULLCHAR;
15456         strcat(commentList[index], text);
15457         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15458         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15459     }
15460 }
15461
15462 static char *
15463 FindStr (char * text, char * sub_text)
15464 {
15465     char * result = strstr( text, sub_text );
15466
15467     if( result != NULL ) {
15468         result += strlen( sub_text );
15469     }
15470
15471     return result;
15472 }
15473
15474 /* [AS] Try to extract PV info from PGN comment */
15475 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15476 char *
15477 GetInfoFromComment (int index, char * text)
15478 {
15479     char * sep = text, *p;
15480
15481     if( text != NULL && index > 0 ) {
15482         int score = 0;
15483         int depth = 0;
15484         int time = -1, sec = 0, deci;
15485         char * s_eval = FindStr( text, "[%eval " );
15486         char * s_emt = FindStr( text, "[%emt " );
15487
15488         if( s_eval != NULL || s_emt != NULL ) {
15489             /* New style */
15490             char delim;
15491
15492             if( s_eval != NULL ) {
15493                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15494                     return text;
15495                 }
15496
15497                 if( delim != ']' ) {
15498                     return text;
15499                 }
15500             }
15501
15502             if( s_emt != NULL ) {
15503             }
15504                 return text;
15505         }
15506         else {
15507             /* We expect something like: [+|-]nnn.nn/dd */
15508             int score_lo = 0;
15509
15510             if(*text != '{') return text; // [HGM] braces: must be normal comment
15511
15512             sep = strchr( text, '/' );
15513             if( sep == NULL || sep < (text+4) ) {
15514                 return text;
15515             }
15516
15517             p = text;
15518             if(p[1] == '(') { // comment starts with PV
15519                p = strchr(p, ')'); // locate end of PV
15520                if(p == NULL || sep < p+5) return text;
15521                // at this point we have something like "{(.*) +0.23/6 ..."
15522                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15523                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15524                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15525             }
15526             time = -1; sec = -1; deci = -1;
15527             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15528                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15529                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15530                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15531                 return text;
15532             }
15533
15534             if( score_lo < 0 || score_lo >= 100 ) {
15535                 return text;
15536             }
15537
15538             if(sec >= 0) time = 600*time + 10*sec; else
15539             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15540
15541             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15542
15543             /* [HGM] PV time: now locate end of PV info */
15544             while( *++sep >= '0' && *sep <= '9'); // strip depth
15545             if(time >= 0)
15546             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15547             if(sec >= 0)
15548             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15549             if(deci >= 0)
15550             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15551             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15552         }
15553
15554         if( depth <= 0 ) {
15555             return text;
15556         }
15557
15558         if( time < 0 ) {
15559             time = -1;
15560         }
15561
15562         pvInfoList[index-1].depth = depth;
15563         pvInfoList[index-1].score = score;
15564         pvInfoList[index-1].time  = 10*time; // centi-sec
15565         if(*sep == '}') *sep = 0; else *--sep = '{';
15566         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15567     }
15568     return sep;
15569 }
15570
15571 void
15572 SendToProgram (char *message, ChessProgramState *cps)
15573 {
15574     int count, outCount, error;
15575     char buf[MSG_SIZ];
15576
15577     if (cps->pr == NoProc) return;
15578     Attention(cps);
15579
15580     if (appData.debugMode) {
15581         TimeMark now;
15582         GetTimeMark(&now);
15583         fprintf(debugFP, "%ld >%-6s: %s",
15584                 SubtractTimeMarks(&now, &programStartTime),
15585                 cps->which, message);
15586         if(serverFP)
15587             fprintf(serverFP, "%ld >%-6s: %s",
15588                 SubtractTimeMarks(&now, &programStartTime),
15589                 cps->which, message), fflush(serverFP);
15590     }
15591
15592     count = strlen(message);
15593     outCount = OutputToProcess(cps->pr, message, count, &error);
15594     if (outCount < count && !exiting
15595                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15596       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15597       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15598         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15599             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15600                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15601                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15602                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15603             } else {
15604                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15605                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15606                 gameInfo.result = res;
15607             }
15608             gameInfo.resultDetails = StrSave(buf);
15609         }
15610         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15611         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15612     }
15613 }
15614
15615 void
15616 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15617 {
15618     char *end_str;
15619     char buf[MSG_SIZ];
15620     ChessProgramState *cps = (ChessProgramState *)closure;
15621
15622     if (isr != cps->isr) return; /* Killed intentionally */
15623     if (count <= 0) {
15624         if (count == 0) {
15625             RemoveInputSource(cps->isr);
15626             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15627                     _(cps->which), cps->program);
15628             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15629             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15630                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15631                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15632                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15633                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15634                 } else {
15635                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15636                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15637                     gameInfo.result = res;
15638                 }
15639                 gameInfo.resultDetails = StrSave(buf);
15640             }
15641             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15642             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15643         } else {
15644             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15645                     _(cps->which), cps->program);
15646             RemoveInputSource(cps->isr);
15647
15648             /* [AS] Program is misbehaving badly... kill it */
15649             if( count == -2 ) {
15650                 DestroyChildProcess( cps->pr, 9 );
15651                 cps->pr = NoProc;
15652             }
15653
15654             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15655         }
15656         return;
15657     }
15658
15659     if ((end_str = strchr(message, '\r')) != NULL)
15660       *end_str = NULLCHAR;
15661     if ((end_str = strchr(message, '\n')) != NULL)
15662       *end_str = NULLCHAR;
15663
15664     if (appData.debugMode) {
15665         TimeMark now; int print = 1;
15666         char *quote = ""; char c; int i;
15667
15668         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15669                 char start = message[0];
15670                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15671                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15672                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15673                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15674                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15675                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15676                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15677                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15678                    sscanf(message, "hint: %c", &c)!=1 && 
15679                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15680                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15681                     print = (appData.engineComments >= 2);
15682                 }
15683                 message[0] = start; // restore original message
15684         }
15685         if(print) {
15686                 GetTimeMark(&now);
15687                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15688                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15689                         quote,
15690                         message);
15691                 if(serverFP)
15692                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15693                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15694                         quote,
15695                         message), fflush(serverFP);
15696         }
15697     }
15698
15699     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15700     if (appData.icsEngineAnalyze) {
15701         if (strstr(message, "whisper") != NULL ||
15702              strstr(message, "kibitz") != NULL ||
15703             strstr(message, "tellics") != NULL) return;
15704     }
15705
15706     HandleMachineMove(message, cps);
15707 }
15708
15709
15710 void
15711 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15712 {
15713     char buf[MSG_SIZ];
15714     int seconds;
15715
15716     if( timeControl_2 > 0 ) {
15717         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15718             tc = timeControl_2;
15719         }
15720     }
15721     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15722     inc /= cps->timeOdds;
15723     st  /= cps->timeOdds;
15724
15725     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15726
15727     if (st > 0) {
15728       /* Set exact time per move, normally using st command */
15729       if (cps->stKludge) {
15730         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15731         seconds = st % 60;
15732         if (seconds == 0) {
15733           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15734         } else {
15735           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15736         }
15737       } else {
15738         snprintf(buf, MSG_SIZ, "st %d\n", st);
15739       }
15740     } else {
15741       /* Set conventional or incremental time control, using level command */
15742       if (seconds == 0) {
15743         /* Note old gnuchess bug -- minutes:seconds used to not work.
15744            Fixed in later versions, but still avoid :seconds
15745            when seconds is 0. */
15746         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15747       } else {
15748         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15749                  seconds, inc/1000.);
15750       }
15751     }
15752     SendToProgram(buf, cps);
15753
15754     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15755     /* Orthogonally, limit search to given depth */
15756     if (sd > 0) {
15757       if (cps->sdKludge) {
15758         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15759       } else {
15760         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15761       }
15762       SendToProgram(buf, cps);
15763     }
15764
15765     if(cps->nps >= 0) { /* [HGM] nps */
15766         if(cps->supportsNPS == FALSE)
15767           cps->nps = -1; // don't use if engine explicitly says not supported!
15768         else {
15769           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15770           SendToProgram(buf, cps);
15771         }
15772     }
15773 }
15774
15775 ChessProgramState *
15776 WhitePlayer ()
15777 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15778 {
15779     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15780        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15781         return &second;
15782     return &first;
15783 }
15784
15785 void
15786 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15787 {
15788     char message[MSG_SIZ];
15789     long time, otime;
15790
15791     /* Note: this routine must be called when the clocks are stopped
15792        or when they have *just* been set or switched; otherwise
15793        it will be off by the time since the current tick started.
15794     */
15795     if (machineWhite) {
15796         time = whiteTimeRemaining / 10;
15797         otime = blackTimeRemaining / 10;
15798     } else {
15799         time = blackTimeRemaining / 10;
15800         otime = whiteTimeRemaining / 10;
15801     }
15802     /* [HGM] translate opponent's time by time-odds factor */
15803     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15804
15805     if (time <= 0) time = 1;
15806     if (otime <= 0) otime = 1;
15807
15808     snprintf(message, MSG_SIZ, "time %ld\n", time);
15809     SendToProgram(message, cps);
15810
15811     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15812     SendToProgram(message, cps);
15813 }
15814
15815 int
15816 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15817 {
15818   char buf[MSG_SIZ];
15819   int len = strlen(name);
15820   int val;
15821
15822   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15823     (*p) += len + 1;
15824     sscanf(*p, "%d", &val);
15825     *loc = (val != 0);
15826     while (**p && **p != ' ')
15827       (*p)++;
15828     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15829     SendToProgram(buf, cps);
15830     return TRUE;
15831   }
15832   return FALSE;
15833 }
15834
15835 int
15836 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15837 {
15838   char buf[MSG_SIZ];
15839   int len = strlen(name);
15840   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15841     (*p) += len + 1;
15842     sscanf(*p, "%d", loc);
15843     while (**p && **p != ' ') (*p)++;
15844     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15845     SendToProgram(buf, cps);
15846     return TRUE;
15847   }
15848   return FALSE;
15849 }
15850
15851 int
15852 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15853 {
15854   char buf[MSG_SIZ];
15855   int len = strlen(name);
15856   if (strncmp((*p), name, len) == 0
15857       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15858     (*p) += len + 2;
15859     sscanf(*p, "%[^\"]", loc);
15860     while (**p && **p != '\"') (*p)++;
15861     if (**p == '\"') (*p)++;
15862     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15863     SendToProgram(buf, cps);
15864     return TRUE;
15865   }
15866   return FALSE;
15867 }
15868
15869 int
15870 ParseOption (Option *opt, ChessProgramState *cps)
15871 // [HGM] options: process the string that defines an engine option, and determine
15872 // name, type, default value, and allowed value range
15873 {
15874         char *p, *q, buf[MSG_SIZ];
15875         int n, min = (-1)<<31, max = 1<<31, def;
15876
15877         if(p = strstr(opt->name, " -spin ")) {
15878             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15879             if(max < min) max = min; // enforce consistency
15880             if(def < min) def = min;
15881             if(def > max) def = max;
15882             opt->value = def;
15883             opt->min = min;
15884             opt->max = max;
15885             opt->type = Spin;
15886         } else if((p = strstr(opt->name, " -slider "))) {
15887             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15888             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15889             if(max < min) max = min; // enforce consistency
15890             if(def < min) def = min;
15891             if(def > max) def = max;
15892             opt->value = def;
15893             opt->min = min;
15894             opt->max = max;
15895             opt->type = Spin; // Slider;
15896         } else if((p = strstr(opt->name, " -string "))) {
15897             opt->textValue = p+9;
15898             opt->type = TextBox;
15899         } else if((p = strstr(opt->name, " -file "))) {
15900             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15901             opt->textValue = p+7;
15902             opt->type = FileName; // FileName;
15903         } else if((p = strstr(opt->name, " -path "))) {
15904             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15905             opt->textValue = p+7;
15906             opt->type = PathName; // PathName;
15907         } else if(p = strstr(opt->name, " -check ")) {
15908             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15909             opt->value = (def != 0);
15910             opt->type = CheckBox;
15911         } else if(p = strstr(opt->name, " -combo ")) {
15912             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15913             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15914             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15915             opt->value = n = 0;
15916             while(q = StrStr(q, " /// ")) {
15917                 n++; *q = 0;    // count choices, and null-terminate each of them
15918                 q += 5;
15919                 if(*q == '*') { // remember default, which is marked with * prefix
15920                     q++;
15921                     opt->value = n;
15922                 }
15923                 cps->comboList[cps->comboCnt++] = q;
15924             }
15925             cps->comboList[cps->comboCnt++] = NULL;
15926             opt->max = n + 1;
15927             opt->type = ComboBox;
15928         } else if(p = strstr(opt->name, " -button")) {
15929             opt->type = Button;
15930         } else if(p = strstr(opt->name, " -save")) {
15931             opt->type = SaveButton;
15932         } else return FALSE;
15933         *p = 0; // terminate option name
15934         // now look if the command-line options define a setting for this engine option.
15935         if(cps->optionSettings && cps->optionSettings[0])
15936             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15937         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15938           snprintf(buf, MSG_SIZ, "option %s", p);
15939                 if(p = strstr(buf, ",")) *p = 0;
15940                 if(q = strchr(buf, '=')) switch(opt->type) {
15941                     case ComboBox:
15942                         for(n=0; n<opt->max; n++)
15943                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15944                         break;
15945                     case TextBox:
15946                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15947                         break;
15948                     case Spin:
15949                     case CheckBox:
15950                         opt->value = atoi(q+1);
15951                     default:
15952                         break;
15953                 }
15954                 strcat(buf, "\n");
15955                 SendToProgram(buf, cps);
15956         }
15957         return TRUE;
15958 }
15959
15960 void
15961 FeatureDone (ChessProgramState *cps, int val)
15962 {
15963   DelayedEventCallback cb = GetDelayedEvent();
15964   if ((cb == InitBackEnd3 && cps == &first) ||
15965       (cb == SettingsMenuIfReady && cps == &second) ||
15966       (cb == LoadEngine) ||
15967       (cb == TwoMachinesEventIfReady)) {
15968     CancelDelayedEvent();
15969     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15970   }
15971   cps->initDone = val;
15972 }
15973
15974 /* Parse feature command from engine */
15975 void
15976 ParseFeatures (char *args, ChessProgramState *cps)
15977 {
15978   char *p = args;
15979   char *q;
15980   int val;
15981   char buf[MSG_SIZ];
15982
15983   for (;;) {
15984     while (*p == ' ') p++;
15985     if (*p == NULLCHAR) return;
15986
15987     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15988     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15989     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15990     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15991     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15992     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15993     if (BoolFeature(&p, "reuse", &val, cps)) {
15994       /* Engine can disable reuse, but can't enable it if user said no */
15995       if (!val) cps->reuse = FALSE;
15996       continue;
15997     }
15998     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15999     if (StringFeature(&p, "myname", cps->tidy, cps)) {
16000       if (gameMode == TwoMachinesPlay) {
16001         DisplayTwoMachinesTitle();
16002       } else {
16003         DisplayTitle("");
16004       }
16005       continue;
16006     }
16007     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
16008     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16009     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16010     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16011     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16012     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16013     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16014     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16015     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16016     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16017     if (IntFeature(&p, "done", &val, cps)) {
16018       FeatureDone(cps, val);
16019       continue;
16020     }
16021     /* Added by Tord: */
16022     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16023     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16024     /* End of additions by Tord */
16025
16026     /* [HGM] added features: */
16027     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16028     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16029     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16030     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16031     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16032     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
16033     if (StringFeature(&p, "option", buf, cps)) {
16034         FREE(cps->option[cps->nrOptions].name);
16035         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
16036         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
16037         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16038           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16039             SendToProgram(buf, cps);
16040             continue;
16041         }
16042         if(cps->nrOptions >= MAX_OPTIONS) {
16043             cps->nrOptions--;
16044             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16045             DisplayError(buf, 0);
16046         }
16047         continue;
16048     }
16049     /* End of additions by HGM */
16050
16051     /* unknown feature: complain and skip */
16052     q = p;
16053     while (*q && *q != '=') q++;
16054     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16055     SendToProgram(buf, cps);
16056     p = q;
16057     if (*p == '=') {
16058       p++;
16059       if (*p == '\"') {
16060         p++;
16061         while (*p && *p != '\"') p++;
16062         if (*p == '\"') p++;
16063       } else {
16064         while (*p && *p != ' ') p++;
16065       }
16066     }
16067   }
16068
16069 }
16070
16071 void
16072 PeriodicUpdatesEvent (int newState)
16073 {
16074     if (newState == appData.periodicUpdates)
16075       return;
16076
16077     appData.periodicUpdates=newState;
16078
16079     /* Display type changes, so update it now */
16080 //    DisplayAnalysis();
16081
16082     /* Get the ball rolling again... */
16083     if (newState) {
16084         AnalysisPeriodicEvent(1);
16085         StartAnalysisClock();
16086     }
16087 }
16088
16089 void
16090 PonderNextMoveEvent (int newState)
16091 {
16092     if (newState == appData.ponderNextMove) return;
16093     if (gameMode == EditPosition) EditPositionDone(TRUE);
16094     if (newState) {
16095         SendToProgram("hard\n", &first);
16096         if (gameMode == TwoMachinesPlay) {
16097             SendToProgram("hard\n", &second);
16098         }
16099     } else {
16100         SendToProgram("easy\n", &first);
16101         thinkOutput[0] = NULLCHAR;
16102         if (gameMode == TwoMachinesPlay) {
16103             SendToProgram("easy\n", &second);
16104         }
16105     }
16106     appData.ponderNextMove = newState;
16107 }
16108
16109 void
16110 NewSettingEvent (int option, int *feature, char *command, int value)
16111 {
16112     char buf[MSG_SIZ];
16113
16114     if (gameMode == EditPosition) EditPositionDone(TRUE);
16115     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16116     if(feature == NULL || *feature) SendToProgram(buf, &first);
16117     if (gameMode == TwoMachinesPlay) {
16118         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16119     }
16120 }
16121
16122 void
16123 ShowThinkingEvent ()
16124 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16125 {
16126     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16127     int newState = appData.showThinking
16128         // [HGM] thinking: other features now need thinking output as well
16129         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16130
16131     if (oldState == newState) return;
16132     oldState = newState;
16133     if (gameMode == EditPosition) EditPositionDone(TRUE);
16134     if (oldState) {
16135         SendToProgram("post\n", &first);
16136         if (gameMode == TwoMachinesPlay) {
16137             SendToProgram("post\n", &second);
16138         }
16139     } else {
16140         SendToProgram("nopost\n", &first);
16141         thinkOutput[0] = NULLCHAR;
16142         if (gameMode == TwoMachinesPlay) {
16143             SendToProgram("nopost\n", &second);
16144         }
16145     }
16146 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16147 }
16148
16149 void
16150 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16151 {
16152   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16153   if (pr == NoProc) return;
16154   AskQuestion(title, question, replyPrefix, pr);
16155 }
16156
16157 void
16158 TypeInEvent (char firstChar)
16159 {
16160     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
16161         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16162         gameMode == AnalyzeMode || gameMode == EditGame || 
16163         gameMode == EditPosition || gameMode == IcsExamining ||
16164         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16165         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16166                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16167                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16168         gameMode == Training) PopUpMoveDialog(firstChar);
16169 }
16170
16171 void
16172 TypeInDoneEvent (char *move)
16173 {
16174         Board board;
16175         int n, fromX, fromY, toX, toY;
16176         char promoChar;
16177         ChessMove moveType;
16178
16179         // [HGM] FENedit
16180         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16181                 EditPositionPasteFEN(move);
16182                 return;
16183         }
16184         // [HGM] movenum: allow move number to be typed in any mode
16185         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16186           ToNrEvent(2*n-1);
16187           return;
16188         }
16189         // undocumented kludge: allow command-line option to be typed in!
16190         // (potentially fatal, and does not implement the effect of the option.)
16191         // should only be used for options that are values on which future decisions will be made,
16192         // and definitely not on options that would be used during initialization.
16193         if(strstr(move, "!!! -") == move) {
16194             ParseArgsFromString(move+4);
16195             return;
16196         }
16197
16198       if (gameMode != EditGame && currentMove != forwardMostMove && 
16199         gameMode != Training) {
16200         DisplayMoveError(_("Displayed move is not current"));
16201       } else {
16202         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
16203           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16204         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16205         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
16206           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16207           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
16208         } else {
16209           DisplayMoveError(_("Could not parse move"));
16210         }
16211       }
16212 }
16213
16214 void
16215 DisplayMove (int moveNumber)
16216 {
16217     char message[MSG_SIZ];
16218     char res[MSG_SIZ];
16219     char cpThinkOutput[MSG_SIZ];
16220
16221     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16222
16223     if (moveNumber == forwardMostMove - 1 ||
16224         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16225
16226         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16227
16228         if (strchr(cpThinkOutput, '\n')) {
16229             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16230         }
16231     } else {
16232         *cpThinkOutput = NULLCHAR;
16233     }
16234
16235     /* [AS] Hide thinking from human user */
16236     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16237         *cpThinkOutput = NULLCHAR;
16238         if( thinkOutput[0] != NULLCHAR ) {
16239             int i;
16240
16241             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16242                 cpThinkOutput[i] = '.';
16243             }
16244             cpThinkOutput[i] = NULLCHAR;
16245             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16246         }
16247     }
16248
16249     if (moveNumber == forwardMostMove - 1 &&
16250         gameInfo.resultDetails != NULL) {
16251         if (gameInfo.resultDetails[0] == NULLCHAR) {
16252           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16253         } else {
16254           snprintf(res, MSG_SIZ, " {%s} %s",
16255                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16256         }
16257     } else {
16258         res[0] = NULLCHAR;
16259     }
16260
16261     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16262         DisplayMessage(res, cpThinkOutput);
16263     } else {
16264       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16265                 WhiteOnMove(moveNumber) ? " " : ".. ",
16266                 parseList[moveNumber], res);
16267         DisplayMessage(message, cpThinkOutput);
16268     }
16269 }
16270
16271 void
16272 DisplayComment (int moveNumber, char *text)
16273 {
16274     char title[MSG_SIZ];
16275
16276     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16277       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16278     } else {
16279       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16280               WhiteOnMove(moveNumber) ? " " : ".. ",
16281               parseList[moveNumber]);
16282     }
16283     if (text != NULL && (appData.autoDisplayComment || commentUp))
16284         CommentPopUp(title, text);
16285 }
16286
16287 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16288  * might be busy thinking or pondering.  It can be omitted if your
16289  * gnuchess is configured to stop thinking immediately on any user
16290  * input.  However, that gnuchess feature depends on the FIONREAD
16291  * ioctl, which does not work properly on some flavors of Unix.
16292  */
16293 void
16294 Attention (ChessProgramState *cps)
16295 {
16296 #if ATTENTION
16297     if (!cps->useSigint) return;
16298     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16299     switch (gameMode) {
16300       case MachinePlaysWhite:
16301       case MachinePlaysBlack:
16302       case TwoMachinesPlay:
16303       case IcsPlayingWhite:
16304       case IcsPlayingBlack:
16305       case AnalyzeMode:
16306       case AnalyzeFile:
16307         /* Skip if we know it isn't thinking */
16308         if (!cps->maybeThinking) return;
16309         if (appData.debugMode)
16310           fprintf(debugFP, "Interrupting %s\n", cps->which);
16311         InterruptChildProcess(cps->pr);
16312         cps->maybeThinking = FALSE;
16313         break;
16314       default:
16315         break;
16316     }
16317 #endif /*ATTENTION*/
16318 }
16319
16320 int
16321 CheckFlags ()
16322 {
16323     if (whiteTimeRemaining <= 0) {
16324         if (!whiteFlag) {
16325             whiteFlag = TRUE;
16326             if (appData.icsActive) {
16327                 if (appData.autoCallFlag &&
16328                     gameMode == IcsPlayingBlack && !blackFlag) {
16329                   SendToICS(ics_prefix);
16330                   SendToICS("flag\n");
16331                 }
16332             } else {
16333                 if (blackFlag) {
16334                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16335                 } else {
16336                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16337                     if (appData.autoCallFlag) {
16338                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16339                         return TRUE;
16340                     }
16341                 }
16342             }
16343         }
16344     }
16345     if (blackTimeRemaining <= 0) {
16346         if (!blackFlag) {
16347             blackFlag = TRUE;
16348             if (appData.icsActive) {
16349                 if (appData.autoCallFlag &&
16350                     gameMode == IcsPlayingWhite && !whiteFlag) {
16351                   SendToICS(ics_prefix);
16352                   SendToICS("flag\n");
16353                 }
16354             } else {
16355                 if (whiteFlag) {
16356                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16357                 } else {
16358                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16359                     if (appData.autoCallFlag) {
16360                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16361                         return TRUE;
16362                     }
16363                 }
16364             }
16365         }
16366     }
16367     return FALSE;
16368 }
16369
16370 void
16371 CheckTimeControl ()
16372 {
16373     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16374         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16375
16376     /*
16377      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16378      */
16379     if ( !WhiteOnMove(forwardMostMove) ) {
16380         /* White made time control */
16381         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16382         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16383         /* [HGM] time odds: correct new time quota for time odds! */
16384                                             / WhitePlayer()->timeOdds;
16385         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16386     } else {
16387         lastBlack -= blackTimeRemaining;
16388         /* Black made time control */
16389         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16390                                             / WhitePlayer()->other->timeOdds;
16391         lastWhite = whiteTimeRemaining;
16392     }
16393 }
16394
16395 void
16396 DisplayBothClocks ()
16397 {
16398     int wom = gameMode == EditPosition ?
16399       !blackPlaysFirst : WhiteOnMove(currentMove);
16400     DisplayWhiteClock(whiteTimeRemaining, wom);
16401     DisplayBlackClock(blackTimeRemaining, !wom);
16402 }
16403
16404
16405 /* Timekeeping seems to be a portability nightmare.  I think everyone
16406    has ftime(), but I'm really not sure, so I'm including some ifdefs
16407    to use other calls if you don't.  Clocks will be less accurate if
16408    you have neither ftime nor gettimeofday.
16409 */
16410
16411 /* VS 2008 requires the #include outside of the function */
16412 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16413 #include <sys/timeb.h>
16414 #endif
16415
16416 /* Get the current time as a TimeMark */
16417 void
16418 GetTimeMark (TimeMark *tm)
16419 {
16420 #if HAVE_GETTIMEOFDAY
16421
16422     struct timeval timeVal;
16423     struct timezone timeZone;
16424
16425     gettimeofday(&timeVal, &timeZone);
16426     tm->sec = (long) timeVal.tv_sec;
16427     tm->ms = (int) (timeVal.tv_usec / 1000L);
16428
16429 #else /*!HAVE_GETTIMEOFDAY*/
16430 #if HAVE_FTIME
16431
16432 // include <sys/timeb.h> / moved to just above start of function
16433     struct timeb timeB;
16434
16435     ftime(&timeB);
16436     tm->sec = (long) timeB.time;
16437     tm->ms = (int) timeB.millitm;
16438
16439 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16440     tm->sec = (long) time(NULL);
16441     tm->ms = 0;
16442 #endif
16443 #endif
16444 }
16445
16446 /* Return the difference in milliseconds between two
16447    time marks.  We assume the difference will fit in a long!
16448 */
16449 long
16450 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16451 {
16452     return 1000L*(tm2->sec - tm1->sec) +
16453            (long) (tm2->ms - tm1->ms);
16454 }
16455
16456
16457 /*
16458  * Code to manage the game clocks.
16459  *
16460  * In tournament play, black starts the clock and then white makes a move.
16461  * We give the human user a slight advantage if he is playing white---the
16462  * clocks don't run until he makes his first move, so it takes zero time.
16463  * Also, we don't account for network lag, so we could get out of sync
16464  * with GNU Chess's clock -- but then, referees are always right.
16465  */
16466
16467 static TimeMark tickStartTM;
16468 static long intendedTickLength;
16469
16470 long
16471 NextTickLength (long timeRemaining)
16472 {
16473     long nominalTickLength, nextTickLength;
16474
16475     if (timeRemaining > 0L && timeRemaining <= 10000L)
16476       nominalTickLength = 100L;
16477     else
16478       nominalTickLength = 1000L;
16479     nextTickLength = timeRemaining % nominalTickLength;
16480     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16481
16482     return nextTickLength;
16483 }
16484
16485 /* Adjust clock one minute up or down */
16486 void
16487 AdjustClock (Boolean which, int dir)
16488 {
16489     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16490     if(which) blackTimeRemaining += 60000*dir;
16491     else      whiteTimeRemaining += 60000*dir;
16492     DisplayBothClocks();
16493     adjustedClock = TRUE;
16494 }
16495
16496 /* Stop clocks and reset to a fresh time control */
16497 void
16498 ResetClocks ()
16499 {
16500     (void) StopClockTimer();
16501     if (appData.icsActive) {
16502         whiteTimeRemaining = blackTimeRemaining = 0;
16503     } else if (searchTime) {
16504         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16505         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16506     } else { /* [HGM] correct new time quote for time odds */
16507         whiteTC = blackTC = fullTimeControlString;
16508         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16509         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16510     }
16511     if (whiteFlag || blackFlag) {
16512         DisplayTitle("");
16513         whiteFlag = blackFlag = FALSE;
16514     }
16515     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16516     DisplayBothClocks();
16517     adjustedClock = FALSE;
16518 }
16519
16520 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16521
16522 /* Decrement running clock by amount of time that has passed */
16523 void
16524 DecrementClocks ()
16525 {
16526     long timeRemaining;
16527     long lastTickLength, fudge;
16528     TimeMark now;
16529
16530     if (!appData.clockMode) return;
16531     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16532
16533     GetTimeMark(&now);
16534
16535     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16536
16537     /* Fudge if we woke up a little too soon */
16538     fudge = intendedTickLength - lastTickLength;
16539     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16540
16541     if (WhiteOnMove(forwardMostMove)) {
16542         if(whiteNPS >= 0) lastTickLength = 0;
16543         timeRemaining = whiteTimeRemaining -= lastTickLength;
16544         if(timeRemaining < 0 && !appData.icsActive) {
16545             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16546             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16547                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16548                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16549             }
16550         }
16551         DisplayWhiteClock(whiteTimeRemaining - fudge,
16552                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16553     } else {
16554         if(blackNPS >= 0) lastTickLength = 0;
16555         timeRemaining = blackTimeRemaining -= lastTickLength;
16556         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16557             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16558             if(suddenDeath) {
16559                 blackStartMove = forwardMostMove;
16560                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16561             }
16562         }
16563         DisplayBlackClock(blackTimeRemaining - fudge,
16564                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16565     }
16566     if (CheckFlags()) return;
16567
16568     if(twoBoards) { // count down secondary board's clocks as well
16569         activePartnerTime -= lastTickLength;
16570         partnerUp = 1;
16571         if(activePartner == 'W')
16572             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16573         else
16574             DisplayBlackClock(activePartnerTime, TRUE);
16575         partnerUp = 0;
16576     }
16577
16578     tickStartTM = now;
16579     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16580     StartClockTimer(intendedTickLength);
16581
16582     /* if the time remaining has fallen below the alarm threshold, sound the
16583      * alarm. if the alarm has sounded and (due to a takeback or time control
16584      * with increment) the time remaining has increased to a level above the
16585      * threshold, reset the alarm so it can sound again.
16586      */
16587
16588     if (appData.icsActive && appData.icsAlarm) {
16589
16590         /* make sure we are dealing with the user's clock */
16591         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16592                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16593            )) return;
16594
16595         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16596             alarmSounded = FALSE;
16597         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16598             PlayAlarmSound();
16599             alarmSounded = TRUE;
16600         }
16601     }
16602 }
16603
16604
16605 /* A player has just moved, so stop the previously running
16606    clock and (if in clock mode) start the other one.
16607    We redisplay both clocks in case we're in ICS mode, because
16608    ICS gives us an update to both clocks after every move.
16609    Note that this routine is called *after* forwardMostMove
16610    is updated, so the last fractional tick must be subtracted
16611    from the color that is *not* on move now.
16612 */
16613 void
16614 SwitchClocks (int newMoveNr)
16615 {
16616     long lastTickLength;
16617     TimeMark now;
16618     int flagged = FALSE;
16619
16620     GetTimeMark(&now);
16621
16622     if (StopClockTimer() && appData.clockMode) {
16623         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16624         if (!WhiteOnMove(forwardMostMove)) {
16625             if(blackNPS >= 0) lastTickLength = 0;
16626             blackTimeRemaining -= lastTickLength;
16627            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16628 //         if(pvInfoList[forwardMostMove].time == -1)
16629                  pvInfoList[forwardMostMove].time =               // use GUI time
16630                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16631         } else {
16632            if(whiteNPS >= 0) lastTickLength = 0;
16633            whiteTimeRemaining -= lastTickLength;
16634            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16635 //         if(pvInfoList[forwardMostMove].time == -1)
16636                  pvInfoList[forwardMostMove].time =
16637                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16638         }
16639         flagged = CheckFlags();
16640     }
16641     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16642     CheckTimeControl();
16643
16644     if (flagged || !appData.clockMode) return;
16645
16646     switch (gameMode) {
16647       case MachinePlaysBlack:
16648       case MachinePlaysWhite:
16649       case BeginningOfGame:
16650         if (pausing) return;
16651         break;
16652
16653       case EditGame:
16654       case PlayFromGameFile:
16655       case IcsExamining:
16656         return;
16657
16658       default:
16659         break;
16660     }
16661
16662     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16663         if(WhiteOnMove(forwardMostMove))
16664              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16665         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16666     }
16667
16668     tickStartTM = now;
16669     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16670       whiteTimeRemaining : blackTimeRemaining);
16671     StartClockTimer(intendedTickLength);
16672 }
16673
16674
16675 /* Stop both clocks */
16676 void
16677 StopClocks ()
16678 {
16679     long lastTickLength;
16680     TimeMark now;
16681
16682     if (!StopClockTimer()) return;
16683     if (!appData.clockMode) return;
16684
16685     GetTimeMark(&now);
16686
16687     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16688     if (WhiteOnMove(forwardMostMove)) {
16689         if(whiteNPS >= 0) lastTickLength = 0;
16690         whiteTimeRemaining -= lastTickLength;
16691         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16692     } else {
16693         if(blackNPS >= 0) lastTickLength = 0;
16694         blackTimeRemaining -= lastTickLength;
16695         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16696     }
16697     CheckFlags();
16698 }
16699
16700 /* Start clock of player on move.  Time may have been reset, so
16701    if clock is already running, stop and restart it. */
16702 void
16703 StartClocks ()
16704 {
16705     (void) StopClockTimer(); /* in case it was running already */
16706     DisplayBothClocks();
16707     if (CheckFlags()) return;
16708
16709     if (!appData.clockMode) return;
16710     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16711
16712     GetTimeMark(&tickStartTM);
16713     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16714       whiteTimeRemaining : blackTimeRemaining);
16715
16716    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16717     whiteNPS = blackNPS = -1;
16718     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16719        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16720         whiteNPS = first.nps;
16721     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16722        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16723         blackNPS = first.nps;
16724     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16725         whiteNPS = second.nps;
16726     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16727         blackNPS = second.nps;
16728     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16729
16730     StartClockTimer(intendedTickLength);
16731 }
16732
16733 char *
16734 TimeString (long ms)
16735 {
16736     long second, minute, hour, day;
16737     char *sign = "";
16738     static char buf[32];
16739
16740     if (ms > 0 && ms <= 9900) {
16741       /* convert milliseconds to tenths, rounding up */
16742       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16743
16744       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16745       return buf;
16746     }
16747
16748     /* convert milliseconds to seconds, rounding up */
16749     /* use floating point to avoid strangeness of integer division
16750        with negative dividends on many machines */
16751     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16752
16753     if (second < 0) {
16754         sign = "-";
16755         second = -second;
16756     }
16757
16758     day = second / (60 * 60 * 24);
16759     second = second % (60 * 60 * 24);
16760     hour = second / (60 * 60);
16761     second = second % (60 * 60);
16762     minute = second / 60;
16763     second = second % 60;
16764
16765     if (day > 0)
16766       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16767               sign, day, hour, minute, second);
16768     else if (hour > 0)
16769       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16770     else
16771       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16772
16773     return buf;
16774 }
16775
16776
16777 /*
16778  * This is necessary because some C libraries aren't ANSI C compliant yet.
16779  */
16780 char *
16781 StrStr (char *string, char *match)
16782 {
16783     int i, length;
16784
16785     length = strlen(match);
16786
16787     for (i = strlen(string) - length; i >= 0; i--, string++)
16788       if (!strncmp(match, string, length))
16789         return string;
16790
16791     return NULL;
16792 }
16793
16794 char *
16795 StrCaseStr (char *string, char *match)
16796 {
16797     int i, j, length;
16798
16799     length = strlen(match);
16800
16801     for (i = strlen(string) - length; i >= 0; i--, string++) {
16802         for (j = 0; j < length; j++) {
16803             if (ToLower(match[j]) != ToLower(string[j]))
16804               break;
16805         }
16806         if (j == length) return string;
16807     }
16808
16809     return NULL;
16810 }
16811
16812 #ifndef _amigados
16813 int
16814 StrCaseCmp (char *s1, char *s2)
16815 {
16816     char c1, c2;
16817
16818     for (;;) {
16819         c1 = ToLower(*s1++);
16820         c2 = ToLower(*s2++);
16821         if (c1 > c2) return 1;
16822         if (c1 < c2) return -1;
16823         if (c1 == NULLCHAR) return 0;
16824     }
16825 }
16826
16827
16828 int
16829 ToLower (int c)
16830 {
16831     return isupper(c) ? tolower(c) : c;
16832 }
16833
16834
16835 int
16836 ToUpper (int c)
16837 {
16838     return islower(c) ? toupper(c) : c;
16839 }
16840 #endif /* !_amigados    */
16841
16842 char *
16843 StrSave (char *s)
16844 {
16845   char *ret;
16846
16847   if ((ret = (char *) malloc(strlen(s) + 1)))
16848     {
16849       safeStrCpy(ret, s, strlen(s)+1);
16850     }
16851   return ret;
16852 }
16853
16854 char *
16855 StrSavePtr (char *s, char **savePtr)
16856 {
16857     if (*savePtr) {
16858         free(*savePtr);
16859     }
16860     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16861       safeStrCpy(*savePtr, s, strlen(s)+1);
16862     }
16863     return(*savePtr);
16864 }
16865
16866 char *
16867 PGNDate ()
16868 {
16869     time_t clock;
16870     struct tm *tm;
16871     char buf[MSG_SIZ];
16872
16873     clock = time((time_t *)NULL);
16874     tm = localtime(&clock);
16875     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16876             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16877     return StrSave(buf);
16878 }
16879
16880
16881 char *
16882 PositionToFEN (int move, char *overrideCastling)
16883 {
16884     int i, j, fromX, fromY, toX, toY;
16885     int whiteToPlay;
16886     char buf[MSG_SIZ];
16887     char *p, *q;
16888     int emptycount;
16889     ChessSquare piece;
16890
16891     whiteToPlay = (gameMode == EditPosition) ?
16892       !blackPlaysFirst : (move % 2 == 0);
16893     p = buf;
16894
16895     /* Piece placement data */
16896     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16897         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16898         emptycount = 0;
16899         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16900             if (boards[move][i][j] == EmptySquare) {
16901                 emptycount++;
16902             } else { ChessSquare piece = boards[move][i][j];
16903                 if (emptycount > 0) {
16904                     if(emptycount<10) /* [HGM] can be >= 10 */
16905                         *p++ = '0' + emptycount;
16906                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16907                     emptycount = 0;
16908                 }
16909                 if(PieceToChar(piece) == '+') {
16910                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16911                     *p++ = '+';
16912                     piece = (ChessSquare)(DEMOTED piece);
16913                 }
16914                 *p++ = PieceToChar(piece);
16915                 if(p[-1] == '~') {
16916                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16917                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16918                     *p++ = '~';
16919                 }
16920             }
16921         }
16922         if (emptycount > 0) {
16923             if(emptycount<10) /* [HGM] can be >= 10 */
16924                 *p++ = '0' + emptycount;
16925             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16926             emptycount = 0;
16927         }
16928         *p++ = '/';
16929     }
16930     *(p - 1) = ' ';
16931
16932     /* [HGM] print Crazyhouse or Shogi holdings */
16933     if( gameInfo.holdingsWidth ) {
16934         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16935         q = p;
16936         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16937             piece = boards[move][i][BOARD_WIDTH-1];
16938             if( piece != EmptySquare )
16939               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16940                   *p++ = PieceToChar(piece);
16941         }
16942         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16943             piece = boards[move][BOARD_HEIGHT-i-1][0];
16944             if( piece != EmptySquare )
16945               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16946                   *p++ = PieceToChar(piece);
16947         }
16948
16949         if( q == p ) *p++ = '-';
16950         *p++ = ']';
16951         *p++ = ' ';
16952     }
16953
16954     /* Active color */
16955     *p++ = whiteToPlay ? 'w' : 'b';
16956     *p++ = ' ';
16957
16958   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16959     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16960   } else {
16961   if(nrCastlingRights) {
16962      q = p;
16963      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16964        /* [HGM] write directly from rights */
16965            if(boards[move][CASTLING][2] != NoRights &&
16966               boards[move][CASTLING][0] != NoRights   )
16967                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16968            if(boards[move][CASTLING][2] != NoRights &&
16969               boards[move][CASTLING][1] != NoRights   )
16970                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16971            if(boards[move][CASTLING][5] != NoRights &&
16972               boards[move][CASTLING][3] != NoRights   )
16973                 *p++ = boards[move][CASTLING][3] + AAA;
16974            if(boards[move][CASTLING][5] != NoRights &&
16975               boards[move][CASTLING][4] != NoRights   )
16976                 *p++ = boards[move][CASTLING][4] + AAA;
16977      } else {
16978
16979         /* [HGM] write true castling rights */
16980         if( nrCastlingRights == 6 ) {
16981             int q, k=0;
16982             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16983                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
16984             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
16985                  boards[move][CASTLING][2] != NoRights  );
16986             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
16987                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
16988                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16989                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
16990                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
16991             }
16992             if(q) *p++ = 'Q';
16993             k = 0;
16994             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16995                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
16996             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
16997                  boards[move][CASTLING][5] != NoRights  );
16998             if(gameInfo.variant == VariantSChess) {
16999                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17000                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17001                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17002                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17003             }
17004             if(q) *p++ = 'q';
17005         }
17006      }
17007      if (q == p) *p++ = '-'; /* No castling rights */
17008      *p++ = ' ';
17009   }
17010
17011   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17012      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17013     /* En passant target square */
17014     if (move > backwardMostMove) {
17015         fromX = moveList[move - 1][0] - AAA;
17016         fromY = moveList[move - 1][1] - ONE;
17017         toX = moveList[move - 1][2] - AAA;
17018         toY = moveList[move - 1][3] - ONE;
17019         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17020             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17021             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17022             fromX == toX) {
17023             /* 2-square pawn move just happened */
17024             *p++ = toX + AAA;
17025             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17026         } else {
17027             *p++ = '-';
17028         }
17029     } else if(move == backwardMostMove) {
17030         // [HGM] perhaps we should always do it like this, and forget the above?
17031         if((signed char)boards[move][EP_STATUS] >= 0) {
17032             *p++ = boards[move][EP_STATUS] + AAA;
17033             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17034         } else {
17035             *p++ = '-';
17036         }
17037     } else {
17038         *p++ = '-';
17039     }
17040     *p++ = ' ';
17041   }
17042   }
17043
17044     /* [HGM] find reversible plies */
17045     {   int i = 0, j=move;
17046
17047         if (appData.debugMode) { int k;
17048             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17049             for(k=backwardMostMove; k<=forwardMostMove; k++)
17050                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17051
17052         }
17053
17054         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17055         if( j == backwardMostMove ) i += initialRulePlies;
17056         sprintf(p, "%d ", i);
17057         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17058     }
17059     /* Fullmove number */
17060     sprintf(p, "%d", (move / 2) + 1);
17061
17062     return StrSave(buf);
17063 }
17064
17065 Boolean
17066 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17067 {
17068     int i, j;
17069     char *p, c;
17070     int emptycount, virgin[BOARD_FILES];
17071     ChessSquare piece;
17072
17073     p = fen;
17074
17075     /* [HGM] by default clear Crazyhouse holdings, if present */
17076     if(gameInfo.holdingsWidth) {
17077        for(i=0; i<BOARD_HEIGHT; i++) {
17078            board[i][0]             = EmptySquare; /* black holdings */
17079            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17080            board[i][1]             = (ChessSquare) 0; /* black counts */
17081            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17082        }
17083     }
17084
17085     /* Piece placement data */
17086     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17087         j = 0;
17088         for (;;) {
17089             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17090                 if (*p == '/') p++;
17091                 emptycount = gameInfo.boardWidth - j;
17092                 while (emptycount--)
17093                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17094                 break;
17095 #if(BOARD_FILES >= 10)
17096             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17097                 p++; emptycount=10;
17098                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17099                 while (emptycount--)
17100                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17101 #endif
17102             } else if (isdigit(*p)) {
17103                 emptycount = *p++ - '0';
17104                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17105                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17106                 while (emptycount--)
17107                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17108             } else if (*p == '+' || isalpha(*p)) {
17109                 if (j >= gameInfo.boardWidth) return FALSE;
17110                 if(*p=='+') {
17111                     piece = CharToPiece(*++p);
17112                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17113                     piece = (ChessSquare) (PROMOTED piece ); p++;
17114                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17115                 } else piece = CharToPiece(*p++);
17116
17117                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17118                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17119                     piece = (ChessSquare) (PROMOTED piece);
17120                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17121                     p++;
17122                 }
17123                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17124             } else {
17125                 return FALSE;
17126             }
17127         }
17128     }
17129     while (*p == '/' || *p == ' ') p++;
17130
17131     /* [HGM] look for Crazyhouse holdings here */
17132     while(*p==' ') p++;
17133     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17134         if(*p == '[') p++;
17135         if(*p == '-' ) p++; /* empty holdings */ else {
17136             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17137             /* if we would allow FEN reading to set board size, we would   */
17138             /* have to add holdings and shift the board read so far here   */
17139             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17140                 p++;
17141                 if((int) piece >= (int) BlackPawn ) {
17142                     i = (int)piece - (int)BlackPawn;
17143                     i = PieceToNumber((ChessSquare)i);
17144                     if( i >= gameInfo.holdingsSize ) return FALSE;
17145                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17146                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17147                 } else {
17148                     i = (int)piece - (int)WhitePawn;
17149                     i = PieceToNumber((ChessSquare)i);
17150                     if( i >= gameInfo.holdingsSize ) return FALSE;
17151                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17152                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17153                 }
17154             }
17155         }
17156         if(*p == ']') p++;
17157     }
17158
17159     while(*p == ' ') p++;
17160
17161     /* Active color */
17162     c = *p++;
17163     if(appData.colorNickNames) {
17164       if( c == appData.colorNickNames[0] ) c = 'w'; else
17165       if( c == appData.colorNickNames[1] ) c = 'b';
17166     }
17167     switch (c) {
17168       case 'w':
17169         *blackPlaysFirst = FALSE;
17170         break;
17171       case 'b':
17172         *blackPlaysFirst = TRUE;
17173         break;
17174       default:
17175         return FALSE;
17176     }
17177
17178     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17179     /* return the extra info in global variiables             */
17180
17181     /* set defaults in case FEN is incomplete */
17182     board[EP_STATUS] = EP_UNKNOWN;
17183     for(i=0; i<nrCastlingRights; i++ ) {
17184         board[CASTLING][i] =
17185             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17186     }   /* assume possible unless obviously impossible */
17187     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17188     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17189     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17190                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17191     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17192     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17193     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17194                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17195     FENrulePlies = 0;
17196
17197     while(*p==' ') p++;
17198     if(nrCastlingRights) {
17199       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17200       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17201           /* castling indicator present, so default becomes no castlings */
17202           for(i=0; i<nrCastlingRights; i++ ) {
17203                  board[CASTLING][i] = NoRights;
17204           }
17205       }
17206       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17207              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17208              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17209              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17210         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17211
17212         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17213             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17214             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17215         }
17216         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17217             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17218         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17219                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17220         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17221                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17222         switch(c) {
17223           case'K':
17224               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17225               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17226               board[CASTLING][2] = whiteKingFile;
17227               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17228               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17229               break;
17230           case'Q':
17231               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17232               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17233               board[CASTLING][2] = whiteKingFile;
17234               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17235               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17236               break;
17237           case'k':
17238               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17239               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17240               board[CASTLING][5] = blackKingFile;
17241               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17242               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17243               break;
17244           case'q':
17245               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17246               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17247               board[CASTLING][5] = blackKingFile;
17248               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17249               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17250           case '-':
17251               break;
17252           default: /* FRC castlings */
17253               if(c >= 'a') { /* black rights */
17254                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17255                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17256                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17257                   if(i == BOARD_RGHT) break;
17258                   board[CASTLING][5] = i;
17259                   c -= AAA;
17260                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17261                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17262                   if(c > i)
17263                       board[CASTLING][3] = c;
17264                   else
17265                       board[CASTLING][4] = c;
17266               } else { /* white rights */
17267                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17268                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17269                     if(board[0][i] == WhiteKing) break;
17270                   if(i == BOARD_RGHT) break;
17271                   board[CASTLING][2] = i;
17272                   c -= AAA - 'a' + 'A';
17273                   if(board[0][c] >= WhiteKing) break;
17274                   if(c > i)
17275                       board[CASTLING][0] = c;
17276                   else
17277                       board[CASTLING][1] = c;
17278               }
17279         }
17280       }
17281       for(i=0; i<nrCastlingRights; i++)
17282         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17283       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17284     if (appData.debugMode) {
17285         fprintf(debugFP, "FEN castling rights:");
17286         for(i=0; i<nrCastlingRights; i++)
17287         fprintf(debugFP, " %d", board[CASTLING][i]);
17288         fprintf(debugFP, "\n");
17289     }
17290
17291       while(*p==' ') p++;
17292     }
17293
17294     /* read e.p. field in games that know e.p. capture */
17295     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17296        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17297       if(*p=='-') {
17298         p++; board[EP_STATUS] = EP_NONE;
17299       } else {
17300          char c = *p++ - AAA;
17301
17302          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17303          if(*p >= '0' && *p <='9') p++;
17304          board[EP_STATUS] = c;
17305       }
17306     }
17307
17308
17309     if(sscanf(p, "%d", &i) == 1) {
17310         FENrulePlies = i; /* 50-move ply counter */
17311         /* (The move number is still ignored)    */
17312     }
17313
17314     return TRUE;
17315 }
17316
17317 void
17318 EditPositionPasteFEN (char *fen)
17319 {
17320   if (fen != NULL) {
17321     Board initial_position;
17322
17323     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17324       DisplayError(_("Bad FEN position in clipboard"), 0);
17325       return ;
17326     } else {
17327       int savedBlackPlaysFirst = blackPlaysFirst;
17328       EditPositionEvent();
17329       blackPlaysFirst = savedBlackPlaysFirst;
17330       CopyBoard(boards[0], initial_position);
17331       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17332       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17333       DisplayBothClocks();
17334       DrawPosition(FALSE, boards[currentMove]);
17335     }
17336   }
17337 }
17338
17339 static char cseq[12] = "\\   ";
17340
17341 Boolean
17342 set_cont_sequence (char *new_seq)
17343 {
17344     int len;
17345     Boolean ret;
17346
17347     // handle bad attempts to set the sequence
17348         if (!new_seq)
17349                 return 0; // acceptable error - no debug
17350
17351     len = strlen(new_seq);
17352     ret = (len > 0) && (len < sizeof(cseq));
17353     if (ret)
17354       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17355     else if (appData.debugMode)
17356       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17357     return ret;
17358 }
17359
17360 /*
17361     reformat a source message so words don't cross the width boundary.  internal
17362     newlines are not removed.  returns the wrapped size (no null character unless
17363     included in source message).  If dest is NULL, only calculate the size required
17364     for the dest buffer.  lp argument indicats line position upon entry, and it's
17365     passed back upon exit.
17366 */
17367 int
17368 wrap (char *dest, char *src, int count, int width, int *lp)
17369 {
17370     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17371
17372     cseq_len = strlen(cseq);
17373     old_line = line = *lp;
17374     ansi = len = clen = 0;
17375
17376     for (i=0; i < count; i++)
17377     {
17378         if (src[i] == '\033')
17379             ansi = 1;
17380
17381         // if we hit the width, back up
17382         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17383         {
17384             // store i & len in case the word is too long
17385             old_i = i, old_len = len;
17386
17387             // find the end of the last word
17388             while (i && src[i] != ' ' && src[i] != '\n')
17389             {
17390                 i--;
17391                 len--;
17392             }
17393
17394             // word too long?  restore i & len before splitting it
17395             if ((old_i-i+clen) >= width)
17396             {
17397                 i = old_i;
17398                 len = old_len;
17399             }
17400
17401             // extra space?
17402             if (i && src[i-1] == ' ')
17403                 len--;
17404
17405             if (src[i] != ' ' && src[i] != '\n')
17406             {
17407                 i--;
17408                 if (len)
17409                     len--;
17410             }
17411
17412             // now append the newline and continuation sequence
17413             if (dest)
17414                 dest[len] = '\n';
17415             len++;
17416             if (dest)
17417                 strncpy(dest+len, cseq, cseq_len);
17418             len += cseq_len;
17419             line = cseq_len;
17420             clen = cseq_len;
17421             continue;
17422         }
17423
17424         if (dest)
17425             dest[len] = src[i];
17426         len++;
17427         if (!ansi)
17428             line++;
17429         if (src[i] == '\n')
17430             line = 0;
17431         if (src[i] == 'm')
17432             ansi = 0;
17433     }
17434     if (dest && appData.debugMode)
17435     {
17436         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17437             count, width, line, len, *lp);
17438         show_bytes(debugFP, src, count);
17439         fprintf(debugFP, "\ndest: ");
17440         show_bytes(debugFP, dest, len);
17441         fprintf(debugFP, "\n");
17442     }
17443     *lp = dest ? line : old_line;
17444
17445     return len;
17446 }
17447
17448 // [HGM] vari: routines for shelving variations
17449 Boolean modeRestore = FALSE;
17450
17451 void
17452 PushInner (int firstMove, int lastMove)
17453 {
17454         int i, j, nrMoves = lastMove - firstMove;
17455
17456         // push current tail of game on stack
17457         savedResult[storedGames] = gameInfo.result;
17458         savedDetails[storedGames] = gameInfo.resultDetails;
17459         gameInfo.resultDetails = NULL;
17460         savedFirst[storedGames] = firstMove;
17461         savedLast [storedGames] = lastMove;
17462         savedFramePtr[storedGames] = framePtr;
17463         framePtr -= nrMoves; // reserve space for the boards
17464         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17465             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17466             for(j=0; j<MOVE_LEN; j++)
17467                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17468             for(j=0; j<2*MOVE_LEN; j++)
17469                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17470             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17471             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17472             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17473             pvInfoList[firstMove+i-1].depth = 0;
17474             commentList[framePtr+i] = commentList[firstMove+i];
17475             commentList[firstMove+i] = NULL;
17476         }
17477
17478         storedGames++;
17479         forwardMostMove = firstMove; // truncate game so we can start variation
17480 }
17481
17482 void
17483 PushTail (int firstMove, int lastMove)
17484 {
17485         if(appData.icsActive) { // only in local mode
17486                 forwardMostMove = currentMove; // mimic old ICS behavior
17487                 return;
17488         }
17489         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17490
17491         PushInner(firstMove, lastMove);
17492         if(storedGames == 1) GreyRevert(FALSE);
17493         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17494 }
17495
17496 void
17497 PopInner (Boolean annotate)
17498 {
17499         int i, j, nrMoves;
17500         char buf[8000], moveBuf[20];
17501
17502         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17503         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17504         nrMoves = savedLast[storedGames] - currentMove;
17505         if(annotate) {
17506                 int cnt = 10;
17507                 if(!WhiteOnMove(currentMove))
17508                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17509                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17510                 for(i=currentMove; i<forwardMostMove; i++) {
17511                         if(WhiteOnMove(i))
17512                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17513                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17514                         strcat(buf, moveBuf);
17515                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17516                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17517                 }
17518                 strcat(buf, ")");
17519         }
17520         for(i=1; i<=nrMoves; i++) { // copy last variation back
17521             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17522             for(j=0; j<MOVE_LEN; j++)
17523                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17524             for(j=0; j<2*MOVE_LEN; j++)
17525                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17526             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17527             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17528             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17529             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17530             commentList[currentMove+i] = commentList[framePtr+i];
17531             commentList[framePtr+i] = NULL;
17532         }
17533         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17534         framePtr = savedFramePtr[storedGames];
17535         gameInfo.result = savedResult[storedGames];
17536         if(gameInfo.resultDetails != NULL) {
17537             free(gameInfo.resultDetails);
17538       }
17539         gameInfo.resultDetails = savedDetails[storedGames];
17540         forwardMostMove = currentMove + nrMoves;
17541 }
17542
17543 Boolean
17544 PopTail (Boolean annotate)
17545 {
17546         if(appData.icsActive) return FALSE; // only in local mode
17547         if(!storedGames) return FALSE; // sanity
17548         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17549
17550         PopInner(annotate);
17551         if(currentMove < forwardMostMove) ForwardEvent(); else
17552         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17553
17554         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17555         return TRUE;
17556 }
17557
17558 void
17559 CleanupTail ()
17560 {       // remove all shelved variations
17561         int i;
17562         for(i=0; i<storedGames; i++) {
17563             if(savedDetails[i])
17564                 free(savedDetails[i]);
17565             savedDetails[i] = NULL;
17566         }
17567         for(i=framePtr; i<MAX_MOVES; i++) {
17568                 if(commentList[i]) free(commentList[i]);
17569                 commentList[i] = NULL;
17570         }
17571         framePtr = MAX_MOVES-1;
17572         storedGames = 0;
17573 }
17574
17575 void
17576 LoadVariation (int index, char *text)
17577 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17578         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17579         int level = 0, move;
17580
17581         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17582         // first find outermost bracketing variation
17583         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17584             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17585                 if(*p == '{') wait = '}'; else
17586                 if(*p == '[') wait = ']'; else
17587                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17588                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17589             }
17590             if(*p == wait) wait = NULLCHAR; // closing ]} found
17591             p++;
17592         }
17593         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17594         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17595         end[1] = NULLCHAR; // clip off comment beyond variation
17596         ToNrEvent(currentMove-1);
17597         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17598         // kludge: use ParsePV() to append variation to game
17599         move = currentMove;
17600         ParsePV(start, TRUE, TRUE);
17601         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17602         ClearPremoveHighlights();
17603         CommentPopDown();
17604         ToNrEvent(currentMove+1);
17605 }
17606
17607 void
17608 LoadTheme ()
17609 {
17610     char *p, *q, buf[MSG_SIZ];
17611     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17612         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17613         ParseArgsFromString(buf);
17614         ActivateTheme(TRUE); // also redo colors
17615         return;
17616     }
17617     p = nickName;
17618     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17619     {
17620         int len;
17621         q = appData.themeNames;
17622         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17623       if(appData.useBitmaps) {
17624         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17625                 appData.liteBackTextureFile, appData.darkBackTextureFile, 
17626                 appData.liteBackTextureMode,
17627                 appData.darkBackTextureMode );
17628       } else {
17629         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17630                 Col2Text(2),   // lightSquareColor
17631                 Col2Text(3) ); // darkSquareColor
17632       }
17633       if(appData.useBorder) {
17634         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17635                 appData.border);
17636       } else {
17637         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17638       }
17639       if(appData.useFont) {
17640         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17641                 appData.renderPiecesWithFont,
17642                 appData.fontToPieceTable,
17643                 Col2Text(9),    // appData.fontBackColorWhite
17644                 Col2Text(10) ); // appData.fontForeColorBlack
17645       } else {
17646         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17647                 appData.pieceDirectory);
17648         if(!appData.pieceDirectory[0])
17649           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17650                 Col2Text(0),   // whitePieceColor
17651                 Col2Text(1) ); // blackPieceColor
17652       }
17653       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17654                 Col2Text(4),   // highlightSquareColor
17655                 Col2Text(5) ); // premoveHighlightColor
17656         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17657         if(insert != q) insert[-1] = NULLCHAR;
17658         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17659         if(q)   free(q);
17660     }
17661     ActivateTheme(FALSE);
17662 }