2bde9f67a79fd572ee644f54521d9502f8fad30e
[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 "gettext.h"
133
134 #ifdef ENABLE_NLS
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
138 #else
139 # ifdef WIN32
140 #   define _(s) T_(s)
141 #   define N_(s) s
142 # else
143 #   define _(s) (s)
144 #   define N_(s) s
145 #   define T_(s) s
146 # endif
147 #endif
148
149
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152                          char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154                       char *buf, int count, int error));
155 void ics_printf P((char *format, ...));
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
227 #ifdef WIN32
228        extern void ConsoleCreate();
229 #endif
230
231 ChessProgramState *WhitePlayer();
232 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
233 int VerifyDisplayMode P(());
234
235 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
236 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
237 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
238 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
239 void ics_update_width P((int new_width));
240 extern char installDir[MSG_SIZ];
241 VariantClass startVariant; /* [HGM] nicks: initial variant */
242 Boolean abortMatch;
243
244 extern int tinyLayout, smallLayout;
245 ChessProgramStats programStats;
246 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
247 int endPV = -1;
248 static int exiting = 0; /* [HGM] moved to top */
249 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
250 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
251 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
252 int partnerHighlight[2];
253 Boolean partnerBoardValid = 0;
254 char partnerStatus[MSG_SIZ];
255 Boolean partnerUp;
256 Boolean originalFlip;
257 Boolean twoBoards = 0;
258 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
259 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
260 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
261 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
262 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
263 int opponentKibitzes;
264 int lastSavedGame; /* [HGM] save: ID of game */
265 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
266 extern int chatCount;
267 int chattingPartner;
268 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
269 char lastMsg[MSG_SIZ];
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
272 int promoDefaultAltered;
273
274 /* States for ics_getting_history */
275 #define H_FALSE 0
276 #define H_REQUESTED 1
277 #define H_GOT_REQ_HEADER 2
278 #define H_GOT_UNREQ_HEADER 3
279 #define H_GETTING_MOVES 4
280 #define H_GOT_UNWANTED_HEADER 5
281
282 /* whosays values for GameEnds */
283 #define GE_ICS 0
284 #define GE_ENGINE 1
285 #define GE_PLAYER 2
286 #define GE_FILE 3
287 #define GE_XBOARD 4
288 #define GE_ENGINE1 5
289 #define GE_ENGINE2 6
290
291 /* Maximum number of games in a cmail message */
292 #define CMAIL_MAX_GAMES 20
293
294 /* Different types of move when calling RegisterMove */
295 #define CMAIL_MOVE   0
296 #define CMAIL_RESIGN 1
297 #define CMAIL_DRAW   2
298 #define CMAIL_ACCEPT 3
299
300 /* Different types of result to remember for each game */
301 #define CMAIL_NOT_RESULT 0
302 #define CMAIL_OLD_RESULT 1
303 #define CMAIL_NEW_RESULT 2
304
305 /* Telnet protocol constants */
306 #define TN_WILL 0373
307 #define TN_WONT 0374
308 #define TN_DO   0375
309 #define TN_DONT 0376
310 #define TN_IAC  0377
311 #define TN_ECHO 0001
312 #define TN_SGA  0003
313 #define TN_PORT 23
314
315 char*
316 safeStrCpy (char *dst, const char *src, size_t count)
317 { // [HGM] made safe
318   int i;
319   assert( dst != NULL );
320   assert( src != NULL );
321   assert( count > 0 );
322
323   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
324   if(  i == count && dst[count-1] != NULLCHAR)
325     {
326       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
327       if(appData.debugMode)
328       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
329     }
330
331   return dst;
332 }
333
334 /* Some compiler can't cast u64 to double
335  * This function do the job for us:
336
337  * We use the highest bit for cast, this only
338  * works if the highest bit is not
339  * in use (This should not happen)
340  *
341  * We used this for all compiler
342  */
343 double
344 u64ToDouble (u64 value)
345 {
346   double r;
347   u64 tmp = value & u64Const(0x7fffffffffffffff);
348   r = (double)(s64)tmp;
349   if (value & u64Const(0x8000000000000000))
350        r +=  9.2233720368547758080e18; /* 2^63 */
351  return r;
352 }
353
354 /* Fake up flags for now, as we aren't keeping track of castling
355    availability yet. [HGM] Change of logic: the flag now only
356    indicates the type of castlings allowed by the rule of the game.
357    The actual rights themselves are maintained in the array
358    castlingRights, as part of the game history, and are not probed
359    by this function.
360  */
361 int
362 PosFlags (index)
363 {
364   int flags = F_ALL_CASTLE_OK;
365   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
366   switch (gameInfo.variant) {
367   case VariantSuicide:
368     flags &= ~F_ALL_CASTLE_OK;
369   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
370     flags |= F_IGNORE_CHECK;
371   case VariantLosers:
372     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
373     break;
374   case VariantAtomic:
375     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
376     break;
377   case VariantKriegspiel:
378     flags |= F_KRIEGSPIEL_CAPTURE;
379     break;
380   case VariantCapaRandom:
381   case VariantFischeRandom:
382     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
383   case VariantNoCastle:
384   case VariantShatranj:
385   case VariantCourier:
386   case VariantMakruk:
387   case VariantGrand:
388     flags &= ~F_ALL_CASTLE_OK;
389     break;
390   default:
391     break;
392   }
393   return flags;
394 }
395
396 FILE *gameFileFP, *debugFP, *serverFP;
397 char *currentDebugFile; // [HGM] debug split: to remember name
398
399 /*
400     [AS] Note: sometimes, the sscanf() function is used to parse the input
401     into a fixed-size buffer. Because of this, we must be prepared to
402     receive strings as long as the size of the input buffer, which is currently
403     set to 4K for Windows and 8K for the rest.
404     So, we must either allocate sufficiently large buffers here, or
405     reduce the size of the input buffer in the input reading part.
406 */
407
408 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
409 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
410 char thinkOutput1[MSG_SIZ*10];
411
412 ChessProgramState first, second, pairing;
413
414 /* premove variables */
415 int premoveToX = 0;
416 int premoveToY = 0;
417 int premoveFromX = 0;
418 int premoveFromY = 0;
419 int premovePromoChar = 0;
420 int gotPremove = 0;
421 Boolean alarmSounded;
422 /* end premove variables */
423
424 char *ics_prefix = "$";
425 int ics_type = ICS_GENERIC;
426
427 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
428 int pauseExamForwardMostMove = 0;
429 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
430 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
431 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
432 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
433 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
434 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
435 int whiteFlag = FALSE, blackFlag = FALSE;
436 int userOfferedDraw = FALSE;
437 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
438 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
439 int cmailMoveType[CMAIL_MAX_GAMES];
440 long ics_clock_paused = 0;
441 ProcRef icsPR = NoProc, cmailPR = NoProc;
442 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
443 GameMode gameMode = BeginningOfGame;
444 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
445 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
446 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
447 int hiddenThinkOutputState = 0; /* [AS] */
448 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
449 int adjudicateLossPlies = 6;
450 char white_holding[64], black_holding[64];
451 TimeMark lastNodeCountTime;
452 long lastNodeCount=0;
453 int shiftKey, controlKey; // [HGM] set by mouse handler
454
455 int have_sent_ICS_logon = 0;
456 int movesPerSession;
457 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
458 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
459 Boolean adjustedClock;
460 long timeControl_2; /* [AS] Allow separate time controls */
461 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
462 long timeRemaining[2][MAX_MOVES];
463 int matchGame = 0, nextGame = 0, roundNr = 0;
464 Boolean waitingForGame = FALSE;
465 TimeMark programStartTime, pauseStart;
466 char ics_handle[MSG_SIZ];
467 int have_set_title = 0;
468
469 /* animateTraining preserves the state of appData.animate
470  * when Training mode is activated. This allows the
471  * response to be animated when appData.animate == TRUE and
472  * appData.animateDragging == TRUE.
473  */
474 Boolean animateTraining;
475
476 GameInfo gameInfo;
477
478 AppData appData;
479
480 Board boards[MAX_MOVES];
481 /* [HGM] Following 7 needed for accurate legality tests: */
482 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
483 signed char  initialRights[BOARD_FILES];
484 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
485 int   initialRulePlies, FENrulePlies;
486 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
487 int loadFlag = 0;
488 Boolean shuffleOpenings;
489 int mute; // mute all sounds
490
491 // [HGM] vari: next 12 to save and restore variations
492 #define MAX_VARIATIONS 10
493 int framePtr = MAX_MOVES-1; // points to free stack entry
494 int storedGames = 0;
495 int savedFirst[MAX_VARIATIONS];
496 int savedLast[MAX_VARIATIONS];
497 int savedFramePtr[MAX_VARIATIONS];
498 char *savedDetails[MAX_VARIATIONS];
499 ChessMove savedResult[MAX_VARIATIONS];
500
501 void PushTail P((int firstMove, int lastMove));
502 Boolean PopTail P((Boolean annotate));
503 void PushInner P((int firstMove, int lastMove));
504 void PopInner P((Boolean annotate));
505 void CleanupTail P((void));
506
507 ChessSquare  FIDEArray[2][BOARD_FILES] = {
508     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
509         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
510     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
511         BlackKing, BlackBishop, BlackKnight, BlackRook }
512 };
513
514 ChessSquare twoKingsArray[2][BOARD_FILES] = {
515     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
516         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
517     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
518         BlackKing, BlackKing, BlackKnight, BlackRook }
519 };
520
521 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
522     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
523         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
524     { BlackRook, BlackMan, BlackBishop, BlackQueen,
525         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
526 };
527
528 ChessSquare SpartanArray[2][BOARD_FILES] = {
529     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
530         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
531     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
532         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
533 };
534
535 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
536     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
537         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
538     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
539         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
540 };
541
542 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
543     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
544         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
545     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
546         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
547 };
548
549 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
550     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
551         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
552     { BlackRook, BlackKnight, BlackMan, BlackFerz,
553         BlackKing, BlackMan, BlackKnight, BlackRook }
554 };
555
556
557 #if (BOARD_FILES>=10)
558 ChessSquare ShogiArray[2][BOARD_FILES] = {
559     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
560         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
561     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
562         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
563 };
564
565 ChessSquare XiangqiArray[2][BOARD_FILES] = {
566     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
567         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
568     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
569         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
570 };
571
572 ChessSquare CapablancaArray[2][BOARD_FILES] = {
573     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
574         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
575     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
576         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
577 };
578
579 ChessSquare GreatArray[2][BOARD_FILES] = {
580     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
581         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
582     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
583         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
584 };
585
586 ChessSquare JanusArray[2][BOARD_FILES] = {
587     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
588         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
589     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
590         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
591 };
592
593 ChessSquare GrandArray[2][BOARD_FILES] = {
594     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
595         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
596     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
597         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
598 };
599
600 #ifdef GOTHIC
601 ChessSquare GothicArray[2][BOARD_FILES] = {
602     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
603         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
604     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
605         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
606 };
607 #else // !GOTHIC
608 #define GothicArray CapablancaArray
609 #endif // !GOTHIC
610
611 #ifdef FALCON
612 ChessSquare FalconArray[2][BOARD_FILES] = {
613     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
614         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
615     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
616         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
617 };
618 #else // !FALCON
619 #define FalconArray CapablancaArray
620 #endif // !FALCON
621
622 #else // !(BOARD_FILES>=10)
623 #define XiangqiPosition FIDEArray
624 #define CapablancaArray FIDEArray
625 #define GothicArray FIDEArray
626 #define GreatArray FIDEArray
627 #endif // !(BOARD_FILES>=10)
628
629 #if (BOARD_FILES>=12)
630 ChessSquare CourierArray[2][BOARD_FILES] = {
631     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
632         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
633     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
634         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
635 };
636 #else // !(BOARD_FILES>=12)
637 #define CourierArray CapablancaArray
638 #endif // !(BOARD_FILES>=12)
639
640
641 Board initialPosition;
642
643
644 /* Convert str to a rating. Checks for special cases of "----",
645
646    "++++", etc. Also strips ()'s */
647 int
648 string_to_rating (char *str)
649 {
650   while(*str && !isdigit(*str)) ++str;
651   if (!*str)
652     return 0;   /* One of the special "no rating" cases */
653   else
654     return atoi(str);
655 }
656
657 void
658 ClearProgramStats ()
659 {
660     /* Init programStats */
661     programStats.movelist[0] = 0;
662     programStats.depth = 0;
663     programStats.nr_moves = 0;
664     programStats.moves_left = 0;
665     programStats.nodes = 0;
666     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
667     programStats.score = 0;
668     programStats.got_only_move = 0;
669     programStats.got_fail = 0;
670     programStats.line_is_book = 0;
671 }
672
673 void
674 CommonEngineInit ()
675 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
676     if (appData.firstPlaysBlack) {
677         first.twoMachinesColor = "black\n";
678         second.twoMachinesColor = "white\n";
679     } else {
680         first.twoMachinesColor = "white\n";
681         second.twoMachinesColor = "black\n";
682     }
683
684     first.other = &second;
685     second.other = &first;
686
687     { float norm = 1;
688         if(appData.timeOddsMode) {
689             norm = appData.timeOdds[0];
690             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
691         }
692         first.timeOdds  = appData.timeOdds[0]/norm;
693         second.timeOdds = appData.timeOdds[1]/norm;
694     }
695
696     if(programVersion) free(programVersion);
697     if (appData.noChessProgram) {
698         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
699         sprintf(programVersion, "%s", PACKAGE_STRING);
700     } else {
701       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
702       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
703       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
704     }
705 }
706
707 void
708 UnloadEngine (ChessProgramState *cps)
709 {
710         /* Kill off first chess program */
711         if (cps->isr != NULL)
712           RemoveInputSource(cps->isr);
713         cps->isr = NULL;
714
715         if (cps->pr != NoProc) {
716             ExitAnalyzeMode();
717             DoSleep( appData.delayBeforeQuit );
718             SendToProgram("quit\n", cps);
719             DoSleep( appData.delayAfterQuit );
720             DestroyChildProcess(cps->pr, cps->useSigterm);
721         }
722         cps->pr = NoProc;
723         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
724 }
725
726 void
727 ClearOptions (ChessProgramState *cps)
728 {
729     int i;
730     cps->nrOptions = cps->comboCnt = 0;
731     for(i=0; i<MAX_OPTIONS; i++) {
732         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
733         cps->option[i].textValue = 0;
734     }
735 }
736
737 char *engineNames[] = {
738   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
739      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
740 N_("first"),
741   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
742      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
743 N_("second")
744 };
745
746 void
747 InitEngine (ChessProgramState *cps, int n)
748 {   // [HGM] all engine initialiation put in a function that does one engine
749
750     ClearOptions(cps);
751
752     cps->which = engineNames[n];
753     cps->maybeThinking = FALSE;
754     cps->pr = NoProc;
755     cps->isr = NULL;
756     cps->sendTime = 2;
757     cps->sendDrawOffers = 1;
758
759     cps->program = appData.chessProgram[n];
760     cps->host = appData.host[n];
761     cps->dir = appData.directory[n];
762     cps->initString = appData.engInitString[n];
763     cps->computerString = appData.computerString[n];
764     cps->useSigint  = TRUE;
765     cps->useSigterm = TRUE;
766     cps->reuse = appData.reuse[n];
767     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
768     cps->useSetboard = FALSE;
769     cps->useSAN = FALSE;
770     cps->usePing = FALSE;
771     cps->lastPing = 0;
772     cps->lastPong = 0;
773     cps->usePlayother = FALSE;
774     cps->useColors = TRUE;
775     cps->useUsermove = FALSE;
776     cps->sendICS = FALSE;
777     cps->sendName = appData.icsActive;
778     cps->sdKludge = FALSE;
779     cps->stKludge = FALSE;
780     TidyProgramName(cps->program, cps->host, cps->tidy);
781     cps->matchWins = 0;
782     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
783     cps->analysisSupport = 2; /* detect */
784     cps->analyzing = FALSE;
785     cps->initDone = FALSE;
786
787     /* New features added by Tord: */
788     cps->useFEN960 = FALSE;
789     cps->useOOCastle = TRUE;
790     /* End of new features added by Tord. */
791     cps->fenOverride  = appData.fenOverride[n];
792
793     /* [HGM] time odds: set factor for each machine */
794     cps->timeOdds  = appData.timeOdds[n];
795
796     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
797     cps->accumulateTC = appData.accumulateTC[n];
798     cps->maxNrOfSessions = 1;
799
800     /* [HGM] debug */
801     cps->debug = FALSE;
802
803     cps->supportsNPS = UNKNOWN;
804     cps->memSize = FALSE;
805     cps->maxCores = FALSE;
806     cps->egtFormats[0] = NULLCHAR;
807
808     /* [HGM] options */
809     cps->optionSettings  = appData.engOptions[n];
810
811     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
812     cps->isUCI = appData.isUCI[n]; /* [AS] */
813     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
814
815     if (appData.protocolVersion[n] > PROTOVER
816         || appData.protocolVersion[n] < 1)
817       {
818         char buf[MSG_SIZ];
819         int len;
820
821         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
822                        appData.protocolVersion[n]);
823         if( (len >= MSG_SIZ) && appData.debugMode )
824           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
825
826         DisplayFatalError(buf, 0, 2);
827       }
828     else
829       {
830         cps->protocolVersion = appData.protocolVersion[n];
831       }
832
833     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
834     ParseFeatures(appData.featureDefaults, cps);
835 }
836
837 ChessProgramState *savCps;
838
839 void
840 LoadEngine ()
841 {
842     int i;
843     if(WaitForEngine(savCps, LoadEngine)) return;
844     CommonEngineInit(); // recalculate time odds
845     if(gameInfo.variant != StringToVariant(appData.variant)) {
846         // we changed variant when loading the engine; this forces us to reset
847         Reset(TRUE, savCps != &first);
848         EditGameEvent(); // for consistency with other path, as Reset changes mode
849     }
850     InitChessProgram(savCps, FALSE);
851     SendToProgram("force\n", savCps);
852     DisplayMessage("", "");
853     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
854     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
855     ThawUI();
856     SetGNUMode();
857 }
858
859 void
860 ReplaceEngine (ChessProgramState *cps, int n)
861 {
862     EditGameEvent();
863     UnloadEngine(cps);
864     appData.noChessProgram = FALSE;
865     appData.clockMode = TRUE;
866     InitEngine(cps, n);
867     UpdateLogos(TRUE);
868     if(n) return; // only startup first engine immediately; second can wait
869     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
870     LoadEngine();
871 }
872
873 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
874 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
875
876 static char resetOptions[] = 
877         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
878         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
879         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
880         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
881
882 void
883 FloatToFront(char **list, char *engineLine)
884 {
885     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
886     int i=0;
887     if(appData.recentEngines <= 0) return;
888     TidyProgramName(engineLine, "localhost", tidy+1);
889     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
890     strncpy(buf+1, *list, MSG_SIZ-50);
891     if(p = strstr(buf, tidy)) { // tidy name appears in list
892         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
893         while(*p++ = *++q); // squeeze out
894     }
895     strcat(tidy, buf+1); // put list behind tidy name
896     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
897     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
898     ASSIGN(*list, tidy+1);
899 }
900
901 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
902
903 void
904 Load (ChessProgramState *cps, int i)
905 {
906     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
907     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
908         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
909         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
910         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
911         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
912         appData.firstProtocolVersion = PROTOVER;
913         ParseArgsFromString(buf);
914         SwapEngines(i);
915         ReplaceEngine(cps, i);
916         FloatToFront(&appData.recentEngineList, engineLine);
917         return;
918     }
919     p = engineName;
920     while(q = strchr(p, SLASH)) p = q+1;
921     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
922     if(engineDir[0] != NULLCHAR) {
923         ASSIGN(appData.directory[i], engineDir); p = engineName;
924     } else if(p != engineName) { // derive directory from engine path, when not given
925         p[-1] = 0;
926         ASSIGN(appData.directory[i], engineName);
927         p[-1] = SLASH;
928         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
929     } else { ASSIGN(appData.directory[i], "."); }
930     if(params[0]) {
931         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
932         snprintf(command, MSG_SIZ, "%s %s", p, params);
933         p = command;
934     }
935     ASSIGN(appData.chessProgram[i], p);
936     appData.isUCI[i] = isUCI;
937     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
938     appData.hasOwnBookUCI[i] = hasBook;
939     if(!nickName[0]) useNick = FALSE;
940     if(useNick) ASSIGN(appData.pgnName[i], nickName);
941     if(addToList) {
942         int len;
943         char quote;
944         q = firstChessProgramNames;
945         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
946         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
947         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
948                         quote, p, quote, appData.directory[i], 
949                         useNick ? " -fn \"" : "",
950                         useNick ? nickName : "",
951                         useNick ? "\"" : "",
952                         v1 ? " -firstProtocolVersion 1" : "",
953                         hasBook ? "" : " -fNoOwnBookUCI",
954                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
955                         storeVariant ? " -variant " : "",
956                         storeVariant ? VariantName(gameInfo.variant) : "");
957         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
958         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
959         if(insert != q) insert[-1] = NULLCHAR;
960         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
961         if(q)   free(q);
962         FloatToFront(&appData.recentEngineList, buf);
963     }
964     ReplaceEngine(cps, i);
965 }
966
967 void
968 InitTimeControls ()
969 {
970     int matched, min, sec;
971     /*
972      * Parse timeControl resource
973      */
974     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
975                           appData.movesPerSession)) {
976         char buf[MSG_SIZ];
977         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
978         DisplayFatalError(buf, 0, 2);
979     }
980
981     /*
982      * Parse searchTime resource
983      */
984     if (*appData.searchTime != NULLCHAR) {
985         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
986         if (matched == 1) {
987             searchTime = min * 60;
988         } else if (matched == 2) {
989             searchTime = min * 60 + sec;
990         } else {
991             char buf[MSG_SIZ];
992             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
993             DisplayFatalError(buf, 0, 2);
994         }
995     }
996 }
997
998 void
999 InitBackEnd1 ()
1000 {
1001
1002     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1003     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1004
1005     GetTimeMark(&programStartTime);
1006     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1007     appData.seedBase = random() + (random()<<15);
1008     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1009
1010     ClearProgramStats();
1011     programStats.ok_to_send = 1;
1012     programStats.seen_stat = 0;
1013
1014     /*
1015      * Initialize game list
1016      */
1017     ListNew(&gameList);
1018
1019
1020     /*
1021      * Internet chess server status
1022      */
1023     if (appData.icsActive) {
1024         appData.matchMode = FALSE;
1025         appData.matchGames = 0;
1026 #if ZIPPY
1027         appData.noChessProgram = !appData.zippyPlay;
1028 #else
1029         appData.zippyPlay = FALSE;
1030         appData.zippyTalk = FALSE;
1031         appData.noChessProgram = TRUE;
1032 #endif
1033         if (*appData.icsHelper != NULLCHAR) {
1034             appData.useTelnet = TRUE;
1035             appData.telnetProgram = appData.icsHelper;
1036         }
1037     } else {
1038         appData.zippyTalk = appData.zippyPlay = FALSE;
1039     }
1040
1041     /* [AS] Initialize pv info list [HGM] and game state */
1042     {
1043         int i, j;
1044
1045         for( i=0; i<=framePtr; i++ ) {
1046             pvInfoList[i].depth = -1;
1047             boards[i][EP_STATUS] = EP_NONE;
1048             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1049         }
1050     }
1051
1052     InitTimeControls();
1053
1054     /* [AS] Adjudication threshold */
1055     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1056
1057     InitEngine(&first, 0);
1058     InitEngine(&second, 1);
1059     CommonEngineInit();
1060
1061     pairing.which = "pairing"; // pairing engine
1062     pairing.pr = NoProc;
1063     pairing.isr = NULL;
1064     pairing.program = appData.pairingEngine;
1065     pairing.host = "localhost";
1066     pairing.dir = ".";
1067
1068     if (appData.icsActive) {
1069         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1070     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1071         appData.clockMode = FALSE;
1072         first.sendTime = second.sendTime = 0;
1073     }
1074
1075 #if ZIPPY
1076     /* Override some settings from environment variables, for backward
1077        compatibility.  Unfortunately it's not feasible to have the env
1078        vars just set defaults, at least in xboard.  Ugh.
1079     */
1080     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1081       ZippyInit();
1082     }
1083 #endif
1084
1085     if (!appData.icsActive) {
1086       char buf[MSG_SIZ];
1087       int len;
1088
1089       /* Check for variants that are supported only in ICS mode,
1090          or not at all.  Some that are accepted here nevertheless
1091          have bugs; see comments below.
1092       */
1093       VariantClass variant = StringToVariant(appData.variant);
1094       switch (variant) {
1095       case VariantBughouse:     /* need four players and two boards */
1096       case VariantKriegspiel:   /* need to hide pieces and move details */
1097         /* case VariantFischeRandom: (Fabien: moved below) */
1098         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1099         if( (len >= MSG_SIZ) && appData.debugMode )
1100           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1101
1102         DisplayFatalError(buf, 0, 2);
1103         return;
1104
1105       case VariantUnknown:
1106       case VariantLoadable:
1107       case Variant29:
1108       case Variant30:
1109       case Variant31:
1110       case Variant32:
1111       case Variant33:
1112       case Variant34:
1113       case Variant35:
1114       case Variant36:
1115       default:
1116         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1117         if( (len >= MSG_SIZ) && appData.debugMode )
1118           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1119
1120         DisplayFatalError(buf, 0, 2);
1121         return;
1122
1123       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1124       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1125       case VariantGothic:     /* [HGM] should work */
1126       case VariantCapablanca: /* [HGM] should work */
1127       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1128       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1129       case VariantKnightmate: /* [HGM] should work */
1130       case VariantCylinder:   /* [HGM] untested */
1131       case VariantFalcon:     /* [HGM] untested */
1132       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1133                                  offboard interposition not understood */
1134       case VariantNormal:     /* definitely works! */
1135       case VariantWildCastle: /* pieces not automatically shuffled */
1136       case VariantNoCastle:   /* pieces not automatically shuffled */
1137       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1138       case VariantLosers:     /* should work except for win condition,
1139                                  and doesn't know captures are mandatory */
1140       case VariantSuicide:    /* should work except for win condition,
1141                                  and doesn't know captures are mandatory */
1142       case VariantGiveaway:   /* should work except for win condition,
1143                                  and doesn't know captures are mandatory */
1144       case VariantTwoKings:   /* should work */
1145       case VariantAtomic:     /* should work except for win condition */
1146       case Variant3Check:     /* should work except for win condition */
1147       case VariantShatranj:   /* should work except for all win conditions */
1148       case VariantMakruk:     /* should work except for draw countdown */
1149       case VariantBerolina:   /* might work if TestLegality is off */
1150       case VariantCapaRandom: /* should work */
1151       case VariantJanus:      /* should work */
1152       case VariantSuper:      /* experimental */
1153       case VariantGreat:      /* experimental, requires legality testing to be off */
1154       case VariantSChess:     /* S-Chess, should work */
1155       case VariantGrand:      /* should work */
1156       case VariantSpartan:    /* should work */
1157         break;
1158       }
1159     }
1160
1161 }
1162
1163 int
1164 NextIntegerFromString (char ** str, long * value)
1165 {
1166     int result = -1;
1167     char * s = *str;
1168
1169     while( *s == ' ' || *s == '\t' ) {
1170         s++;
1171     }
1172
1173     *value = 0;
1174
1175     if( *s >= '0' && *s <= '9' ) {
1176         while( *s >= '0' && *s <= '9' ) {
1177             *value = *value * 10 + (*s - '0');
1178             s++;
1179         }
1180
1181         result = 0;
1182     }
1183
1184     *str = s;
1185
1186     return result;
1187 }
1188
1189 int
1190 NextTimeControlFromString (char ** str, long * value)
1191 {
1192     long temp;
1193     int result = NextIntegerFromString( str, &temp );
1194
1195     if( result == 0 ) {
1196         *value = temp * 60; /* Minutes */
1197         if( **str == ':' ) {
1198             (*str)++;
1199             result = NextIntegerFromString( str, &temp );
1200             *value += temp; /* Seconds */
1201         }
1202     }
1203
1204     return result;
1205 }
1206
1207 int
1208 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1209 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1210     int result = -1, type = 0; long temp, temp2;
1211
1212     if(**str != ':') return -1; // old params remain in force!
1213     (*str)++;
1214     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1215     if( NextIntegerFromString( str, &temp ) ) return -1;
1216     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1217
1218     if(**str != '/') {
1219         /* time only: incremental or sudden-death time control */
1220         if(**str == '+') { /* increment follows; read it */
1221             (*str)++;
1222             if(**str == '!') type = *(*str)++; // Bronstein TC
1223             if(result = NextIntegerFromString( str, &temp2)) return -1;
1224             *inc = temp2 * 1000;
1225             if(**str == '.') { // read fraction of increment
1226                 char *start = ++(*str);
1227                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1228                 temp2 *= 1000;
1229                 while(start++ < *str) temp2 /= 10;
1230                 *inc += temp2;
1231             }
1232         } else *inc = 0;
1233         *moves = 0; *tc = temp * 1000; *incType = type;
1234         return 0;
1235     }
1236
1237     (*str)++; /* classical time control */
1238     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1239
1240     if(result == 0) {
1241         *moves = temp;
1242         *tc    = temp2 * 1000;
1243         *inc   = 0;
1244         *incType = type;
1245     }
1246     return result;
1247 }
1248
1249 int
1250 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1251 {   /* [HGM] get time to add from the multi-session time-control string */
1252     int incType, moves=1; /* kludge to force reading of first session */
1253     long time, increment;
1254     char *s = tcString;
1255
1256     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1257     do {
1258         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1259         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1260         if(movenr == -1) return time;    /* last move before new session     */
1261         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1262         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1263         if(!moves) return increment;     /* current session is incremental   */
1264         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1265     } while(movenr >= -1);               /* try again for next session       */
1266
1267     return 0; // no new time quota on this move
1268 }
1269
1270 int
1271 ParseTimeControl (char *tc, float ti, int mps)
1272 {
1273   long tc1;
1274   long tc2;
1275   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1276   int min, sec=0;
1277
1278   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1279   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1280       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1281   if(ti > 0) {
1282
1283     if(mps)
1284       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1285     else 
1286       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1287   } else {
1288     if(mps)
1289       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1290     else 
1291       snprintf(buf, MSG_SIZ, ":%s", mytc);
1292   }
1293   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1294   
1295   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1296     return FALSE;
1297   }
1298
1299   if( *tc == '/' ) {
1300     /* Parse second time control */
1301     tc++;
1302
1303     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1304       return FALSE;
1305     }
1306
1307     if( tc2 == 0 ) {
1308       return FALSE;
1309     }
1310
1311     timeControl_2 = tc2 * 1000;
1312   }
1313   else {
1314     timeControl_2 = 0;
1315   }
1316
1317   if( tc1 == 0 ) {
1318     return FALSE;
1319   }
1320
1321   timeControl = tc1 * 1000;
1322
1323   if (ti >= 0) {
1324     timeIncrement = ti * 1000;  /* convert to ms */
1325     movesPerSession = 0;
1326   } else {
1327     timeIncrement = 0;
1328     movesPerSession = mps;
1329   }
1330   return TRUE;
1331 }
1332
1333 void
1334 InitBackEnd2 ()
1335 {
1336     if (appData.debugMode) {
1337         fprintf(debugFP, "%s\n", programVersion);
1338     }
1339     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1340
1341     set_cont_sequence(appData.wrapContSeq);
1342     if (appData.matchGames > 0) {
1343         appData.matchMode = TRUE;
1344     } else if (appData.matchMode) {
1345         appData.matchGames = 1;
1346     }
1347     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1348         appData.matchGames = appData.sameColorGames;
1349     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1350         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1351         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1352     }
1353     Reset(TRUE, FALSE);
1354     if (appData.noChessProgram || first.protocolVersion == 1) {
1355       InitBackEnd3();
1356     } else {
1357       /* kludge: allow timeout for initial "feature" commands */
1358       FreezeUI();
1359       DisplayMessage("", _("Starting chess program"));
1360       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1361     }
1362 }
1363
1364 int
1365 CalculateIndex (int index, int gameNr)
1366 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1367     int res;
1368     if(index > 0) return index; // fixed nmber
1369     if(index == 0) return 1;
1370     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1371     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1372     return res;
1373 }
1374
1375 int
1376 LoadGameOrPosition (int gameNr)
1377 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1378     if (*appData.loadGameFile != NULLCHAR) {
1379         if (!LoadGameFromFile(appData.loadGameFile,
1380                 CalculateIndex(appData.loadGameIndex, gameNr),
1381                               appData.loadGameFile, FALSE)) {
1382             DisplayFatalError(_("Bad game file"), 0, 1);
1383             return 0;
1384         }
1385     } else if (*appData.loadPositionFile != NULLCHAR) {
1386         if (!LoadPositionFromFile(appData.loadPositionFile,
1387                 CalculateIndex(appData.loadPositionIndex, gameNr),
1388                                   appData.loadPositionFile)) {
1389             DisplayFatalError(_("Bad position file"), 0, 1);
1390             return 0;
1391         }
1392     }
1393     return 1;
1394 }
1395
1396 void
1397 ReserveGame (int gameNr, char resChar)
1398 {
1399     FILE *tf = fopen(appData.tourneyFile, "r+");
1400     char *p, *q, c, buf[MSG_SIZ];
1401     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1402     safeStrCpy(buf, lastMsg, MSG_SIZ);
1403     DisplayMessage(_("Pick new game"), "");
1404     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1405     ParseArgsFromFile(tf);
1406     p = q = appData.results;
1407     if(appData.debugMode) {
1408       char *r = appData.participants;
1409       fprintf(debugFP, "results = '%s'\n", p);
1410       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1411       fprintf(debugFP, "\n");
1412     }
1413     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1414     nextGame = q - p;
1415     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1416     safeStrCpy(q, p, strlen(p) + 2);
1417     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1418     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1419     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1420         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1421         q[nextGame] = '*';
1422     }
1423     fseek(tf, -(strlen(p)+4), SEEK_END);
1424     c = fgetc(tf);
1425     if(c != '"') // depending on DOS or Unix line endings we can be one off
1426          fseek(tf, -(strlen(p)+2), SEEK_END);
1427     else fseek(tf, -(strlen(p)+3), SEEK_END);
1428     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1429     DisplayMessage(buf, "");
1430     free(p); appData.results = q;
1431     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1432        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1433       int round = appData.defaultMatchGames * appData.tourneyType;
1434       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1435          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1436         UnloadEngine(&first);  // next game belongs to other pairing;
1437         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1438     }
1439     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1440 }
1441
1442 void
1443 MatchEvent (int mode)
1444 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1445         int dummy;
1446         if(matchMode) { // already in match mode: switch it off
1447             abortMatch = TRUE;
1448             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1449             return;
1450         }
1451 //      if(gameMode != BeginningOfGame) {
1452 //          DisplayError(_("You can only start a match from the initial position."), 0);
1453 //          return;
1454 //      }
1455         abortMatch = FALSE;
1456         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1457         /* Set up machine vs. machine match */
1458         nextGame = 0;
1459         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1460         if(appData.tourneyFile[0]) {
1461             ReserveGame(-1, 0);
1462             if(nextGame > appData.matchGames) {
1463                 char buf[MSG_SIZ];
1464                 if(strchr(appData.results, '*') == NULL) {
1465                     FILE *f;
1466                     appData.tourneyCycles++;
1467                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1468                         fclose(f);
1469                         NextTourneyGame(-1, &dummy);
1470                         ReserveGame(-1, 0);
1471                         if(nextGame <= appData.matchGames) {
1472                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1473                             matchMode = mode;
1474                             ScheduleDelayedEvent(NextMatchGame, 10000);
1475                             return;
1476                         }
1477                     }
1478                 }
1479                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1480                 DisplayError(buf, 0);
1481                 appData.tourneyFile[0] = 0;
1482                 return;
1483             }
1484         } else
1485         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1486             DisplayFatalError(_("Can't have a match with no chess programs"),
1487                               0, 2);
1488             return;
1489         }
1490         matchMode = mode;
1491         matchGame = roundNr = 1;
1492         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1493         NextMatchGame();
1494 }
1495
1496 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1497
1498 void
1499 InitBackEnd3 P((void))
1500 {
1501     GameMode initialMode;
1502     char buf[MSG_SIZ];
1503     int err, len;
1504
1505     InitChessProgram(&first, startedFromSetupPosition);
1506
1507     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1508         free(programVersion);
1509         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1510         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1511         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1512     }
1513
1514     if (appData.icsActive) {
1515 #ifdef WIN32
1516         /* [DM] Make a console window if needed [HGM] merged ifs */
1517         ConsoleCreate();
1518 #endif
1519         err = establish();
1520         if (err != 0)
1521           {
1522             if (*appData.icsCommPort != NULLCHAR)
1523               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1524                              appData.icsCommPort);
1525             else
1526               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1527                         appData.icsHost, appData.icsPort);
1528
1529             if( (len >= MSG_SIZ) && appData.debugMode )
1530               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1531
1532             DisplayFatalError(buf, err, 1);
1533             return;
1534         }
1535         SetICSMode();
1536         telnetISR =
1537           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1538         fromUserISR =
1539           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1540         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1541             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1542     } else if (appData.noChessProgram) {
1543         SetNCPMode();
1544     } else {
1545         SetGNUMode();
1546     }
1547
1548     if (*appData.cmailGameName != NULLCHAR) {
1549         SetCmailMode();
1550         OpenLoopback(&cmailPR);
1551         cmailISR =
1552           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1553     }
1554
1555     ThawUI();
1556     DisplayMessage("", "");
1557     if (StrCaseCmp(appData.initialMode, "") == 0) {
1558       initialMode = BeginningOfGame;
1559       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1560         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1561         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1562         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1563         ModeHighlight();
1564       }
1565     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1566       initialMode = TwoMachinesPlay;
1567     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1568       initialMode = AnalyzeFile;
1569     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1570       initialMode = AnalyzeMode;
1571     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1572       initialMode = MachinePlaysWhite;
1573     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1574       initialMode = MachinePlaysBlack;
1575     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1576       initialMode = EditGame;
1577     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1578       initialMode = EditPosition;
1579     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1580       initialMode = Training;
1581     } else {
1582       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1583       if( (len >= MSG_SIZ) && appData.debugMode )
1584         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1585
1586       DisplayFatalError(buf, 0, 2);
1587       return;
1588     }
1589
1590     if (appData.matchMode) {
1591         if(appData.tourneyFile[0]) { // start tourney from command line
1592             FILE *f;
1593             if(f = fopen(appData.tourneyFile, "r")) {
1594                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1595                 fclose(f);
1596                 appData.clockMode = TRUE;
1597                 SetGNUMode();
1598             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1599         }
1600         MatchEvent(TRUE);
1601     } else if (*appData.cmailGameName != NULLCHAR) {
1602         /* Set up cmail mode */
1603         ReloadCmailMsgEvent(TRUE);
1604     } else {
1605         /* Set up other modes */
1606         if (initialMode == AnalyzeFile) {
1607           if (*appData.loadGameFile == NULLCHAR) {
1608             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1609             return;
1610           }
1611         }
1612         if (*appData.loadGameFile != NULLCHAR) {
1613             (void) LoadGameFromFile(appData.loadGameFile,
1614                                     appData.loadGameIndex,
1615                                     appData.loadGameFile, TRUE);
1616         } else if (*appData.loadPositionFile != NULLCHAR) {
1617             (void) LoadPositionFromFile(appData.loadPositionFile,
1618                                         appData.loadPositionIndex,
1619                                         appData.loadPositionFile);
1620             /* [HGM] try to make self-starting even after FEN load */
1621             /* to allow automatic setup of fairy variants with wtm */
1622             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1623                 gameMode = BeginningOfGame;
1624                 setboardSpoiledMachineBlack = 1;
1625             }
1626             /* [HGM] loadPos: make that every new game uses the setup */
1627             /* from file as long as we do not switch variant          */
1628             if(!blackPlaysFirst) {
1629                 startedFromPositionFile = TRUE;
1630                 CopyBoard(filePosition, boards[0]);
1631             }
1632         }
1633         if (initialMode == AnalyzeMode) {
1634           if (appData.noChessProgram) {
1635             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1636             return;
1637           }
1638           if (appData.icsActive) {
1639             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1640             return;
1641           }
1642           AnalyzeModeEvent();
1643         } else if (initialMode == AnalyzeFile) {
1644           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1645           ShowThinkingEvent();
1646           AnalyzeFileEvent();
1647           AnalysisPeriodicEvent(1);
1648         } else if (initialMode == MachinePlaysWhite) {
1649           if (appData.noChessProgram) {
1650             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1651                               0, 2);
1652             return;
1653           }
1654           if (appData.icsActive) {
1655             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1656                               0, 2);
1657             return;
1658           }
1659           MachineWhiteEvent();
1660         } else if (initialMode == MachinePlaysBlack) {
1661           if (appData.noChessProgram) {
1662             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1663                               0, 2);
1664             return;
1665           }
1666           if (appData.icsActive) {
1667             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1668                               0, 2);
1669             return;
1670           }
1671           MachineBlackEvent();
1672         } else if (initialMode == TwoMachinesPlay) {
1673           if (appData.noChessProgram) {
1674             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1675                               0, 2);
1676             return;
1677           }
1678           if (appData.icsActive) {
1679             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1680                               0, 2);
1681             return;
1682           }
1683           TwoMachinesEvent();
1684         } else if (initialMode == EditGame) {
1685           EditGameEvent();
1686         } else if (initialMode == EditPosition) {
1687           EditPositionEvent();
1688         } else if (initialMode == Training) {
1689           if (*appData.loadGameFile == NULLCHAR) {
1690             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1691             return;
1692           }
1693           TrainingEvent();
1694         }
1695     }
1696 }
1697
1698 void
1699 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1700 {
1701     DisplayBook(current+1);
1702
1703     MoveHistorySet( movelist, first, last, current, pvInfoList );
1704
1705     EvalGraphSet( first, last, current, pvInfoList );
1706
1707     MakeEngineOutputTitle();
1708 }
1709
1710 /*
1711  * Establish will establish a contact to a remote host.port.
1712  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1713  *  used to talk to the host.
1714  * Returns 0 if okay, error code if not.
1715  */
1716 int
1717 establish ()
1718 {
1719     char buf[MSG_SIZ];
1720
1721     if (*appData.icsCommPort != NULLCHAR) {
1722         /* Talk to the host through a serial comm port */
1723         return OpenCommPort(appData.icsCommPort, &icsPR);
1724
1725     } else if (*appData.gateway != NULLCHAR) {
1726         if (*appData.remoteShell == NULLCHAR) {
1727             /* Use the rcmd protocol to run telnet program on a gateway host */
1728             snprintf(buf, sizeof(buf), "%s %s %s",
1729                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1730             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1731
1732         } else {
1733             /* Use the rsh program to run telnet program on a gateway host */
1734             if (*appData.remoteUser == NULLCHAR) {
1735                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1736                         appData.gateway, appData.telnetProgram,
1737                         appData.icsHost, appData.icsPort);
1738             } else {
1739                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1740                         appData.remoteShell, appData.gateway,
1741                         appData.remoteUser, appData.telnetProgram,
1742                         appData.icsHost, appData.icsPort);
1743             }
1744             return StartChildProcess(buf, "", &icsPR);
1745
1746         }
1747     } else if (appData.useTelnet) {
1748         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1749
1750     } else {
1751         /* TCP socket interface differs somewhat between
1752            Unix and NT; handle details in the front end.
1753            */
1754         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1755     }
1756 }
1757
1758 void
1759 EscapeExpand (char *p, char *q)
1760 {       // [HGM] initstring: routine to shape up string arguments
1761         while(*p++ = *q++) if(p[-1] == '\\')
1762             switch(*q++) {
1763                 case 'n': p[-1] = '\n'; break;
1764                 case 'r': p[-1] = '\r'; break;
1765                 case 't': p[-1] = '\t'; break;
1766                 case '\\': p[-1] = '\\'; break;
1767                 case 0: *p = 0; return;
1768                 default: p[-1] = q[-1]; break;
1769             }
1770 }
1771
1772 void
1773 show_bytes (FILE *fp, char *buf, int count)
1774 {
1775     while (count--) {
1776         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1777             fprintf(fp, "\\%03o", *buf & 0xff);
1778         } else {
1779             putc(*buf, fp);
1780         }
1781         buf++;
1782     }
1783     fflush(fp);
1784 }
1785
1786 /* Returns an errno value */
1787 int
1788 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1789 {
1790     char buf[8192], *p, *q, *buflim;
1791     int left, newcount, outcount;
1792
1793     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1794         *appData.gateway != NULLCHAR) {
1795         if (appData.debugMode) {
1796             fprintf(debugFP, ">ICS: ");
1797             show_bytes(debugFP, message, count);
1798             fprintf(debugFP, "\n");
1799         }
1800         return OutputToProcess(pr, message, count, outError);
1801     }
1802
1803     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1804     p = message;
1805     q = buf;
1806     left = count;
1807     newcount = 0;
1808     while (left) {
1809         if (q >= buflim) {
1810             if (appData.debugMode) {
1811                 fprintf(debugFP, ">ICS: ");
1812                 show_bytes(debugFP, buf, newcount);
1813                 fprintf(debugFP, "\n");
1814             }
1815             outcount = OutputToProcess(pr, buf, newcount, outError);
1816             if (outcount < newcount) return -1; /* to be sure */
1817             q = buf;
1818             newcount = 0;
1819         }
1820         if (*p == '\n') {
1821             *q++ = '\r';
1822             newcount++;
1823         } else if (((unsigned char) *p) == TN_IAC) {
1824             *q++ = (char) TN_IAC;
1825             newcount ++;
1826         }
1827         *q++ = *p++;
1828         newcount++;
1829         left--;
1830     }
1831     if (appData.debugMode) {
1832         fprintf(debugFP, ">ICS: ");
1833         show_bytes(debugFP, buf, newcount);
1834         fprintf(debugFP, "\n");
1835     }
1836     outcount = OutputToProcess(pr, buf, newcount, outError);
1837     if (outcount < newcount) return -1; /* to be sure */
1838     return count;
1839 }
1840
1841 void
1842 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1843 {
1844     int outError, outCount;
1845     static int gotEof = 0;
1846
1847     /* Pass data read from player on to ICS */
1848     if (count > 0) {
1849         gotEof = 0;
1850         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1851         if (outCount < count) {
1852             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1853         }
1854     } else if (count < 0) {
1855         RemoveInputSource(isr);
1856         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1857     } else if (gotEof++ > 0) {
1858         RemoveInputSource(isr);
1859         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1860     }
1861 }
1862
1863 void
1864 KeepAlive ()
1865 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1866     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1867     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1868     SendToICS("date\n");
1869     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1870 }
1871
1872 /* added routine for printf style output to ics */
1873 void
1874 ics_printf (char *format, ...)
1875 {
1876     char buffer[MSG_SIZ];
1877     va_list args;
1878
1879     va_start(args, format);
1880     vsnprintf(buffer, sizeof(buffer), format, args);
1881     buffer[sizeof(buffer)-1] = '\0';
1882     SendToICS(buffer);
1883     va_end(args);
1884 }
1885
1886 void
1887 SendToICS (char *s)
1888 {
1889     int count, outCount, outError;
1890
1891     if (icsPR == NoProc) return;
1892
1893     count = strlen(s);
1894     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1895     if (outCount < count) {
1896         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1897     }
1898 }
1899
1900 /* This is used for sending logon scripts to the ICS. Sending
1901    without a delay causes problems when using timestamp on ICC
1902    (at least on my machine). */
1903 void
1904 SendToICSDelayed (char *s, long msdelay)
1905 {
1906     int count, outCount, outError;
1907
1908     if (icsPR == NoProc) return;
1909
1910     count = strlen(s);
1911     if (appData.debugMode) {
1912         fprintf(debugFP, ">ICS: ");
1913         show_bytes(debugFP, s, count);
1914         fprintf(debugFP, "\n");
1915     }
1916     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1917                                       msdelay);
1918     if (outCount < count) {
1919         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1920     }
1921 }
1922
1923
1924 /* Remove all highlighting escape sequences in s
1925    Also deletes any suffix starting with '('
1926    */
1927 char *
1928 StripHighlightAndTitle (char *s)
1929 {
1930     static char retbuf[MSG_SIZ];
1931     char *p = retbuf;
1932
1933     while (*s != NULLCHAR) {
1934         while (*s == '\033') {
1935             while (*s != NULLCHAR && !isalpha(*s)) s++;
1936             if (*s != NULLCHAR) s++;
1937         }
1938         while (*s != NULLCHAR && *s != '\033') {
1939             if (*s == '(' || *s == '[') {
1940                 *p = NULLCHAR;
1941                 return retbuf;
1942             }
1943             *p++ = *s++;
1944         }
1945     }
1946     *p = NULLCHAR;
1947     return retbuf;
1948 }
1949
1950 /* Remove all highlighting escape sequences in s */
1951 char *
1952 StripHighlight (char *s)
1953 {
1954     static char retbuf[MSG_SIZ];
1955     char *p = retbuf;
1956
1957     while (*s != NULLCHAR) {
1958         while (*s == '\033') {
1959             while (*s != NULLCHAR && !isalpha(*s)) s++;
1960             if (*s != NULLCHAR) s++;
1961         }
1962         while (*s != NULLCHAR && *s != '\033') {
1963             *p++ = *s++;
1964         }
1965     }
1966     *p = NULLCHAR;
1967     return retbuf;
1968 }
1969
1970 char *variantNames[] = VARIANT_NAMES;
1971 char *
1972 VariantName (VariantClass v)
1973 {
1974     return variantNames[v];
1975 }
1976
1977
1978 /* Identify a variant from the strings the chess servers use or the
1979    PGN Variant tag names we use. */
1980 VariantClass
1981 StringToVariant (char *e)
1982 {
1983     char *p;
1984     int wnum = -1;
1985     VariantClass v = VariantNormal;
1986     int i, found = FALSE;
1987     char buf[MSG_SIZ];
1988     int len;
1989
1990     if (!e) return v;
1991
1992     /* [HGM] skip over optional board-size prefixes */
1993     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1994         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1995         while( *e++ != '_');
1996     }
1997
1998     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1999         v = VariantNormal;
2000         found = TRUE;
2001     } else
2002     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2003       if (StrCaseStr(e, variantNames[i])) {
2004         v = (VariantClass) i;
2005         found = TRUE;
2006         break;
2007       }
2008     }
2009
2010     if (!found) {
2011       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2012           || StrCaseStr(e, "wild/fr")
2013           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2014         v = VariantFischeRandom;
2015       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2016                  (i = 1, p = StrCaseStr(e, "w"))) {
2017         p += i;
2018         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2019         if (isdigit(*p)) {
2020           wnum = atoi(p);
2021         } else {
2022           wnum = -1;
2023         }
2024         switch (wnum) {
2025         case 0: /* FICS only, actually */
2026         case 1:
2027           /* Castling legal even if K starts on d-file */
2028           v = VariantWildCastle;
2029           break;
2030         case 2:
2031         case 3:
2032         case 4:
2033           /* Castling illegal even if K & R happen to start in
2034              normal positions. */
2035           v = VariantNoCastle;
2036           break;
2037         case 5:
2038         case 7:
2039         case 8:
2040         case 10:
2041         case 11:
2042         case 12:
2043         case 13:
2044         case 14:
2045         case 15:
2046         case 18:
2047         case 19:
2048           /* Castling legal iff K & R start in normal positions */
2049           v = VariantNormal;
2050           break;
2051         case 6:
2052         case 20:
2053         case 21:
2054           /* Special wilds for position setup; unclear what to do here */
2055           v = VariantLoadable;
2056           break;
2057         case 9:
2058           /* Bizarre ICC game */
2059           v = VariantTwoKings;
2060           break;
2061         case 16:
2062           v = VariantKriegspiel;
2063           break;
2064         case 17:
2065           v = VariantLosers;
2066           break;
2067         case 22:
2068           v = VariantFischeRandom;
2069           break;
2070         case 23:
2071           v = VariantCrazyhouse;
2072           break;
2073         case 24:
2074           v = VariantBughouse;
2075           break;
2076         case 25:
2077           v = Variant3Check;
2078           break;
2079         case 26:
2080           /* Not quite the same as FICS suicide! */
2081           v = VariantGiveaway;
2082           break;
2083         case 27:
2084           v = VariantAtomic;
2085           break;
2086         case 28:
2087           v = VariantShatranj;
2088           break;
2089
2090         /* Temporary names for future ICC types.  The name *will* change in
2091            the next xboard/WinBoard release after ICC defines it. */
2092         case 29:
2093           v = Variant29;
2094           break;
2095         case 30:
2096           v = Variant30;
2097           break;
2098         case 31:
2099           v = Variant31;
2100           break;
2101         case 32:
2102           v = Variant32;
2103           break;
2104         case 33:
2105           v = Variant33;
2106           break;
2107         case 34:
2108           v = Variant34;
2109           break;
2110         case 35:
2111           v = Variant35;
2112           break;
2113         case 36:
2114           v = Variant36;
2115           break;
2116         case 37:
2117           v = VariantShogi;
2118           break;
2119         case 38:
2120           v = VariantXiangqi;
2121           break;
2122         case 39:
2123           v = VariantCourier;
2124           break;
2125         case 40:
2126           v = VariantGothic;
2127           break;
2128         case 41:
2129           v = VariantCapablanca;
2130           break;
2131         case 42:
2132           v = VariantKnightmate;
2133           break;
2134         case 43:
2135           v = VariantFairy;
2136           break;
2137         case 44:
2138           v = VariantCylinder;
2139           break;
2140         case 45:
2141           v = VariantFalcon;
2142           break;
2143         case 46:
2144           v = VariantCapaRandom;
2145           break;
2146         case 47:
2147           v = VariantBerolina;
2148           break;
2149         case 48:
2150           v = VariantJanus;
2151           break;
2152         case 49:
2153           v = VariantSuper;
2154           break;
2155         case 50:
2156           v = VariantGreat;
2157           break;
2158         case -1:
2159           /* Found "wild" or "w" in the string but no number;
2160              must assume it's normal chess. */
2161           v = VariantNormal;
2162           break;
2163         default:
2164           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2165           if( (len >= MSG_SIZ) && appData.debugMode )
2166             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2167
2168           DisplayError(buf, 0);
2169           v = VariantUnknown;
2170           break;
2171         }
2172       }
2173     }
2174     if (appData.debugMode) {
2175       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2176               e, wnum, VariantName(v));
2177     }
2178     return v;
2179 }
2180
2181 static int leftover_start = 0, leftover_len = 0;
2182 char star_match[STAR_MATCH_N][MSG_SIZ];
2183
2184 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2185    advance *index beyond it, and set leftover_start to the new value of
2186    *index; else return FALSE.  If pattern contains the character '*', it
2187    matches any sequence of characters not containing '\r', '\n', or the
2188    character following the '*' (if any), and the matched sequence(s) are
2189    copied into star_match.
2190    */
2191 int
2192 looking_at ( char *buf, int *index, char *pattern)
2193 {
2194     char *bufp = &buf[*index], *patternp = pattern;
2195     int star_count = 0;
2196     char *matchp = star_match[0];
2197
2198     for (;;) {
2199         if (*patternp == NULLCHAR) {
2200             *index = leftover_start = bufp - buf;
2201             *matchp = NULLCHAR;
2202             return TRUE;
2203         }
2204         if (*bufp == NULLCHAR) return FALSE;
2205         if (*patternp == '*') {
2206             if (*bufp == *(patternp + 1)) {
2207                 *matchp = NULLCHAR;
2208                 matchp = star_match[++star_count];
2209                 patternp += 2;
2210                 bufp++;
2211                 continue;
2212             } else if (*bufp == '\n' || *bufp == '\r') {
2213                 patternp++;
2214                 if (*patternp == NULLCHAR)
2215                   continue;
2216                 else
2217                   return FALSE;
2218             } else {
2219                 *matchp++ = *bufp++;
2220                 continue;
2221             }
2222         }
2223         if (*patternp != *bufp) return FALSE;
2224         patternp++;
2225         bufp++;
2226     }
2227 }
2228
2229 void
2230 SendToPlayer (char *data, int length)
2231 {
2232     int error, outCount;
2233     outCount = OutputToProcess(NoProc, data, length, &error);
2234     if (outCount < length) {
2235         DisplayFatalError(_("Error writing to display"), error, 1);
2236     }
2237 }
2238
2239 void
2240 PackHolding (char packed[], char *holding)
2241 {
2242     char *p = holding;
2243     char *q = packed;
2244     int runlength = 0;
2245     int curr = 9999;
2246     do {
2247         if (*p == curr) {
2248             runlength++;
2249         } else {
2250             switch (runlength) {
2251               case 0:
2252                 break;
2253               case 1:
2254                 *q++ = curr;
2255                 break;
2256               case 2:
2257                 *q++ = curr;
2258                 *q++ = curr;
2259                 break;
2260               default:
2261                 sprintf(q, "%d", runlength);
2262                 while (*q) q++;
2263                 *q++ = curr;
2264                 break;
2265             }
2266             runlength = 1;
2267             curr = *p;
2268         }
2269     } while (*p++);
2270     *q = NULLCHAR;
2271 }
2272
2273 /* Telnet protocol requests from the front end */
2274 void
2275 TelnetRequest (unsigned char ddww, unsigned char option)
2276 {
2277     unsigned char msg[3];
2278     int outCount, outError;
2279
2280     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2281
2282     if (appData.debugMode) {
2283         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2284         switch (ddww) {
2285           case TN_DO:
2286             ddwwStr = "DO";
2287             break;
2288           case TN_DONT:
2289             ddwwStr = "DONT";
2290             break;
2291           case TN_WILL:
2292             ddwwStr = "WILL";
2293             break;
2294           case TN_WONT:
2295             ddwwStr = "WONT";
2296             break;
2297           default:
2298             ddwwStr = buf1;
2299             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2300             break;
2301         }
2302         switch (option) {
2303           case TN_ECHO:
2304             optionStr = "ECHO";
2305             break;
2306           default:
2307             optionStr = buf2;
2308             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2309             break;
2310         }
2311         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2312     }
2313     msg[0] = TN_IAC;
2314     msg[1] = ddww;
2315     msg[2] = option;
2316     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2317     if (outCount < 3) {
2318         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2319     }
2320 }
2321
2322 void
2323 DoEcho ()
2324 {
2325     if (!appData.icsActive) return;
2326     TelnetRequest(TN_DO, TN_ECHO);
2327 }
2328
2329 void
2330 DontEcho ()
2331 {
2332     if (!appData.icsActive) return;
2333     TelnetRequest(TN_DONT, TN_ECHO);
2334 }
2335
2336 void
2337 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2338 {
2339     /* put the holdings sent to us by the server on the board holdings area */
2340     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2341     char p;
2342     ChessSquare piece;
2343
2344     if(gameInfo.holdingsWidth < 2)  return;
2345     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2346         return; // prevent overwriting by pre-board holdings
2347
2348     if( (int)lowestPiece >= BlackPawn ) {
2349         holdingsColumn = 0;
2350         countsColumn = 1;
2351         holdingsStartRow = BOARD_HEIGHT-1;
2352         direction = -1;
2353     } else {
2354         holdingsColumn = BOARD_WIDTH-1;
2355         countsColumn = BOARD_WIDTH-2;
2356         holdingsStartRow = 0;
2357         direction = 1;
2358     }
2359
2360     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2361         board[i][holdingsColumn] = EmptySquare;
2362         board[i][countsColumn]   = (ChessSquare) 0;
2363     }
2364     while( (p=*holdings++) != NULLCHAR ) {
2365         piece = CharToPiece( ToUpper(p) );
2366         if(piece == EmptySquare) continue;
2367         /*j = (int) piece - (int) WhitePawn;*/
2368         j = PieceToNumber(piece);
2369         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2370         if(j < 0) continue;               /* should not happen */
2371         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2372         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2373         board[holdingsStartRow+j*direction][countsColumn]++;
2374     }
2375 }
2376
2377
2378 void
2379 VariantSwitch (Board board, VariantClass newVariant)
2380 {
2381    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2382    static Board oldBoard;
2383
2384    startedFromPositionFile = FALSE;
2385    if(gameInfo.variant == newVariant) return;
2386
2387    /* [HGM] This routine is called each time an assignment is made to
2388     * gameInfo.variant during a game, to make sure the board sizes
2389     * are set to match the new variant. If that means adding or deleting
2390     * holdings, we shift the playing board accordingly
2391     * This kludge is needed because in ICS observe mode, we get boards
2392     * of an ongoing game without knowing the variant, and learn about the
2393     * latter only later. This can be because of the move list we requested,
2394     * in which case the game history is refilled from the beginning anyway,
2395     * but also when receiving holdings of a crazyhouse game. In the latter
2396     * case we want to add those holdings to the already received position.
2397     */
2398
2399
2400    if (appData.debugMode) {
2401      fprintf(debugFP, "Switch board from %s to %s\n",
2402              VariantName(gameInfo.variant), VariantName(newVariant));
2403      setbuf(debugFP, NULL);
2404    }
2405    shuffleOpenings = 0;       /* [HGM] shuffle */
2406    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2407    switch(newVariant)
2408      {
2409      case VariantShogi:
2410        newWidth = 9;  newHeight = 9;
2411        gameInfo.holdingsSize = 7;
2412      case VariantBughouse:
2413      case VariantCrazyhouse:
2414        newHoldingsWidth = 2; break;
2415      case VariantGreat:
2416        newWidth = 10;
2417      case VariantSuper:
2418        newHoldingsWidth = 2;
2419        gameInfo.holdingsSize = 8;
2420        break;
2421      case VariantGothic:
2422      case VariantCapablanca:
2423      case VariantCapaRandom:
2424        newWidth = 10;
2425      default:
2426        newHoldingsWidth = gameInfo.holdingsSize = 0;
2427      };
2428
2429    if(newWidth  != gameInfo.boardWidth  ||
2430       newHeight != gameInfo.boardHeight ||
2431       newHoldingsWidth != gameInfo.holdingsWidth ) {
2432
2433      /* shift position to new playing area, if needed */
2434      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2435        for(i=0; i<BOARD_HEIGHT; i++)
2436          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2437            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2438              board[i][j];
2439        for(i=0; i<newHeight; i++) {
2440          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2441          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2442        }
2443      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2444        for(i=0; i<BOARD_HEIGHT; i++)
2445          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2446            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2447              board[i][j];
2448      }
2449      gameInfo.boardWidth  = newWidth;
2450      gameInfo.boardHeight = newHeight;
2451      gameInfo.holdingsWidth = newHoldingsWidth;
2452      gameInfo.variant = newVariant;
2453      InitDrawingSizes(-2, 0);
2454    } else gameInfo.variant = newVariant;
2455    CopyBoard(oldBoard, board);   // remember correctly formatted board
2456      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2457    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2458 }
2459
2460 static int loggedOn = FALSE;
2461
2462 /*-- Game start info cache: --*/
2463 int gs_gamenum;
2464 char gs_kind[MSG_SIZ];
2465 static char player1Name[128] = "";
2466 static char player2Name[128] = "";
2467 static char cont_seq[] = "\n\\   ";
2468 static int player1Rating = -1;
2469 static int player2Rating = -1;
2470 /*----------------------------*/
2471
2472 ColorClass curColor = ColorNormal;
2473 int suppressKibitz = 0;
2474
2475 // [HGM] seekgraph
2476 Boolean soughtPending = FALSE;
2477 Boolean seekGraphUp;
2478 #define MAX_SEEK_ADS 200
2479 #define SQUARE 0x80
2480 char *seekAdList[MAX_SEEK_ADS];
2481 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2482 float tcList[MAX_SEEK_ADS];
2483 char colorList[MAX_SEEK_ADS];
2484 int nrOfSeekAds = 0;
2485 int minRating = 1010, maxRating = 2800;
2486 int hMargin = 10, vMargin = 20, h, w;
2487 extern int squareSize, lineGap;
2488
2489 void
2490 PlotSeekAd (int i)
2491 {
2492         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2493         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2494         if(r < minRating+100 && r >=0 ) r = minRating+100;
2495         if(r > maxRating) r = maxRating;
2496         if(tc < 1.) tc = 1.;
2497         if(tc > 95.) tc = 95.;
2498         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2499         y = ((double)r - minRating)/(maxRating - minRating)
2500             * (h-vMargin-squareSize/8-1) + vMargin;
2501         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2502         if(strstr(seekAdList[i], " u ")) color = 1;
2503         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2504            !strstr(seekAdList[i], "bullet") &&
2505            !strstr(seekAdList[i], "blitz") &&
2506            !strstr(seekAdList[i], "standard") ) color = 2;
2507         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2508         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2509 }
2510
2511 void
2512 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2513 {
2514         char buf[MSG_SIZ], *ext = "";
2515         VariantClass v = StringToVariant(type);
2516         if(strstr(type, "wild")) {
2517             ext = type + 4; // append wild number
2518             if(v == VariantFischeRandom) type = "chess960"; else
2519             if(v == VariantLoadable) type = "setup"; else
2520             type = VariantName(v);
2521         }
2522         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2523         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2524             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2525             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2526             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2527             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2528             seekNrList[nrOfSeekAds] = nr;
2529             zList[nrOfSeekAds] = 0;
2530             seekAdList[nrOfSeekAds++] = StrSave(buf);
2531             if(plot) PlotSeekAd(nrOfSeekAds-1);
2532         }
2533 }
2534
2535 void
2536 EraseSeekDot (int i)
2537 {
2538     int x = xList[i], y = yList[i], d=squareSize/4, k;
2539     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2540     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2541     // now replot every dot that overlapped
2542     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2543         int xx = xList[k], yy = yList[k];
2544         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2545             DrawSeekDot(xx, yy, colorList[k]);
2546     }
2547 }
2548
2549 void
2550 RemoveSeekAd (int nr)
2551 {
2552         int i;
2553         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2554             EraseSeekDot(i);
2555             if(seekAdList[i]) free(seekAdList[i]);
2556             seekAdList[i] = seekAdList[--nrOfSeekAds];
2557             seekNrList[i] = seekNrList[nrOfSeekAds];
2558             ratingList[i] = ratingList[nrOfSeekAds];
2559             colorList[i]  = colorList[nrOfSeekAds];
2560             tcList[i] = tcList[nrOfSeekAds];
2561             xList[i]  = xList[nrOfSeekAds];
2562             yList[i]  = yList[nrOfSeekAds];
2563             zList[i]  = zList[nrOfSeekAds];
2564             seekAdList[nrOfSeekAds] = NULL;
2565             break;
2566         }
2567 }
2568
2569 Boolean
2570 MatchSoughtLine (char *line)
2571 {
2572     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2573     int nr, base, inc, u=0; char dummy;
2574
2575     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2576        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2577        (u=1) &&
2578        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2579         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2580         // match: compact and save the line
2581         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2582         return TRUE;
2583     }
2584     return FALSE;
2585 }
2586
2587 int
2588 DrawSeekGraph ()
2589 {
2590     int i;
2591     if(!seekGraphUp) return FALSE;
2592     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2593     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2594
2595     DrawSeekBackground(0, 0, w, h);
2596     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2597     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2598     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2599         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2600         yy = h-1-yy;
2601         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2602         if(i%500 == 0) {
2603             char buf[MSG_SIZ];
2604             snprintf(buf, MSG_SIZ, "%d", i);
2605             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2606         }
2607     }
2608     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2609     for(i=1; i<100; i+=(i<10?1:5)) {
2610         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2611         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2612         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2613             char buf[MSG_SIZ];
2614             snprintf(buf, MSG_SIZ, "%d", i);
2615             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2616         }
2617     }
2618     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2619     return TRUE;
2620 }
2621
2622 int
2623 SeekGraphClick (ClickType click, int x, int y, int moving)
2624 {
2625     static int lastDown = 0, displayed = 0, lastSecond;
2626     if(y < 0) return FALSE;
2627     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2628         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2629         if(!seekGraphUp) return FALSE;
2630         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2631         DrawPosition(TRUE, NULL);
2632         return TRUE;
2633     }
2634     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2635         if(click == Release || moving) return FALSE;
2636         nrOfSeekAds = 0;
2637         soughtPending = TRUE;
2638         SendToICS(ics_prefix);
2639         SendToICS("sought\n"); // should this be "sought all"?
2640     } else { // issue challenge based on clicked ad
2641         int dist = 10000; int i, closest = 0, second = 0;
2642         for(i=0; i<nrOfSeekAds; i++) {
2643             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2644             if(d < dist) { dist = d; closest = i; }
2645             second += (d - zList[i] < 120); // count in-range ads
2646             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2647         }
2648         if(dist < 120) {
2649             char buf[MSG_SIZ];
2650             second = (second > 1);
2651             if(displayed != closest || second != lastSecond) {
2652                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2653                 lastSecond = second; displayed = closest;
2654             }
2655             if(click == Press) {
2656                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2657                 lastDown = closest;
2658                 return TRUE;
2659             } // on press 'hit', only show info
2660             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2661             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2662             SendToICS(ics_prefix);
2663             SendToICS(buf);
2664             return TRUE; // let incoming board of started game pop down the graph
2665         } else if(click == Release) { // release 'miss' is ignored
2666             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2667             if(moving == 2) { // right up-click
2668                 nrOfSeekAds = 0; // refresh graph
2669                 soughtPending = TRUE;
2670                 SendToICS(ics_prefix);
2671                 SendToICS("sought\n"); // should this be "sought all"?
2672             }
2673             return TRUE;
2674         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2675         // press miss or release hit 'pop down' seek graph
2676         seekGraphUp = FALSE;
2677         DrawPosition(TRUE, NULL);
2678     }
2679     return TRUE;
2680 }
2681
2682 void
2683 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2684 {
2685 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2686 #define STARTED_NONE 0
2687 #define STARTED_MOVES 1
2688 #define STARTED_BOARD 2
2689 #define STARTED_OBSERVE 3
2690 #define STARTED_HOLDINGS 4
2691 #define STARTED_CHATTER 5
2692 #define STARTED_COMMENT 6
2693 #define STARTED_MOVES_NOHIDE 7
2694
2695     static int started = STARTED_NONE;
2696     static char parse[20000];
2697     static int parse_pos = 0;
2698     static char buf[BUF_SIZE + 1];
2699     static int firstTime = TRUE, intfSet = FALSE;
2700     static ColorClass prevColor = ColorNormal;
2701     static int savingComment = FALSE;
2702     static int cmatch = 0; // continuation sequence match
2703     char *bp;
2704     char str[MSG_SIZ];
2705     int i, oldi;
2706     int buf_len;
2707     int next_out;
2708     int tkind;
2709     int backup;    /* [DM] For zippy color lines */
2710     char *p;
2711     char talker[MSG_SIZ]; // [HGM] chat
2712     int channel;
2713
2714     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2715
2716     if (appData.debugMode) {
2717       if (!error) {
2718         fprintf(debugFP, "<ICS: ");
2719         show_bytes(debugFP, data, count);
2720         fprintf(debugFP, "\n");
2721       }
2722     }
2723
2724     if (appData.debugMode) { int f = forwardMostMove;
2725         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2726                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2727                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2728     }
2729     if (count > 0) {
2730         /* If last read ended with a partial line that we couldn't parse,
2731            prepend it to the new read and try again. */
2732         if (leftover_len > 0) {
2733             for (i=0; i<leftover_len; i++)
2734               buf[i] = buf[leftover_start + i];
2735         }
2736
2737     /* copy new characters into the buffer */
2738     bp = buf + leftover_len;
2739     buf_len=leftover_len;
2740     for (i=0; i<count; i++)
2741     {
2742         // ignore these
2743         if (data[i] == '\r')
2744             continue;
2745
2746         // join lines split by ICS?
2747         if (!appData.noJoin)
2748         {
2749             /*
2750                 Joining just consists of finding matches against the
2751                 continuation sequence, and discarding that sequence
2752                 if found instead of copying it.  So, until a match
2753                 fails, there's nothing to do since it might be the
2754                 complete sequence, and thus, something we don't want
2755                 copied.
2756             */
2757             if (data[i] == cont_seq[cmatch])
2758             {
2759                 cmatch++;
2760                 if (cmatch == strlen(cont_seq))
2761                 {
2762                     cmatch = 0; // complete match.  just reset the counter
2763
2764                     /*
2765                         it's possible for the ICS to not include the space
2766                         at the end of the last word, making our [correct]
2767                         join operation fuse two separate words.  the server
2768                         does this when the space occurs at the width setting.
2769                     */
2770                     if (!buf_len || buf[buf_len-1] != ' ')
2771                     {
2772                         *bp++ = ' ';
2773                         buf_len++;
2774                     }
2775                 }
2776                 continue;
2777             }
2778             else if (cmatch)
2779             {
2780                 /*
2781                     match failed, so we have to copy what matched before
2782                     falling through and copying this character.  In reality,
2783                     this will only ever be just the newline character, but
2784                     it doesn't hurt to be precise.
2785                 */
2786                 strncpy(bp, cont_seq, cmatch);
2787                 bp += cmatch;
2788                 buf_len += cmatch;
2789                 cmatch = 0;
2790             }
2791         }
2792
2793         // copy this char
2794         *bp++ = data[i];
2795         buf_len++;
2796     }
2797
2798         buf[buf_len] = NULLCHAR;
2799 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2800         next_out = 0;
2801         leftover_start = 0;
2802
2803         i = 0;
2804         while (i < buf_len) {
2805             /* Deal with part of the TELNET option negotiation
2806                protocol.  We refuse to do anything beyond the
2807                defaults, except that we allow the WILL ECHO option,
2808                which ICS uses to turn off password echoing when we are
2809                directly connected to it.  We reject this option
2810                if localLineEditing mode is on (always on in xboard)
2811                and we are talking to port 23, which might be a real
2812                telnet server that will try to keep WILL ECHO on permanently.
2813              */
2814             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2815                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2816                 unsigned char option;
2817                 oldi = i;
2818                 switch ((unsigned char) buf[++i]) {
2819                   case TN_WILL:
2820                     if (appData.debugMode)
2821                       fprintf(debugFP, "\n<WILL ");
2822                     switch (option = (unsigned char) buf[++i]) {
2823                       case TN_ECHO:
2824                         if (appData.debugMode)
2825                           fprintf(debugFP, "ECHO ");
2826                         /* Reply only if this is a change, according
2827                            to the protocol rules. */
2828                         if (remoteEchoOption) break;
2829                         if (appData.localLineEditing &&
2830                             atoi(appData.icsPort) == TN_PORT) {
2831                             TelnetRequest(TN_DONT, TN_ECHO);
2832                         } else {
2833                             EchoOff();
2834                             TelnetRequest(TN_DO, TN_ECHO);
2835                             remoteEchoOption = TRUE;
2836                         }
2837                         break;
2838                       default:
2839                         if (appData.debugMode)
2840                           fprintf(debugFP, "%d ", option);
2841                         /* Whatever this is, we don't want it. */
2842                         TelnetRequest(TN_DONT, option);
2843                         break;
2844                     }
2845                     break;
2846                   case TN_WONT:
2847                     if (appData.debugMode)
2848                       fprintf(debugFP, "\n<WONT ");
2849                     switch (option = (unsigned char) buf[++i]) {
2850                       case TN_ECHO:
2851                         if (appData.debugMode)
2852                           fprintf(debugFP, "ECHO ");
2853                         /* Reply only if this is a change, according
2854                            to the protocol rules. */
2855                         if (!remoteEchoOption) break;
2856                         EchoOn();
2857                         TelnetRequest(TN_DONT, TN_ECHO);
2858                         remoteEchoOption = FALSE;
2859                         break;
2860                       default:
2861                         if (appData.debugMode)
2862                           fprintf(debugFP, "%d ", (unsigned char) option);
2863                         /* Whatever this is, it must already be turned
2864                            off, because we never agree to turn on
2865                            anything non-default, so according to the
2866                            protocol rules, we don't reply. */
2867                         break;
2868                     }
2869                     break;
2870                   case TN_DO:
2871                     if (appData.debugMode)
2872                       fprintf(debugFP, "\n<DO ");
2873                     switch (option = (unsigned char) buf[++i]) {
2874                       default:
2875                         /* Whatever this is, we refuse to do it. */
2876                         if (appData.debugMode)
2877                           fprintf(debugFP, "%d ", option);
2878                         TelnetRequest(TN_WONT, option);
2879                         break;
2880                     }
2881                     break;
2882                   case TN_DONT:
2883                     if (appData.debugMode)
2884                       fprintf(debugFP, "\n<DONT ");
2885                     switch (option = (unsigned char) buf[++i]) {
2886                       default:
2887                         if (appData.debugMode)
2888                           fprintf(debugFP, "%d ", option);
2889                         /* Whatever this is, we are already not doing
2890                            it, because we never agree to do anything
2891                            non-default, so according to the protocol
2892                            rules, we don't reply. */
2893                         break;
2894                     }
2895                     break;
2896                   case TN_IAC:
2897                     if (appData.debugMode)
2898                       fprintf(debugFP, "\n<IAC ");
2899                     /* Doubled IAC; pass it through */
2900                     i--;
2901                     break;
2902                   default:
2903                     if (appData.debugMode)
2904                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2905                     /* Drop all other telnet commands on the floor */
2906                     break;
2907                 }
2908                 if (oldi > next_out)
2909                   SendToPlayer(&buf[next_out], oldi - next_out);
2910                 if (++i > next_out)
2911                   next_out = i;
2912                 continue;
2913             }
2914
2915             /* OK, this at least will *usually* work */
2916             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2917                 loggedOn = TRUE;
2918             }
2919
2920             if (loggedOn && !intfSet) {
2921                 if (ics_type == ICS_ICC) {
2922                   snprintf(str, MSG_SIZ,
2923                           "/set-quietly interface %s\n/set-quietly style 12\n",
2924                           programVersion);
2925                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2926                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2927                 } else if (ics_type == ICS_CHESSNET) {
2928                   snprintf(str, MSG_SIZ, "/style 12\n");
2929                 } else {
2930                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2931                   strcat(str, programVersion);
2932                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2933                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2934                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2935 #ifdef WIN32
2936                   strcat(str, "$iset nohighlight 1\n");
2937 #endif
2938                   strcat(str, "$iset lock 1\n$style 12\n");
2939                 }
2940                 SendToICS(str);
2941                 NotifyFrontendLogin();
2942                 intfSet = TRUE;
2943             }
2944
2945             if (started == STARTED_COMMENT) {
2946                 /* Accumulate characters in comment */
2947                 parse[parse_pos++] = buf[i];
2948                 if (buf[i] == '\n') {
2949                     parse[parse_pos] = NULLCHAR;
2950                     if(chattingPartner>=0) {
2951                         char mess[MSG_SIZ];
2952                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2953                         OutputChatMessage(chattingPartner, mess);
2954                         chattingPartner = -1;
2955                         next_out = i+1; // [HGM] suppress printing in ICS window
2956                     } else
2957                     if(!suppressKibitz) // [HGM] kibitz
2958                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2959                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2960                         int nrDigit = 0, nrAlph = 0, j;
2961                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2962                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2963                         parse[parse_pos] = NULLCHAR;
2964                         // try to be smart: if it does not look like search info, it should go to
2965                         // ICS interaction window after all, not to engine-output window.
2966                         for(j=0; j<parse_pos; j++) { // count letters and digits
2967                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2968                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2969                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2970                         }
2971                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2972                             int depth=0; float score;
2973                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2974                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2975                                 pvInfoList[forwardMostMove-1].depth = depth;
2976                                 pvInfoList[forwardMostMove-1].score = 100*score;
2977                             }
2978                             OutputKibitz(suppressKibitz, parse);
2979                         } else {
2980                             char tmp[MSG_SIZ];
2981                             if(gameMode == IcsObserving) // restore original ICS messages
2982                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2983                             else
2984                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2985                             SendToPlayer(tmp, strlen(tmp));
2986                         }
2987                         next_out = i+1; // [HGM] suppress printing in ICS window
2988                     }
2989                     started = STARTED_NONE;
2990                 } else {
2991                     /* Don't match patterns against characters in comment */
2992                     i++;
2993                     continue;
2994                 }
2995             }
2996             if (started == STARTED_CHATTER) {
2997                 if (buf[i] != '\n') {
2998                     /* Don't match patterns against characters in chatter */
2999                     i++;
3000                     continue;
3001                 }
3002                 started = STARTED_NONE;
3003                 if(suppressKibitz) next_out = i+1;
3004             }
3005
3006             /* Kludge to deal with rcmd protocol */
3007             if (firstTime && looking_at(buf, &i, "\001*")) {
3008                 DisplayFatalError(&buf[1], 0, 1);
3009                 continue;
3010             } else {
3011                 firstTime = FALSE;
3012             }
3013
3014             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3015                 ics_type = ICS_ICC;
3016                 ics_prefix = "/";
3017                 if (appData.debugMode)
3018                   fprintf(debugFP, "ics_type %d\n", ics_type);
3019                 continue;
3020             }
3021             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3022                 ics_type = ICS_FICS;
3023                 ics_prefix = "$";
3024                 if (appData.debugMode)
3025                   fprintf(debugFP, "ics_type %d\n", ics_type);
3026                 continue;
3027             }
3028             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3029                 ics_type = ICS_CHESSNET;
3030                 ics_prefix = "/";
3031                 if (appData.debugMode)
3032                   fprintf(debugFP, "ics_type %d\n", ics_type);
3033                 continue;
3034             }
3035
3036             if (!loggedOn &&
3037                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3038                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3039                  looking_at(buf, &i, "will be \"*\""))) {
3040               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3041               continue;
3042             }
3043
3044             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3045               char buf[MSG_SIZ];
3046               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3047               DisplayIcsInteractionTitle(buf);
3048               have_set_title = TRUE;
3049             }
3050
3051             /* skip finger notes */
3052             if (started == STARTED_NONE &&
3053                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3054                  (buf[i] == '1' && buf[i+1] == '0')) &&
3055                 buf[i+2] == ':' && buf[i+3] == ' ') {
3056               started = STARTED_CHATTER;
3057               i += 3;
3058               continue;
3059             }
3060
3061             oldi = i;
3062             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3063             if(appData.seekGraph) {
3064                 if(soughtPending && MatchSoughtLine(buf+i)) {
3065                     i = strstr(buf+i, "rated") - buf;
3066                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3067                     next_out = leftover_start = i;
3068                     started = STARTED_CHATTER;
3069                     suppressKibitz = TRUE;
3070                     continue;
3071                 }
3072                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3073                         && looking_at(buf, &i, "* ads displayed")) {
3074                     soughtPending = FALSE;
3075                     seekGraphUp = TRUE;
3076                     DrawSeekGraph();
3077                     continue;
3078                 }
3079                 if(appData.autoRefresh) {
3080                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3081                         int s = (ics_type == ICS_ICC); // ICC format differs
3082                         if(seekGraphUp)
3083                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3084                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3085                         looking_at(buf, &i, "*% "); // eat prompt
3086                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3087                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3088                         next_out = i; // suppress
3089                         continue;
3090                     }
3091                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3092                         char *p = star_match[0];
3093                         while(*p) {
3094                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3095                             while(*p && *p++ != ' '); // next
3096                         }
3097                         looking_at(buf, &i, "*% "); // eat prompt
3098                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3099                         next_out = i;
3100                         continue;
3101                     }
3102                 }
3103             }
3104
3105             /* skip formula vars */
3106             if (started == STARTED_NONE &&
3107                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3108               started = STARTED_CHATTER;
3109               i += 3;
3110               continue;
3111             }
3112
3113             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3114             if (appData.autoKibitz && started == STARTED_NONE &&
3115                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3116                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3117                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3118                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3119                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3120                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3121                         suppressKibitz = TRUE;
3122                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3123                         next_out = i;
3124                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3125                                 && (gameMode == IcsPlayingWhite)) ||
3126                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3127                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3128                             started = STARTED_CHATTER; // own kibitz we simply discard
3129                         else {
3130                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3131                             parse_pos = 0; parse[0] = NULLCHAR;
3132                             savingComment = TRUE;
3133                             suppressKibitz = gameMode != IcsObserving ? 2 :
3134                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3135                         }
3136                         continue;
3137                 } else
3138                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3139                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3140                          && atoi(star_match[0])) {
3141                     // suppress the acknowledgements of our own autoKibitz
3142                     char *p;
3143                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3144                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3145                     SendToPlayer(star_match[0], strlen(star_match[0]));
3146                     if(looking_at(buf, &i, "*% ")) // eat prompt
3147                         suppressKibitz = FALSE;
3148                     next_out = i;
3149                     continue;
3150                 }
3151             } // [HGM] kibitz: end of patch
3152
3153             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3154
3155             // [HGM] chat: intercept tells by users for which we have an open chat window
3156             channel = -1;
3157             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3158                                            looking_at(buf, &i, "* whispers:") ||
3159                                            looking_at(buf, &i, "* kibitzes:") ||
3160                                            looking_at(buf, &i, "* shouts:") ||
3161                                            looking_at(buf, &i, "* c-shouts:") ||
3162                                            looking_at(buf, &i, "--> * ") ||
3163                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3164                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3165                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3166                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3167                 int p;
3168                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3169                 chattingPartner = -1;
3170
3171                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3172                 for(p=0; p<MAX_CHAT; p++) {
3173                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3174                     talker[0] = '['; strcat(talker, "] ");
3175                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3176                     chattingPartner = p; break;
3177                     }
3178                 } else
3179                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3180                 for(p=0; p<MAX_CHAT; p++) {
3181                     if(!strcmp("kibitzes", chatPartner[p])) {
3182                         talker[0] = '['; strcat(talker, "] ");
3183                         chattingPartner = p; break;
3184                     }
3185                 } else
3186                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3187                 for(p=0; p<MAX_CHAT; p++) {
3188                     if(!strcmp("whispers", chatPartner[p])) {
3189                         talker[0] = '['; strcat(talker, "] ");
3190                         chattingPartner = p; break;
3191                     }
3192                 } else
3193                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3194                   if(buf[i-8] == '-' && buf[i-3] == 't')
3195                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3196                     if(!strcmp("c-shouts", chatPartner[p])) {
3197                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3198                         chattingPartner = p; break;
3199                     }
3200                   }
3201                   if(chattingPartner < 0)
3202                   for(p=0; p<MAX_CHAT; p++) {
3203                     if(!strcmp("shouts", chatPartner[p])) {
3204                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3205                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3206                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3207                         chattingPartner = p; break;
3208                     }
3209                   }
3210                 }
3211                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3212                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3213                     talker[0] = 0; Colorize(ColorTell, FALSE);
3214                     chattingPartner = p; break;
3215                 }
3216                 if(chattingPartner<0) i = oldi; else {
3217                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3218                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3219                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3220                     started = STARTED_COMMENT;
3221                     parse_pos = 0; parse[0] = NULLCHAR;
3222                     savingComment = 3 + chattingPartner; // counts as TRUE
3223                     suppressKibitz = TRUE;
3224                     continue;
3225                 }
3226             } // [HGM] chat: end of patch
3227
3228           backup = i;
3229             if (appData.zippyTalk || appData.zippyPlay) {
3230                 /* [DM] Backup address for color zippy lines */
3231 #if ZIPPY
3232                if (loggedOn == TRUE)
3233                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3234                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3235 #endif
3236             } // [DM] 'else { ' deleted
3237                 if (
3238                     /* Regular tells and says */
3239                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3240                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3241                     looking_at(buf, &i, "* says: ") ||
3242                     /* Don't color "message" or "messages" output */
3243                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3244                     looking_at(buf, &i, "*. * at *:*: ") ||
3245                     looking_at(buf, &i, "--* (*:*): ") ||
3246                     /* Message notifications (same color as tells) */
3247                     looking_at(buf, &i, "* has left a message ") ||
3248                     looking_at(buf, &i, "* just sent you a message:\n") ||
3249                     /* Whispers and kibitzes */
3250                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3251                     looking_at(buf, &i, "* kibitzes: ") ||
3252                     /* Channel tells */
3253                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3254
3255                   if (tkind == 1 && strchr(star_match[0], ':')) {
3256                       /* Avoid "tells you:" spoofs in channels */
3257                      tkind = 3;
3258                   }
3259                   if (star_match[0][0] == NULLCHAR ||
3260                       strchr(star_match[0], ' ') ||
3261                       (tkind == 3 && strchr(star_match[1], ' '))) {
3262                     /* Reject bogus matches */
3263                     i = oldi;
3264                   } else {
3265                     if (appData.colorize) {
3266                       if (oldi > next_out) {
3267                         SendToPlayer(&buf[next_out], oldi - next_out);
3268                         next_out = oldi;
3269                       }
3270                       switch (tkind) {
3271                       case 1:
3272                         Colorize(ColorTell, FALSE);
3273                         curColor = ColorTell;
3274                         break;
3275                       case 2:
3276                         Colorize(ColorKibitz, FALSE);
3277                         curColor = ColorKibitz;
3278                         break;
3279                       case 3:
3280                         p = strrchr(star_match[1], '(');
3281                         if (p == NULL) {
3282                           p = star_match[1];
3283                         } else {
3284                           p++;
3285                         }
3286                         if (atoi(p) == 1) {
3287                           Colorize(ColorChannel1, FALSE);
3288                           curColor = ColorChannel1;
3289                         } else {
3290                           Colorize(ColorChannel, FALSE);
3291                           curColor = ColorChannel;
3292                         }
3293                         break;
3294                       case 5:
3295                         curColor = ColorNormal;
3296                         break;
3297                       }
3298                     }
3299                     if (started == STARTED_NONE && appData.autoComment &&
3300                         (gameMode == IcsObserving ||
3301                          gameMode == IcsPlayingWhite ||
3302                          gameMode == IcsPlayingBlack)) {
3303                       parse_pos = i - oldi;
3304                       memcpy(parse, &buf[oldi], parse_pos);
3305                       parse[parse_pos] = NULLCHAR;
3306                       started = STARTED_COMMENT;
3307                       savingComment = TRUE;
3308                     } else {
3309                       started = STARTED_CHATTER;
3310                       savingComment = FALSE;
3311                     }
3312                     loggedOn = TRUE;
3313                     continue;
3314                   }
3315                 }
3316
3317                 if (looking_at(buf, &i, "* s-shouts: ") ||
3318                     looking_at(buf, &i, "* c-shouts: ")) {
3319                     if (appData.colorize) {
3320                         if (oldi > next_out) {
3321                             SendToPlayer(&buf[next_out], oldi - next_out);
3322                             next_out = oldi;
3323                         }
3324                         Colorize(ColorSShout, FALSE);
3325                         curColor = ColorSShout;
3326                     }
3327                     loggedOn = TRUE;
3328                     started = STARTED_CHATTER;
3329                     continue;
3330                 }
3331
3332                 if (looking_at(buf, &i, "--->")) {
3333                     loggedOn = TRUE;
3334                     continue;
3335                 }
3336
3337                 if (looking_at(buf, &i, "* shouts: ") ||
3338                     looking_at(buf, &i, "--> ")) {
3339                     if (appData.colorize) {
3340                         if (oldi > next_out) {
3341                             SendToPlayer(&buf[next_out], oldi - next_out);
3342                             next_out = oldi;
3343                         }
3344                         Colorize(ColorShout, FALSE);
3345                         curColor = ColorShout;
3346                     }
3347                     loggedOn = TRUE;
3348                     started = STARTED_CHATTER;
3349                     continue;
3350                 }
3351
3352                 if (looking_at( buf, &i, "Challenge:")) {
3353                     if (appData.colorize) {
3354                         if (oldi > next_out) {
3355                             SendToPlayer(&buf[next_out], oldi - next_out);
3356                             next_out = oldi;
3357                         }
3358                         Colorize(ColorChallenge, FALSE);
3359                         curColor = ColorChallenge;
3360                     }
3361                     loggedOn = TRUE;
3362                     continue;
3363                 }
3364
3365                 if (looking_at(buf, &i, "* offers you") ||
3366                     looking_at(buf, &i, "* offers to be") ||
3367                     looking_at(buf, &i, "* would like to") ||
3368                     looking_at(buf, &i, "* requests to") ||
3369                     looking_at(buf, &i, "Your opponent offers") ||
3370                     looking_at(buf, &i, "Your opponent requests")) {
3371
3372                     if (appData.colorize) {
3373                         if (oldi > next_out) {
3374                             SendToPlayer(&buf[next_out], oldi - next_out);
3375                             next_out = oldi;
3376                         }
3377                         Colorize(ColorRequest, FALSE);
3378                         curColor = ColorRequest;
3379                     }
3380                     continue;
3381                 }
3382
3383                 if (looking_at(buf, &i, "* (*) seeking")) {
3384                     if (appData.colorize) {
3385                         if (oldi > next_out) {
3386                             SendToPlayer(&buf[next_out], oldi - next_out);
3387                             next_out = oldi;
3388                         }
3389                         Colorize(ColorSeek, FALSE);
3390                         curColor = ColorSeek;
3391                     }
3392                     continue;
3393             }
3394
3395           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3396
3397             if (looking_at(buf, &i, "\\   ")) {
3398                 if (prevColor != ColorNormal) {
3399                     if (oldi > next_out) {
3400                         SendToPlayer(&buf[next_out], oldi - next_out);
3401                         next_out = oldi;
3402                     }
3403                     Colorize(prevColor, TRUE);
3404                     curColor = prevColor;
3405                 }
3406                 if (savingComment) {
3407                     parse_pos = i - oldi;
3408                     memcpy(parse, &buf[oldi], parse_pos);
3409                     parse[parse_pos] = NULLCHAR;
3410                     started = STARTED_COMMENT;
3411                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3412                         chattingPartner = savingComment - 3; // kludge to remember the box
3413                 } else {
3414                     started = STARTED_CHATTER;
3415                 }
3416                 continue;
3417             }
3418
3419             if (looking_at(buf, &i, "Black Strength :") ||
3420                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3421                 looking_at(buf, &i, "<10>") ||
3422                 looking_at(buf, &i, "#@#")) {
3423                 /* Wrong board style */
3424                 loggedOn = TRUE;
3425                 SendToICS(ics_prefix);
3426                 SendToICS("set style 12\n");
3427                 SendToICS(ics_prefix);
3428                 SendToICS("refresh\n");
3429                 continue;
3430             }
3431
3432             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3433                 ICSInitScript();
3434                 have_sent_ICS_logon = 1;
3435                 continue;
3436             }
3437
3438             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3439                 (looking_at(buf, &i, "\n<12> ") ||
3440                  looking_at(buf, &i, "<12> "))) {
3441                 loggedOn = TRUE;
3442                 if (oldi > next_out) {
3443                     SendToPlayer(&buf[next_out], oldi - next_out);
3444                 }
3445                 next_out = i;
3446                 started = STARTED_BOARD;
3447                 parse_pos = 0;
3448                 continue;
3449             }
3450
3451             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3452                 looking_at(buf, &i, "<b1> ")) {
3453                 if (oldi > next_out) {
3454                     SendToPlayer(&buf[next_out], oldi - next_out);
3455                 }
3456                 next_out = i;
3457                 started = STARTED_HOLDINGS;
3458                 parse_pos = 0;
3459                 continue;
3460             }
3461
3462             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3463                 loggedOn = TRUE;
3464                 /* Header for a move list -- first line */
3465
3466                 switch (ics_getting_history) {
3467                   case H_FALSE:
3468                     switch (gameMode) {
3469                       case IcsIdle:
3470                       case BeginningOfGame:
3471                         /* User typed "moves" or "oldmoves" while we
3472                            were idle.  Pretend we asked for these
3473                            moves and soak them up so user can step
3474                            through them and/or save them.
3475                            */
3476                         Reset(FALSE, TRUE);
3477                         gameMode = IcsObserving;
3478                         ModeHighlight();
3479                         ics_gamenum = -1;
3480                         ics_getting_history = H_GOT_UNREQ_HEADER;
3481                         break;
3482                       case EditGame: /*?*/
3483                       case EditPosition: /*?*/
3484                         /* Should above feature work in these modes too? */
3485                         /* For now it doesn't */
3486                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3487                         break;
3488                       default:
3489                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3490                         break;
3491                     }
3492                     break;
3493                   case H_REQUESTED:
3494                     /* Is this the right one? */
3495                     if (gameInfo.white && gameInfo.black &&
3496                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3497                         strcmp(gameInfo.black, star_match[2]) == 0) {
3498                         /* All is well */
3499                         ics_getting_history = H_GOT_REQ_HEADER;
3500                     }
3501                     break;
3502                   case H_GOT_REQ_HEADER:
3503                   case H_GOT_UNREQ_HEADER:
3504                   case H_GOT_UNWANTED_HEADER:
3505                   case H_GETTING_MOVES:
3506                     /* Should not happen */
3507                     DisplayError(_("Error gathering move list: two headers"), 0);
3508                     ics_getting_history = H_FALSE;
3509                     break;
3510                 }
3511
3512                 /* Save player ratings into gameInfo if needed */
3513                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3514                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3515                     (gameInfo.whiteRating == -1 ||
3516                      gameInfo.blackRating == -1)) {
3517
3518                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3519                     gameInfo.blackRating = string_to_rating(star_match[3]);
3520                     if (appData.debugMode)
3521                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3522                               gameInfo.whiteRating, gameInfo.blackRating);
3523                 }
3524                 continue;
3525             }
3526
3527             if (looking_at(buf, &i,
3528               "* * match, initial time: * minute*, increment: * second")) {
3529                 /* Header for a move list -- second line */
3530                 /* Initial board will follow if this is a wild game */
3531                 if (gameInfo.event != NULL) free(gameInfo.event);
3532                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3533                 gameInfo.event = StrSave(str);
3534                 /* [HGM] we switched variant. Translate boards if needed. */
3535                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3536                 continue;
3537             }
3538
3539             if (looking_at(buf, &i, "Move  ")) {
3540                 /* Beginning of a move list */
3541                 switch (ics_getting_history) {
3542                   case H_FALSE:
3543                     /* Normally should not happen */
3544                     /* Maybe user hit reset while we were parsing */
3545                     break;
3546                   case H_REQUESTED:
3547                     /* Happens if we are ignoring a move list that is not
3548                      * the one we just requested.  Common if the user
3549                      * tries to observe two games without turning off
3550                      * getMoveList */
3551                     break;
3552                   case H_GETTING_MOVES:
3553                     /* Should not happen */
3554                     DisplayError(_("Error gathering move list: nested"), 0);
3555                     ics_getting_history = H_FALSE;
3556                     break;
3557                   case H_GOT_REQ_HEADER:
3558                     ics_getting_history = H_GETTING_MOVES;
3559                     started = STARTED_MOVES;
3560                     parse_pos = 0;
3561                     if (oldi > next_out) {
3562                         SendToPlayer(&buf[next_out], oldi - next_out);
3563                     }
3564                     break;
3565                   case H_GOT_UNREQ_HEADER:
3566                     ics_getting_history = H_GETTING_MOVES;
3567                     started = STARTED_MOVES_NOHIDE;
3568                     parse_pos = 0;
3569                     break;
3570                   case H_GOT_UNWANTED_HEADER:
3571                     ics_getting_history = H_FALSE;
3572                     break;
3573                 }
3574                 continue;
3575             }
3576
3577             if (looking_at(buf, &i, "% ") ||
3578                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3579                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3580                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3581                     soughtPending = FALSE;
3582                     seekGraphUp = TRUE;
3583                     DrawSeekGraph();
3584                 }
3585                 if(suppressKibitz) next_out = i;
3586                 savingComment = FALSE;
3587                 suppressKibitz = 0;
3588                 switch (started) {
3589                   case STARTED_MOVES:
3590                   case STARTED_MOVES_NOHIDE:
3591                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3592                     parse[parse_pos + i - oldi] = NULLCHAR;
3593                     ParseGameHistory(parse);
3594 #if ZIPPY
3595                     if (appData.zippyPlay && first.initDone) {
3596                         FeedMovesToProgram(&first, forwardMostMove);
3597                         if (gameMode == IcsPlayingWhite) {
3598                             if (WhiteOnMove(forwardMostMove)) {
3599                                 if (first.sendTime) {
3600                                   if (first.useColors) {
3601                                     SendToProgram("black\n", &first);
3602                                   }
3603                                   SendTimeRemaining(&first, TRUE);
3604                                 }
3605                                 if (first.useColors) {
3606                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3607                                 }
3608                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3609                                 first.maybeThinking = TRUE;
3610                             } else {
3611                                 if (first.usePlayother) {
3612                                   if (first.sendTime) {
3613                                     SendTimeRemaining(&first, TRUE);
3614                                   }
3615                                   SendToProgram("playother\n", &first);
3616                                   firstMove = FALSE;
3617                                 } else {
3618                                   firstMove = TRUE;
3619                                 }
3620                             }
3621                         } else if (gameMode == IcsPlayingBlack) {
3622                             if (!WhiteOnMove(forwardMostMove)) {
3623                                 if (first.sendTime) {
3624                                   if (first.useColors) {
3625                                     SendToProgram("white\n", &first);
3626                                   }
3627                                   SendTimeRemaining(&first, FALSE);
3628                                 }
3629                                 if (first.useColors) {
3630                                   SendToProgram("black\n", &first);
3631                                 }
3632                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3633                                 first.maybeThinking = TRUE;
3634                             } else {
3635                                 if (first.usePlayother) {
3636                                   if (first.sendTime) {
3637                                     SendTimeRemaining(&first, FALSE);
3638                                   }
3639                                   SendToProgram("playother\n", &first);
3640                                   firstMove = FALSE;
3641                                 } else {
3642                                   firstMove = TRUE;
3643                                 }
3644                             }
3645                         }
3646                     }
3647 #endif
3648                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3649                         /* Moves came from oldmoves or moves command
3650                            while we weren't doing anything else.
3651                            */
3652                         currentMove = forwardMostMove;
3653                         ClearHighlights();/*!!could figure this out*/
3654                         flipView = appData.flipView;
3655                         DrawPosition(TRUE, boards[currentMove]);
3656                         DisplayBothClocks();
3657                         snprintf(str, MSG_SIZ, "%s %s %s",
3658                                 gameInfo.white, _("vs."),  gameInfo.black);
3659                         DisplayTitle(str);
3660                         gameMode = IcsIdle;
3661                     } else {
3662                         /* Moves were history of an active game */
3663                         if (gameInfo.resultDetails != NULL) {
3664                             free(gameInfo.resultDetails);
3665                             gameInfo.resultDetails = NULL;
3666                         }
3667                     }
3668                     HistorySet(parseList, backwardMostMove,
3669                                forwardMostMove, currentMove-1);
3670                     DisplayMove(currentMove - 1);
3671                     if (started == STARTED_MOVES) next_out = i;
3672                     started = STARTED_NONE;
3673                     ics_getting_history = H_FALSE;
3674                     break;
3675
3676                   case STARTED_OBSERVE:
3677                     started = STARTED_NONE;
3678                     SendToICS(ics_prefix);
3679                     SendToICS("refresh\n");
3680                     break;
3681
3682                   default:
3683                     break;
3684                 }
3685                 if(bookHit) { // [HGM] book: simulate book reply
3686                     static char bookMove[MSG_SIZ]; // a bit generous?
3687
3688                     programStats.nodes = programStats.depth = programStats.time =
3689                     programStats.score = programStats.got_only_move = 0;
3690                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3691
3692                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3693                     strcat(bookMove, bookHit);
3694                     HandleMachineMove(bookMove, &first);
3695                 }
3696                 continue;
3697             }
3698
3699             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3700                  started == STARTED_HOLDINGS ||
3701                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3702                 /* Accumulate characters in move list or board */
3703                 parse[parse_pos++] = buf[i];
3704             }
3705
3706             /* Start of game messages.  Mostly we detect start of game
3707                when the first board image arrives.  On some versions
3708                of the ICS, though, we need to do a "refresh" after starting
3709                to observe in order to get the current board right away. */
3710             if (looking_at(buf, &i, "Adding game * to observation list")) {
3711                 started = STARTED_OBSERVE;
3712                 continue;
3713             }
3714
3715             /* Handle auto-observe */
3716             if (appData.autoObserve &&
3717                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3718                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3719                 char *player;
3720                 /* Choose the player that was highlighted, if any. */
3721                 if (star_match[0][0] == '\033' ||
3722                     star_match[1][0] != '\033') {
3723                     player = star_match[0];
3724                 } else {
3725                     player = star_match[2];
3726                 }
3727                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3728                         ics_prefix, StripHighlightAndTitle(player));
3729                 SendToICS(str);
3730
3731                 /* Save ratings from notify string */
3732                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3733                 player1Rating = string_to_rating(star_match[1]);
3734                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3735                 player2Rating = string_to_rating(star_match[3]);
3736
3737                 if (appData.debugMode)
3738                   fprintf(debugFP,
3739                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3740                           player1Name, player1Rating,
3741                           player2Name, player2Rating);
3742
3743                 continue;
3744             }
3745
3746             /* Deal with automatic examine mode after a game,
3747                and with IcsObserving -> IcsExamining transition */
3748             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3749                 looking_at(buf, &i, "has made you an examiner of game *")) {
3750
3751                 int gamenum = atoi(star_match[0]);
3752                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3753                     gamenum == ics_gamenum) {
3754                     /* We were already playing or observing this game;
3755                        no need to refetch history */
3756                     gameMode = IcsExamining;
3757                     if (pausing) {
3758                         pauseExamForwardMostMove = forwardMostMove;
3759                     } else if (currentMove < forwardMostMove) {
3760                         ForwardInner(forwardMostMove);
3761                     }
3762                 } else {
3763                     /* I don't think this case really can happen */
3764                     SendToICS(ics_prefix);
3765                     SendToICS("refresh\n");
3766                 }
3767                 continue;
3768             }
3769
3770             /* Error messages */
3771 //          if (ics_user_moved) {
3772             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3773                 if (looking_at(buf, &i, "Illegal move") ||
3774                     looking_at(buf, &i, "Not a legal move") ||
3775                     looking_at(buf, &i, "Your king is in check") ||
3776                     looking_at(buf, &i, "It isn't your turn") ||
3777                     looking_at(buf, &i, "It is not your move")) {
3778                     /* Illegal move */
3779                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3780                         currentMove = forwardMostMove-1;
3781                         DisplayMove(currentMove - 1); /* before DMError */
3782                         DrawPosition(FALSE, boards[currentMove]);
3783                         SwitchClocks(forwardMostMove-1); // [HGM] race
3784                         DisplayBothClocks();
3785                     }
3786                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3787                     ics_user_moved = 0;
3788                     continue;
3789                 }
3790             }
3791
3792             if (looking_at(buf, &i, "still have time") ||
3793                 looking_at(buf, &i, "not out of time") ||
3794                 looking_at(buf, &i, "either player is out of time") ||
3795                 looking_at(buf, &i, "has timeseal; checking")) {
3796                 /* We must have called his flag a little too soon */
3797                 whiteFlag = blackFlag = FALSE;
3798                 continue;
3799             }
3800
3801             if (looking_at(buf, &i, "added * seconds to") ||
3802                 looking_at(buf, &i, "seconds were added to")) {
3803                 /* Update the clocks */
3804                 SendToICS(ics_prefix);
3805                 SendToICS("refresh\n");
3806                 continue;
3807             }
3808
3809             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3810                 ics_clock_paused = TRUE;
3811                 StopClocks();
3812                 continue;
3813             }
3814
3815             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3816                 ics_clock_paused = FALSE;
3817                 StartClocks();
3818                 continue;
3819             }
3820
3821             /* Grab player ratings from the Creating: message.
3822                Note we have to check for the special case when
3823                the ICS inserts things like [white] or [black]. */
3824             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3825                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3826                 /* star_matches:
3827                    0    player 1 name (not necessarily white)
3828                    1    player 1 rating
3829                    2    empty, white, or black (IGNORED)
3830                    3    player 2 name (not necessarily black)
3831                    4    player 2 rating
3832
3833                    The names/ratings are sorted out when the game
3834                    actually starts (below).
3835                 */
3836                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3837                 player1Rating = string_to_rating(star_match[1]);
3838                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3839                 player2Rating = string_to_rating(star_match[4]);
3840
3841                 if (appData.debugMode)
3842                   fprintf(debugFP,
3843                           "Ratings from 'Creating:' %s %d, %s %d\n",
3844                           player1Name, player1Rating,
3845                           player2Name, player2Rating);
3846
3847                 continue;
3848             }
3849
3850             /* Improved generic start/end-of-game messages */
3851             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3852                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3853                 /* If tkind == 0: */
3854                 /* star_match[0] is the game number */
3855                 /*           [1] is the white player's name */
3856                 /*           [2] is the black player's name */
3857                 /* For end-of-game: */
3858                 /*           [3] is the reason for the game end */
3859                 /*           [4] is a PGN end game-token, preceded by " " */
3860                 /* For start-of-game: */
3861                 /*           [3] begins with "Creating" or "Continuing" */
3862                 /*           [4] is " *" or empty (don't care). */
3863                 int gamenum = atoi(star_match[0]);
3864                 char *whitename, *blackname, *why, *endtoken;
3865                 ChessMove endtype = EndOfFile;
3866
3867                 if (tkind == 0) {
3868                   whitename = star_match[1];
3869                   blackname = star_match[2];
3870                   why = star_match[3];
3871                   endtoken = star_match[4];
3872                 } else {
3873                   whitename = star_match[1];
3874                   blackname = star_match[3];
3875                   why = star_match[5];
3876                   endtoken = star_match[6];
3877                 }
3878
3879                 /* Game start messages */
3880                 if (strncmp(why, "Creating ", 9) == 0 ||
3881                     strncmp(why, "Continuing ", 11) == 0) {
3882                     gs_gamenum = gamenum;
3883                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3884                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3885                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3886 #if ZIPPY
3887                     if (appData.zippyPlay) {
3888                         ZippyGameStart(whitename, blackname);
3889                     }
3890 #endif /*ZIPPY*/
3891                     partnerBoardValid = FALSE; // [HGM] bughouse
3892                     continue;
3893                 }
3894
3895                 /* Game end messages */
3896                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3897                     ics_gamenum != gamenum) {
3898                     continue;
3899                 }
3900                 while (endtoken[0] == ' ') endtoken++;
3901                 switch (endtoken[0]) {
3902                   case '*':
3903                   default:
3904                     endtype = GameUnfinished;
3905                     break;
3906                   case '0':
3907                     endtype = BlackWins;
3908                     break;
3909                   case '1':
3910                     if (endtoken[1] == '/')
3911                       endtype = GameIsDrawn;
3912                     else
3913                       endtype = WhiteWins;
3914                     break;
3915                 }
3916                 GameEnds(endtype, why, GE_ICS);
3917 #if ZIPPY
3918                 if (appData.zippyPlay && first.initDone) {
3919                     ZippyGameEnd(endtype, why);
3920                     if (first.pr == NoProc) {
3921                       /* Start the next process early so that we'll
3922                          be ready for the next challenge */
3923                       StartChessProgram(&first);
3924                     }
3925                     /* Send "new" early, in case this command takes
3926                        a long time to finish, so that we'll be ready
3927                        for the next challenge. */
3928                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3929                     Reset(TRUE, TRUE);
3930                 }
3931 #endif /*ZIPPY*/
3932                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3933                 continue;
3934             }
3935
3936             if (looking_at(buf, &i, "Removing game * from observation") ||
3937                 looking_at(buf, &i, "no longer observing game *") ||
3938                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3939                 if (gameMode == IcsObserving &&
3940                     atoi(star_match[0]) == ics_gamenum)
3941                   {
3942                       /* icsEngineAnalyze */
3943                       if (appData.icsEngineAnalyze) {
3944                             ExitAnalyzeMode();
3945                             ModeHighlight();
3946                       }
3947                       StopClocks();
3948                       gameMode = IcsIdle;
3949                       ics_gamenum = -1;
3950                       ics_user_moved = FALSE;
3951                   }
3952                 continue;
3953             }
3954
3955             if (looking_at(buf, &i, "no longer examining game *")) {
3956                 if (gameMode == IcsExamining &&
3957                     atoi(star_match[0]) == ics_gamenum)
3958                   {
3959                       gameMode = IcsIdle;
3960                       ics_gamenum = -1;
3961                       ics_user_moved = FALSE;
3962                   }
3963                 continue;
3964             }
3965
3966             /* Advance leftover_start past any newlines we find,
3967                so only partial lines can get reparsed */
3968             if (looking_at(buf, &i, "\n")) {
3969                 prevColor = curColor;
3970                 if (curColor != ColorNormal) {
3971                     if (oldi > next_out) {
3972                         SendToPlayer(&buf[next_out], oldi - next_out);
3973                         next_out = oldi;
3974                     }
3975                     Colorize(ColorNormal, FALSE);
3976                     curColor = ColorNormal;
3977                 }
3978                 if (started == STARTED_BOARD) {
3979                     started = STARTED_NONE;
3980                     parse[parse_pos] = NULLCHAR;
3981                     ParseBoard12(parse);
3982                     ics_user_moved = 0;
3983
3984                     /* Send premove here */
3985                     if (appData.premove) {
3986                       char str[MSG_SIZ];
3987                       if (currentMove == 0 &&
3988                           gameMode == IcsPlayingWhite &&
3989                           appData.premoveWhite) {
3990                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3991                         if (appData.debugMode)
3992                           fprintf(debugFP, "Sending premove:\n");
3993                         SendToICS(str);
3994                       } else if (currentMove == 1 &&
3995                                  gameMode == IcsPlayingBlack &&
3996                                  appData.premoveBlack) {
3997                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3998                         if (appData.debugMode)
3999                           fprintf(debugFP, "Sending premove:\n");
4000                         SendToICS(str);
4001                       } else if (gotPremove) {
4002                         gotPremove = 0;
4003                         ClearPremoveHighlights();
4004                         if (appData.debugMode)
4005                           fprintf(debugFP, "Sending premove:\n");
4006                           UserMoveEvent(premoveFromX, premoveFromY,
4007                                         premoveToX, premoveToY,
4008                                         premovePromoChar);
4009                       }
4010                     }
4011
4012                     /* Usually suppress following prompt */
4013                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4014                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4015                         if (looking_at(buf, &i, "*% ")) {
4016                             savingComment = FALSE;
4017                             suppressKibitz = 0;
4018                         }
4019                     }
4020                     next_out = i;
4021                 } else if (started == STARTED_HOLDINGS) {
4022                     int gamenum;
4023                     char new_piece[MSG_SIZ];
4024                     started = STARTED_NONE;
4025                     parse[parse_pos] = NULLCHAR;
4026                     if (appData.debugMode)
4027                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4028                                                         parse, currentMove);
4029                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4030                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4031                         if (gameInfo.variant == VariantNormal) {
4032                           /* [HGM] We seem to switch variant during a game!
4033                            * Presumably no holdings were displayed, so we have
4034                            * to move the position two files to the right to
4035                            * create room for them!
4036                            */
4037                           VariantClass newVariant;
4038                           switch(gameInfo.boardWidth) { // base guess on board width
4039                                 case 9:  newVariant = VariantShogi; break;
4040                                 case 10: newVariant = VariantGreat; break;
4041                                 default: newVariant = VariantCrazyhouse; break;
4042                           }
4043                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4044                           /* Get a move list just to see the header, which
4045                              will tell us whether this is really bug or zh */
4046                           if (ics_getting_history == H_FALSE) {
4047                             ics_getting_history = H_REQUESTED;
4048                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4049                             SendToICS(str);
4050                           }
4051                         }
4052                         new_piece[0] = NULLCHAR;
4053                         sscanf(parse, "game %d white [%s black [%s <- %s",
4054                                &gamenum, white_holding, black_holding,
4055                                new_piece);
4056                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4057                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4058                         /* [HGM] copy holdings to board holdings area */
4059                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4060                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4061                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4062 #if ZIPPY
4063                         if (appData.zippyPlay && first.initDone) {
4064                             ZippyHoldings(white_holding, black_holding,
4065                                           new_piece);
4066                         }
4067 #endif /*ZIPPY*/
4068                         if (tinyLayout || smallLayout) {
4069                             char wh[16], bh[16];
4070                             PackHolding(wh, white_holding);
4071                             PackHolding(bh, black_holding);
4072                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4073                                     gameInfo.white, gameInfo.black);
4074                         } else {
4075                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4076                                     gameInfo.white, white_holding, _("vs."),
4077                                     gameInfo.black, black_holding);
4078                         }
4079                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4080                         DrawPosition(FALSE, boards[currentMove]);
4081                         DisplayTitle(str);
4082                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4083                         sscanf(parse, "game %d white [%s black [%s <- %s",
4084                                &gamenum, white_holding, black_holding,
4085                                new_piece);
4086                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4087                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4088                         /* [HGM] copy holdings to partner-board holdings area */
4089                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4090                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4091                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4092                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4093                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4094                       }
4095                     }
4096                     /* Suppress following prompt */
4097                     if (looking_at(buf, &i, "*% ")) {
4098                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4099                         savingComment = FALSE;
4100                         suppressKibitz = 0;
4101                     }
4102                     next_out = i;
4103                 }
4104                 continue;
4105             }
4106
4107             i++;                /* skip unparsed character and loop back */
4108         }
4109
4110         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4111 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4112 //          SendToPlayer(&buf[next_out], i - next_out);
4113             started != STARTED_HOLDINGS && leftover_start > next_out) {
4114             SendToPlayer(&buf[next_out], leftover_start - next_out);
4115             next_out = i;
4116         }
4117
4118         leftover_len = buf_len - leftover_start;
4119         /* if buffer ends with something we couldn't parse,
4120            reparse it after appending the next read */
4121
4122     } else if (count == 0) {
4123         RemoveInputSource(isr);
4124         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4125     } else {
4126         DisplayFatalError(_("Error reading from ICS"), error, 1);
4127     }
4128 }
4129
4130
4131 /* Board style 12 looks like this:
4132
4133    <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
4134
4135  * The "<12> " is stripped before it gets to this routine.  The two
4136  * trailing 0's (flip state and clock ticking) are later addition, and
4137  * some chess servers may not have them, or may have only the first.
4138  * Additional trailing fields may be added in the future.
4139  */
4140
4141 #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"
4142
4143 #define RELATION_OBSERVING_PLAYED    0
4144 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4145 #define RELATION_PLAYING_MYMOVE      1
4146 #define RELATION_PLAYING_NOTMYMOVE  -1
4147 #define RELATION_EXAMINING           2
4148 #define RELATION_ISOLATED_BOARD     -3
4149 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4150
4151 void
4152 ParseBoard12 (char *string)
4153 {
4154     GameMode newGameMode;
4155     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4156     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4157     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4158     char to_play, board_chars[200];
4159     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4160     char black[32], white[32];
4161     Board board;
4162     int prevMove = currentMove;
4163     int ticking = 2;
4164     ChessMove moveType;
4165     int fromX, fromY, toX, toY;
4166     char promoChar;
4167     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4168     char *bookHit = NULL; // [HGM] book
4169     Boolean weird = FALSE, reqFlag = FALSE;
4170
4171     fromX = fromY = toX = toY = -1;
4172
4173     newGame = FALSE;
4174
4175     if (appData.debugMode)
4176       fprintf(debugFP, _("Parsing board: %s\n"), string);
4177
4178     move_str[0] = NULLCHAR;
4179     elapsed_time[0] = NULLCHAR;
4180     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4181         int  i = 0, j;
4182         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4183             if(string[i] == ' ') { ranks++; files = 0; }
4184             else files++;
4185             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4186             i++;
4187         }
4188         for(j = 0; j <i; j++) board_chars[j] = string[j];
4189         board_chars[i] = '\0';
4190         string += i + 1;
4191     }
4192     n = sscanf(string, PATTERN, &to_play, &double_push,
4193                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4194                &gamenum, white, black, &relation, &basetime, &increment,
4195                &white_stren, &black_stren, &white_time, &black_time,
4196                &moveNum, str, elapsed_time, move_str, &ics_flip,
4197                &ticking);
4198
4199     if (n < 21) {
4200         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4201         DisplayError(str, 0);
4202         return;
4203     }
4204
4205     /* Convert the move number to internal form */
4206     moveNum = (moveNum - 1) * 2;
4207     if (to_play == 'B') moveNum++;
4208     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4209       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4210                         0, 1);
4211       return;
4212     }
4213
4214     switch (relation) {
4215       case RELATION_OBSERVING_PLAYED:
4216       case RELATION_OBSERVING_STATIC:
4217         if (gamenum == -1) {
4218             /* Old ICC buglet */
4219             relation = RELATION_OBSERVING_STATIC;
4220         }
4221         newGameMode = IcsObserving;
4222         break;
4223       case RELATION_PLAYING_MYMOVE:
4224       case RELATION_PLAYING_NOTMYMOVE:
4225         newGameMode =
4226           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4227             IcsPlayingWhite : IcsPlayingBlack;
4228         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4229         break;
4230       case RELATION_EXAMINING:
4231         newGameMode = IcsExamining;
4232         break;
4233       case RELATION_ISOLATED_BOARD:
4234       default:
4235         /* Just display this board.  If user was doing something else,
4236            we will forget about it until the next board comes. */
4237         newGameMode = IcsIdle;
4238         break;
4239       case RELATION_STARTING_POSITION:
4240         newGameMode = gameMode;
4241         break;
4242     }
4243
4244     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4245          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4246       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4247       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4248       char *toSqr;
4249       for (k = 0; k < ranks; k++) {
4250         for (j = 0; j < files; j++)
4251           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4252         if(gameInfo.holdingsWidth > 1) {
4253              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4254              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4255         }
4256       }
4257       CopyBoard(partnerBoard, board);
4258       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4259         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4260         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4261       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4262       if(toSqr = strchr(str, '-')) {
4263         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4264         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4265       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4266       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4267       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4268       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4269       if(twoBoards) {
4270           DisplayWhiteClock(white_time*fac, to_play == 'W');
4271           DisplayBlackClock(black_time*fac, to_play != 'W');
4272           activePartner = to_play;
4273           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4274                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4275       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4276                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4277       DisplayMessage(partnerStatus, "");
4278         partnerBoardValid = TRUE;
4279       return;
4280     }
4281
4282     /* Modify behavior for initial board display on move listing
4283        of wild games.
4284        */
4285     switch (ics_getting_history) {
4286       case H_FALSE:
4287       case H_REQUESTED:
4288         break;
4289       case H_GOT_REQ_HEADER:
4290       case H_GOT_UNREQ_HEADER:
4291         /* This is the initial position of the current game */
4292         gamenum = ics_gamenum;
4293         moveNum = 0;            /* old ICS bug workaround */
4294         if (to_play == 'B') {
4295           startedFromSetupPosition = TRUE;
4296           blackPlaysFirst = TRUE;
4297           moveNum = 1;
4298           if (forwardMostMove == 0) forwardMostMove = 1;
4299           if (backwardMostMove == 0) backwardMostMove = 1;
4300           if (currentMove == 0) currentMove = 1;
4301         }
4302         newGameMode = gameMode;
4303         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4304         break;
4305       case H_GOT_UNWANTED_HEADER:
4306         /* This is an initial board that we don't want */
4307         return;
4308       case H_GETTING_MOVES:
4309         /* Should not happen */
4310         DisplayError(_("Error gathering move list: extra board"), 0);
4311         ics_getting_history = H_FALSE;
4312         return;
4313     }
4314
4315    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4316                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4317      /* [HGM] We seem to have switched variant unexpectedly
4318       * Try to guess new variant from board size
4319       */
4320           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4321           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4322           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4323           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4324           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4325           if(!weird) newVariant = VariantNormal;
4326           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4327           /* Get a move list just to see the header, which
4328              will tell us whether this is really bug or zh */
4329           if (ics_getting_history == H_FALSE) {
4330             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4331             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4332             SendToICS(str);
4333           }
4334     }
4335
4336     /* Take action if this is the first board of a new game, or of a
4337        different game than is currently being displayed.  */
4338     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4339         relation == RELATION_ISOLATED_BOARD) {
4340
4341         /* Forget the old game and get the history (if any) of the new one */
4342         if (gameMode != BeginningOfGame) {
4343           Reset(TRUE, TRUE);
4344         }
4345         newGame = TRUE;
4346         if (appData.autoRaiseBoard) BoardToTop();
4347         prevMove = -3;
4348         if (gamenum == -1) {
4349             newGameMode = IcsIdle;
4350         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4351                    appData.getMoveList && !reqFlag) {
4352             /* Need to get game history */
4353             ics_getting_history = H_REQUESTED;
4354             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4355             SendToICS(str);
4356         }
4357
4358         /* Initially flip the board to have black on the bottom if playing
4359            black or if the ICS flip flag is set, but let the user change
4360            it with the Flip View button. */
4361         flipView = appData.autoFlipView ?
4362           (newGameMode == IcsPlayingBlack) || ics_flip :
4363           appData.flipView;
4364
4365         /* Done with values from previous mode; copy in new ones */
4366         gameMode = newGameMode;
4367         ModeHighlight();
4368         ics_gamenum = gamenum;
4369         if (gamenum == gs_gamenum) {
4370             int klen = strlen(gs_kind);
4371             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4372             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4373             gameInfo.event = StrSave(str);
4374         } else {
4375             gameInfo.event = StrSave("ICS game");
4376         }
4377         gameInfo.site = StrSave(appData.icsHost);
4378         gameInfo.date = PGNDate();
4379         gameInfo.round = StrSave("-");
4380         gameInfo.white = StrSave(white);
4381         gameInfo.black = StrSave(black);
4382         timeControl = basetime * 60 * 1000;
4383         timeControl_2 = 0;
4384         timeIncrement = increment * 1000;
4385         movesPerSession = 0;
4386         gameInfo.timeControl = TimeControlTagValue();
4387         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4388   if (appData.debugMode) {
4389     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4390     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4391     setbuf(debugFP, NULL);
4392   }
4393
4394         gameInfo.outOfBook = NULL;
4395
4396         /* Do we have the ratings? */
4397         if (strcmp(player1Name, white) == 0 &&
4398             strcmp(player2Name, black) == 0) {
4399             if (appData.debugMode)
4400               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4401                       player1Rating, player2Rating);
4402             gameInfo.whiteRating = player1Rating;
4403             gameInfo.blackRating = player2Rating;
4404         } else if (strcmp(player2Name, white) == 0 &&
4405                    strcmp(player1Name, black) == 0) {
4406             if (appData.debugMode)
4407               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4408                       player2Rating, player1Rating);
4409             gameInfo.whiteRating = player2Rating;
4410             gameInfo.blackRating = player1Rating;
4411         }
4412         player1Name[0] = player2Name[0] = NULLCHAR;
4413
4414         /* Silence shouts if requested */
4415         if (appData.quietPlay &&
4416             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4417             SendToICS(ics_prefix);
4418             SendToICS("set shout 0\n");
4419         }
4420     }
4421
4422     /* Deal with midgame name changes */
4423     if (!newGame) {
4424         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4425             if (gameInfo.white) free(gameInfo.white);
4426             gameInfo.white = StrSave(white);
4427         }
4428         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4429             if (gameInfo.black) free(gameInfo.black);
4430             gameInfo.black = StrSave(black);
4431         }
4432     }
4433
4434     /* Throw away game result if anything actually changes in examine mode */
4435     if (gameMode == IcsExamining && !newGame) {
4436         gameInfo.result = GameUnfinished;
4437         if (gameInfo.resultDetails != NULL) {
4438             free(gameInfo.resultDetails);
4439             gameInfo.resultDetails = NULL;
4440         }
4441     }
4442
4443     /* In pausing && IcsExamining mode, we ignore boards coming
4444        in if they are in a different variation than we are. */
4445     if (pauseExamInvalid) return;
4446     if (pausing && gameMode == IcsExamining) {
4447         if (moveNum <= pauseExamForwardMostMove) {
4448             pauseExamInvalid = TRUE;
4449             forwardMostMove = pauseExamForwardMostMove;
4450             return;
4451         }
4452     }
4453
4454   if (appData.debugMode) {
4455     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4456   }
4457     /* Parse the board */
4458     for (k = 0; k < ranks; k++) {
4459       for (j = 0; j < files; j++)
4460         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4461       if(gameInfo.holdingsWidth > 1) {
4462            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4463            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4464       }
4465     }
4466     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4467       board[5][BOARD_RGHT+1] = WhiteAngel;
4468       board[6][BOARD_RGHT+1] = WhiteMarshall;
4469       board[1][0] = BlackMarshall;
4470       board[2][0] = BlackAngel;
4471       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4472     }
4473     CopyBoard(boards[moveNum], board);
4474     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4475     if (moveNum == 0) {
4476         startedFromSetupPosition =
4477           !CompareBoards(board, initialPosition);
4478         if(startedFromSetupPosition)
4479             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4480     }
4481
4482     /* [HGM] Set castling rights. Take the outermost Rooks,
4483        to make it also work for FRC opening positions. Note that board12
4484        is really defective for later FRC positions, as it has no way to
4485        indicate which Rook can castle if they are on the same side of King.
4486        For the initial position we grant rights to the outermost Rooks,
4487        and remember thos rights, and we then copy them on positions
4488        later in an FRC game. This means WB might not recognize castlings with
4489        Rooks that have moved back to their original position as illegal,
4490        but in ICS mode that is not its job anyway.
4491     */
4492     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4493     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4494
4495         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4496             if(board[0][i] == WhiteRook) j = i;
4497         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4498         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4499             if(board[0][i] == WhiteRook) j = i;
4500         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4501         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4502             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4503         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4504         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4505             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4506         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4507
4508         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4509         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4510         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4511             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4512         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4513             if(board[BOARD_HEIGHT-1][k] == bKing)
4514                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4515         if(gameInfo.variant == VariantTwoKings) {
4516             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4517             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4518             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4519         }
4520     } else { int r;
4521         r = boards[moveNum][CASTLING][0] = initialRights[0];
4522         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4523         r = boards[moveNum][CASTLING][1] = initialRights[1];
4524         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4525         r = boards[moveNum][CASTLING][3] = initialRights[3];
4526         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4527         r = boards[moveNum][CASTLING][4] = initialRights[4];
4528         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4529         /* wildcastle kludge: always assume King has rights */
4530         r = boards[moveNum][CASTLING][2] = initialRights[2];
4531         r = boards[moveNum][CASTLING][5] = initialRights[5];
4532     }
4533     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4534     boards[moveNum][EP_STATUS] = EP_NONE;
4535     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4536     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4537     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4538
4539
4540     if (ics_getting_history == H_GOT_REQ_HEADER ||
4541         ics_getting_history == H_GOT_UNREQ_HEADER) {
4542         /* This was an initial position from a move list, not
4543            the current position */
4544         return;
4545     }
4546
4547     /* Update currentMove and known move number limits */
4548     newMove = newGame || moveNum > forwardMostMove;
4549
4550     if (newGame) {
4551         forwardMostMove = backwardMostMove = currentMove = moveNum;
4552         if (gameMode == IcsExamining && moveNum == 0) {
4553           /* Workaround for ICS limitation: we are not told the wild
4554              type when starting to examine a game.  But if we ask for
4555              the move list, the move list header will tell us */
4556             ics_getting_history = H_REQUESTED;
4557             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4558             SendToICS(str);
4559         }
4560     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4561                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4562 #if ZIPPY
4563         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4564         /* [HGM] applied this also to an engine that is silently watching        */
4565         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4566             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4567             gameInfo.variant == currentlyInitializedVariant) {
4568           takeback = forwardMostMove - moveNum;
4569           for (i = 0; i < takeback; i++) {
4570             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4571             SendToProgram("undo\n", &first);
4572           }
4573         }
4574 #endif
4575
4576         forwardMostMove = moveNum;
4577         if (!pausing || currentMove > forwardMostMove)
4578           currentMove = forwardMostMove;
4579     } else {
4580         /* New part of history that is not contiguous with old part */
4581         if (pausing && gameMode == IcsExamining) {
4582             pauseExamInvalid = TRUE;
4583             forwardMostMove = pauseExamForwardMostMove;
4584             return;
4585         }
4586         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4587 #if ZIPPY
4588             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4589                 // [HGM] when we will receive the move list we now request, it will be
4590                 // fed to the engine from the first move on. So if the engine is not
4591                 // in the initial position now, bring it there.
4592                 InitChessProgram(&first, 0);
4593             }
4594 #endif
4595             ics_getting_history = H_REQUESTED;
4596             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4597             SendToICS(str);
4598         }
4599         forwardMostMove = backwardMostMove = currentMove = moveNum;
4600     }
4601
4602     /* Update the clocks */
4603     if (strchr(elapsed_time, '.')) {
4604       /* Time is in ms */
4605       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4606       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4607     } else {
4608       /* Time is in seconds */
4609       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4610       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4611     }
4612
4613
4614 #if ZIPPY
4615     if (appData.zippyPlay && newGame &&
4616         gameMode != IcsObserving && gameMode != IcsIdle &&
4617         gameMode != IcsExamining)
4618       ZippyFirstBoard(moveNum, basetime, increment);
4619 #endif
4620
4621     /* Put the move on the move list, first converting
4622        to canonical algebraic form. */
4623     if (moveNum > 0) {
4624   if (appData.debugMode) {
4625     if (appData.debugMode) { int f = forwardMostMove;
4626         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4627                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4628                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4629     }
4630     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4631     fprintf(debugFP, "moveNum = %d\n", moveNum);
4632     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4633     setbuf(debugFP, NULL);
4634   }
4635         if (moveNum <= backwardMostMove) {
4636             /* We don't know what the board looked like before
4637                this move.  Punt. */
4638           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4639             strcat(parseList[moveNum - 1], " ");
4640             strcat(parseList[moveNum - 1], elapsed_time);
4641             moveList[moveNum - 1][0] = NULLCHAR;
4642         } else if (strcmp(move_str, "none") == 0) {
4643             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4644             /* Again, we don't know what the board looked like;
4645                this is really the start of the game. */
4646             parseList[moveNum - 1][0] = NULLCHAR;
4647             moveList[moveNum - 1][0] = NULLCHAR;
4648             backwardMostMove = moveNum;
4649             startedFromSetupPosition = TRUE;
4650             fromX = fromY = toX = toY = -1;
4651         } else {
4652           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4653           //                 So we parse the long-algebraic move string in stead of the SAN move
4654           int valid; char buf[MSG_SIZ], *prom;
4655
4656           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4657                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4658           // str looks something like "Q/a1-a2"; kill the slash
4659           if(str[1] == '/')
4660             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4661           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4662           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4663                 strcat(buf, prom); // long move lacks promo specification!
4664           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4665                 if(appData.debugMode)
4666                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4667                 safeStrCpy(move_str, buf, MSG_SIZ);
4668           }
4669           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4670                                 &fromX, &fromY, &toX, &toY, &promoChar)
4671                || ParseOneMove(buf, moveNum - 1, &moveType,
4672                                 &fromX, &fromY, &toX, &toY, &promoChar);
4673           // end of long SAN patch
4674           if (valid) {
4675             (void) CoordsToAlgebraic(boards[moveNum - 1],
4676                                      PosFlags(moveNum - 1),
4677                                      fromY, fromX, toY, toX, promoChar,
4678                                      parseList[moveNum-1]);
4679             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4680               case MT_NONE:
4681               case MT_STALEMATE:
4682               default:
4683                 break;
4684               case MT_CHECK:
4685                 if(gameInfo.variant != VariantShogi)
4686                     strcat(parseList[moveNum - 1], "+");
4687                 break;
4688               case MT_CHECKMATE:
4689               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4690                 strcat(parseList[moveNum - 1], "#");
4691                 break;
4692             }
4693             strcat(parseList[moveNum - 1], " ");
4694             strcat(parseList[moveNum - 1], elapsed_time);
4695             /* currentMoveString is set as a side-effect of ParseOneMove */
4696             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4697             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4698             strcat(moveList[moveNum - 1], "\n");
4699
4700             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4701                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4702               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4703                 ChessSquare old, new = boards[moveNum][k][j];
4704                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4705                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4706                   if(old == new) continue;
4707                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4708                   else if(new == WhiteWazir || new == BlackWazir) {
4709                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4710                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4711                       else boards[moveNum][k][j] = old; // preserve type of Gold
4712                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4713                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4714               }
4715           } else {
4716             /* Move from ICS was illegal!?  Punt. */
4717             if (appData.debugMode) {
4718               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4719               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4720             }
4721             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4722             strcat(parseList[moveNum - 1], " ");
4723             strcat(parseList[moveNum - 1], elapsed_time);
4724             moveList[moveNum - 1][0] = NULLCHAR;
4725             fromX = fromY = toX = toY = -1;
4726           }
4727         }
4728   if (appData.debugMode) {
4729     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4730     setbuf(debugFP, NULL);
4731   }
4732
4733 #if ZIPPY
4734         /* Send move to chess program (BEFORE animating it). */
4735         if (appData.zippyPlay && !newGame && newMove &&
4736            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4737
4738             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4739                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4740                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4741                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4742                             move_str);
4743                     DisplayError(str, 0);
4744                 } else {
4745                     if (first.sendTime) {
4746                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4747                     }
4748                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4749                     if (firstMove && !bookHit) {
4750                         firstMove = FALSE;
4751                         if (first.useColors) {
4752                           SendToProgram(gameMode == IcsPlayingWhite ?
4753                                         "white\ngo\n" :
4754                                         "black\ngo\n", &first);
4755                         } else {
4756                           SendToProgram("go\n", &first);
4757                         }
4758                         first.maybeThinking = TRUE;
4759                     }
4760                 }
4761             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4762               if (moveList[moveNum - 1][0] == NULLCHAR) {
4763                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4764                 DisplayError(str, 0);
4765               } else {
4766                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4767                 SendMoveToProgram(moveNum - 1, &first);
4768               }
4769             }
4770         }
4771 #endif
4772     }
4773
4774     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4775         /* If move comes from a remote source, animate it.  If it
4776            isn't remote, it will have already been animated. */
4777         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4778             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4779         }
4780         if (!pausing && appData.highlightLastMove) {
4781             SetHighlights(fromX, fromY, toX, toY);
4782         }
4783     }
4784
4785     /* Start the clocks */
4786     whiteFlag = blackFlag = FALSE;
4787     appData.clockMode = !(basetime == 0 && increment == 0);
4788     if (ticking == 0) {
4789       ics_clock_paused = TRUE;
4790       StopClocks();
4791     } else if (ticking == 1) {
4792       ics_clock_paused = FALSE;
4793     }
4794     if (gameMode == IcsIdle ||
4795         relation == RELATION_OBSERVING_STATIC ||
4796         relation == RELATION_EXAMINING ||
4797         ics_clock_paused)
4798       DisplayBothClocks();
4799     else
4800       StartClocks();
4801
4802     /* Display opponents and material strengths */
4803     if (gameInfo.variant != VariantBughouse &&
4804         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4805         if (tinyLayout || smallLayout) {
4806             if(gameInfo.variant == VariantNormal)
4807               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4808                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4809                     basetime, increment);
4810             else
4811               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4812                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4813                     basetime, increment, (int) gameInfo.variant);
4814         } else {
4815             if(gameInfo.variant == VariantNormal)
4816               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4817                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4818                     basetime, increment);
4819             else
4820               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4821                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4822                     basetime, increment, VariantName(gameInfo.variant));
4823         }
4824         DisplayTitle(str);
4825   if (appData.debugMode) {
4826     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4827   }
4828     }
4829
4830
4831     /* Display the board */
4832     if (!pausing && !appData.noGUI) {
4833
4834       if (appData.premove)
4835           if (!gotPremove ||
4836              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4837              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4838               ClearPremoveHighlights();
4839
4840       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4841         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4842       DrawPosition(j, boards[currentMove]);
4843
4844       DisplayMove(moveNum - 1);
4845       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4846             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4847               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4848         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4849       }
4850     }
4851
4852     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4853 #if ZIPPY
4854     if(bookHit) { // [HGM] book: simulate book reply
4855         static char bookMove[MSG_SIZ]; // a bit generous?
4856
4857         programStats.nodes = programStats.depth = programStats.time =
4858         programStats.score = programStats.got_only_move = 0;
4859         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4860
4861         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4862         strcat(bookMove, bookHit);
4863         HandleMachineMove(bookMove, &first);
4864     }
4865 #endif
4866 }
4867
4868 void
4869 GetMoveListEvent ()
4870 {
4871     char buf[MSG_SIZ];
4872     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4873         ics_getting_history = H_REQUESTED;
4874         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4875         SendToICS(buf);
4876     }
4877 }
4878
4879 void
4880 AnalysisPeriodicEvent (int force)
4881 {
4882     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4883          && !force) || !appData.periodicUpdates)
4884       return;
4885
4886     /* Send . command to Crafty to collect stats */
4887     SendToProgram(".\n", &first);
4888
4889     /* Don't send another until we get a response (this makes
4890        us stop sending to old Crafty's which don't understand
4891        the "." command (sending illegal cmds resets node count & time,
4892        which looks bad)) */
4893     programStats.ok_to_send = 0;
4894 }
4895
4896 void
4897 ics_update_width (int new_width)
4898 {
4899         ics_printf("set width %d\n", new_width);
4900 }
4901
4902 void
4903 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4904 {
4905     char buf[MSG_SIZ];
4906
4907     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4908         // null move in variant where engine does not understand it (for analysis purposes)
4909         SendBoard(cps, moveNum + 1); // send position after move in stead.
4910         return;
4911     }
4912     if (cps->useUsermove) {
4913       SendToProgram("usermove ", cps);
4914     }
4915     if (cps->useSAN) {
4916       char *space;
4917       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4918         int len = space - parseList[moveNum];
4919         memcpy(buf, parseList[moveNum], len);
4920         buf[len++] = '\n';
4921         buf[len] = NULLCHAR;
4922       } else {
4923         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4924       }
4925       SendToProgram(buf, cps);
4926     } else {
4927       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4928         AlphaRank(moveList[moveNum], 4);
4929         SendToProgram(moveList[moveNum], cps);
4930         AlphaRank(moveList[moveNum], 4); // and back
4931       } else
4932       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4933        * the engine. It would be nice to have a better way to identify castle
4934        * moves here. */
4935       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4936                                                                          && cps->useOOCastle) {
4937         int fromX = moveList[moveNum][0] - AAA;
4938         int fromY = moveList[moveNum][1] - ONE;
4939         int toX = moveList[moveNum][2] - AAA;
4940         int toY = moveList[moveNum][3] - ONE;
4941         if((boards[moveNum][fromY][fromX] == WhiteKing
4942             && boards[moveNum][toY][toX] == WhiteRook)
4943            || (boards[moveNum][fromY][fromX] == BlackKing
4944                && boards[moveNum][toY][toX] == BlackRook)) {
4945           if(toX > fromX) SendToProgram("O-O\n", cps);
4946           else SendToProgram("O-O-O\n", cps);
4947         }
4948         else SendToProgram(moveList[moveNum], cps);
4949       } else
4950       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4951         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4952           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4953           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4954                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4955         } else
4956           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4957                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4958         SendToProgram(buf, cps);
4959       }
4960       else SendToProgram(moveList[moveNum], cps);
4961       /* End of additions by Tord */
4962     }
4963
4964     /* [HGM] setting up the opening has brought engine in force mode! */
4965     /*       Send 'go' if we are in a mode where machine should play. */
4966     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4967         (gameMode == TwoMachinesPlay   ||
4968 #if ZIPPY
4969          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4970 #endif
4971          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4972         SendToProgram("go\n", cps);
4973   if (appData.debugMode) {
4974     fprintf(debugFP, "(extra)\n");
4975   }
4976     }
4977     setboardSpoiledMachineBlack = 0;
4978 }
4979
4980 void
4981 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4982 {
4983     char user_move[MSG_SIZ];
4984     char suffix[4];
4985
4986     if(gameInfo.variant == VariantSChess && promoChar) {
4987         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4988         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4989     } else suffix[0] = NULLCHAR;
4990
4991     switch (moveType) {
4992       default:
4993         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4994                 (int)moveType, fromX, fromY, toX, toY);
4995         DisplayError(user_move + strlen("say "), 0);
4996         break;
4997       case WhiteKingSideCastle:
4998       case BlackKingSideCastle:
4999       case WhiteQueenSideCastleWild:
5000       case BlackQueenSideCastleWild:
5001       /* PUSH Fabien */
5002       case WhiteHSideCastleFR:
5003       case BlackHSideCastleFR:
5004       /* POP Fabien */
5005         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5006         break;
5007       case WhiteQueenSideCastle:
5008       case BlackQueenSideCastle:
5009       case WhiteKingSideCastleWild:
5010       case BlackKingSideCastleWild:
5011       /* PUSH Fabien */
5012       case WhiteASideCastleFR:
5013       case BlackASideCastleFR:
5014       /* POP Fabien */
5015         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5016         break;
5017       case WhiteNonPromotion:
5018       case BlackNonPromotion:
5019         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5020         break;
5021       case WhitePromotion:
5022       case BlackPromotion:
5023         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5024           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5025                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5026                 PieceToChar(WhiteFerz));
5027         else if(gameInfo.variant == VariantGreat)
5028           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5029                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5030                 PieceToChar(WhiteMan));
5031         else
5032           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5033                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5034                 promoChar);
5035         break;
5036       case WhiteDrop:
5037       case BlackDrop:
5038       drop:
5039         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5040                  ToUpper(PieceToChar((ChessSquare) fromX)),
5041                  AAA + toX, ONE + toY);
5042         break;
5043       case IllegalMove:  /* could be a variant we don't quite understand */
5044         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5045       case NormalMove:
5046       case WhiteCapturesEnPassant:
5047       case BlackCapturesEnPassant:
5048         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5049                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5050         break;
5051     }
5052     SendToICS(user_move);
5053     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5054         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5055 }
5056
5057 void
5058 UploadGameEvent ()
5059 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5060     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5061     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5062     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5063       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5064       return;
5065     }
5066     if(gameMode != IcsExamining) { // is this ever not the case?
5067         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5068
5069         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5070           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5071         } else { // on FICS we must first go to general examine mode
5072           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5073         }
5074         if(gameInfo.variant != VariantNormal) {
5075             // try figure out wild number, as xboard names are not always valid on ICS
5076             for(i=1; i<=36; i++) {
5077               snprintf(buf, MSG_SIZ, "wild/%d", i);
5078                 if(StringToVariant(buf) == gameInfo.variant) break;
5079             }
5080             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5081             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5082             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5083         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5084         SendToICS(ics_prefix);
5085         SendToICS(buf);
5086         if(startedFromSetupPosition || backwardMostMove != 0) {
5087           fen = PositionToFEN(backwardMostMove, NULL);
5088           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5089             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5090             SendToICS(buf);
5091           } else { // FICS: everything has to set by separate bsetup commands
5092             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5093             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5094             SendToICS(buf);
5095             if(!WhiteOnMove(backwardMostMove)) {
5096                 SendToICS("bsetup tomove black\n");
5097             }
5098             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5099             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5100             SendToICS(buf);
5101             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5102             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5103             SendToICS(buf);
5104             i = boards[backwardMostMove][EP_STATUS];
5105             if(i >= 0) { // set e.p.
5106               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5107                 SendToICS(buf);
5108             }
5109             bsetup++;
5110           }
5111         }
5112       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5113             SendToICS("bsetup done\n"); // switch to normal examining.
5114     }
5115     for(i = backwardMostMove; i<last; i++) {
5116         char buf[20];
5117         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5118         SendToICS(buf);
5119     }
5120     SendToICS(ics_prefix);
5121     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5122 }
5123
5124 void
5125 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5126 {
5127     if (rf == DROP_RANK) {
5128       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5129       sprintf(move, "%c@%c%c\n",
5130                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5131     } else {
5132         if (promoChar == 'x' || promoChar == NULLCHAR) {
5133           sprintf(move, "%c%c%c%c\n",
5134                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5135         } else {
5136             sprintf(move, "%c%c%c%c%c\n",
5137                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5138         }
5139     }
5140 }
5141
5142 void
5143 ProcessICSInitScript (FILE *f)
5144 {
5145     char buf[MSG_SIZ];
5146
5147     while (fgets(buf, MSG_SIZ, f)) {
5148         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5149     }
5150
5151     fclose(f);
5152 }
5153
5154
5155 static int lastX, lastY, selectFlag, dragging;
5156
5157 void
5158 Sweep (int step)
5159 {
5160     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5161     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5162     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5163     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5164     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5165     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5166     do {
5167         promoSweep -= step;
5168         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5169         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5170         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5171         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5172         if(!step) step = -1;
5173     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5174             appData.testLegality && (promoSweep == king ||
5175             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5176     ChangeDragPiece(promoSweep);
5177 }
5178
5179 int
5180 PromoScroll (int x, int y)
5181 {
5182   int step = 0;
5183
5184   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5185   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5186   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5187   if(!step) return FALSE;
5188   lastX = x; lastY = y;
5189   if((promoSweep < BlackPawn) == flipView) step = -step;
5190   if(step > 0) selectFlag = 1;
5191   if(!selectFlag) Sweep(step);
5192   return FALSE;
5193 }
5194
5195 void
5196 NextPiece (int step)
5197 {
5198     ChessSquare piece = boards[currentMove][toY][toX];
5199     do {
5200         pieceSweep -= step;
5201         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5202         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5203         if(!step) step = -1;
5204     } while(PieceToChar(pieceSweep) == '.');
5205     boards[currentMove][toY][toX] = pieceSweep;
5206     DrawPosition(FALSE, boards[currentMove]);
5207     boards[currentMove][toY][toX] = piece;
5208 }
5209 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5210 void
5211 AlphaRank (char *move, int n)
5212 {
5213 //    char *p = move, c; int x, y;
5214
5215     if (appData.debugMode) {
5216         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5217     }
5218
5219     if(move[1]=='*' &&
5220        move[2]>='0' && move[2]<='9' &&
5221        move[3]>='a' && move[3]<='x'    ) {
5222         move[1] = '@';
5223         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5224         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5225     } else
5226     if(move[0]>='0' && move[0]<='9' &&
5227        move[1]>='a' && move[1]<='x' &&
5228        move[2]>='0' && move[2]<='9' &&
5229        move[3]>='a' && move[3]<='x'    ) {
5230         /* input move, Shogi -> normal */
5231         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5232         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5233         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5234         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5235     } else
5236     if(move[1]=='@' &&
5237        move[3]>='0' && move[3]<='9' &&
5238        move[2]>='a' && move[2]<='x'    ) {
5239         move[1] = '*';
5240         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5241         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5242     } else
5243     if(
5244        move[0]>='a' && move[0]<='x' &&
5245        move[3]>='0' && move[3]<='9' &&
5246        move[2]>='a' && move[2]<='x'    ) {
5247          /* output move, normal -> Shogi */
5248         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5249         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5250         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5251         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5252         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5253     }
5254     if (appData.debugMode) {
5255         fprintf(debugFP, "   out = '%s'\n", move);
5256     }
5257 }
5258
5259 char yy_textstr[8000];
5260
5261 /* Parser for moves from gnuchess, ICS, or user typein box */
5262 Boolean
5263 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5264 {
5265     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5266
5267     switch (*moveType) {
5268       case WhitePromotion:
5269       case BlackPromotion:
5270       case WhiteNonPromotion:
5271       case BlackNonPromotion:
5272       case NormalMove:
5273       case WhiteCapturesEnPassant:
5274       case BlackCapturesEnPassant:
5275       case WhiteKingSideCastle:
5276       case WhiteQueenSideCastle:
5277       case BlackKingSideCastle:
5278       case BlackQueenSideCastle:
5279       case WhiteKingSideCastleWild:
5280       case WhiteQueenSideCastleWild:
5281       case BlackKingSideCastleWild:
5282       case BlackQueenSideCastleWild:
5283       /* Code added by Tord: */
5284       case WhiteHSideCastleFR:
5285       case WhiteASideCastleFR:
5286       case BlackHSideCastleFR:
5287       case BlackASideCastleFR:
5288       /* End of code added by Tord */
5289       case IllegalMove:         /* bug or odd chess variant */
5290         *fromX = currentMoveString[0] - AAA;
5291         *fromY = currentMoveString[1] - ONE;
5292         *toX = currentMoveString[2] - AAA;
5293         *toY = currentMoveString[3] - ONE;
5294         *promoChar = currentMoveString[4];
5295         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5296             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5297     if (appData.debugMode) {
5298         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5299     }
5300             *fromX = *fromY = *toX = *toY = 0;
5301             return FALSE;
5302         }
5303         if (appData.testLegality) {
5304           return (*moveType != IllegalMove);
5305         } else {
5306           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5307                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5308         }
5309
5310       case WhiteDrop:
5311       case BlackDrop:
5312         *fromX = *moveType == WhiteDrop ?
5313           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5314           (int) CharToPiece(ToLower(currentMoveString[0]));
5315         *fromY = DROP_RANK;
5316         *toX = currentMoveString[2] - AAA;
5317         *toY = currentMoveString[3] - ONE;
5318         *promoChar = NULLCHAR;
5319         return TRUE;
5320
5321       case AmbiguousMove:
5322       case ImpossibleMove:
5323       case EndOfFile:
5324       case ElapsedTime:
5325       case Comment:
5326       case PGNTag:
5327       case NAG:
5328       case WhiteWins:
5329       case BlackWins:
5330       case GameIsDrawn:
5331       default:
5332     if (appData.debugMode) {
5333         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5334     }
5335         /* bug? */
5336         *fromX = *fromY = *toX = *toY = 0;
5337         *promoChar = NULLCHAR;
5338         return FALSE;
5339     }
5340 }
5341
5342 Boolean pushed = FALSE;
5343 char *lastParseAttempt;
5344
5345 void
5346 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5347 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5348   int fromX, fromY, toX, toY; char promoChar;
5349   ChessMove moveType;
5350   Boolean valid;
5351   int nr = 0;
5352
5353   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5354     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5355     pushed = TRUE;
5356   }
5357   endPV = forwardMostMove;
5358   do {
5359     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5360     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5361     lastParseAttempt = pv;
5362     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5363     if(!valid && nr == 0 &&
5364        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5365         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5366         // Hande case where played move is different from leading PV move
5367         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5368         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5369         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5370         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5371           endPV += 2; // if position different, keep this
5372           moveList[endPV-1][0] = fromX + AAA;
5373           moveList[endPV-1][1] = fromY + ONE;
5374           moveList[endPV-1][2] = toX + AAA;
5375           moveList[endPV-1][3] = toY + ONE;
5376           parseList[endPV-1][0] = NULLCHAR;
5377           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5378         }
5379       }
5380     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5381     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5382     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5383     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5384         valid++; // allow comments in PV
5385         continue;
5386     }
5387     nr++;
5388     if(endPV+1 > framePtr) break; // no space, truncate
5389     if(!valid) break;
5390     endPV++;
5391     CopyBoard(boards[endPV], boards[endPV-1]);
5392     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5393     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5394     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5395     CoordsToAlgebraic(boards[endPV - 1],
5396                              PosFlags(endPV - 1),
5397                              fromY, fromX, toY, toX, promoChar,
5398                              parseList[endPV - 1]);
5399   } while(valid);
5400   if(atEnd == 2) return; // used hidden, for PV conversion
5401   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5402   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5403   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5404                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5405   DrawPosition(TRUE, boards[currentMove]);
5406 }
5407
5408 int
5409 MultiPV (ChessProgramState *cps)
5410 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5411         int i;
5412         for(i=0; i<cps->nrOptions; i++)
5413             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5414                 return i;
5415         return -1;
5416 }
5417
5418 Boolean
5419 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5420 {
5421         int startPV, multi, lineStart, origIndex = index;
5422         char *p, buf2[MSG_SIZ];
5423
5424         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5425         lastX = x; lastY = y;
5426         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5427         lineStart = startPV = index;
5428         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5429         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5430         index = startPV;
5431         do{ while(buf[index] && buf[index] != '\n') index++;
5432         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5433         buf[index] = 0;
5434         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5435                 int n = first.option[multi].value;
5436                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5437                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5438                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5439                 first.option[multi].value = n;
5440                 *start = *end = 0;
5441                 return FALSE;
5442         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5443                 ExcludeClick(origIndex - lineStart);
5444                 return FALSE;
5445         }
5446         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5447         *start = startPV; *end = index-1;
5448         return TRUE;
5449 }
5450
5451 char *
5452 PvToSAN (char *pv)
5453 {
5454         static char buf[10*MSG_SIZ];
5455         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5456         *buf = NULLCHAR;
5457         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5458         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5459         for(i = forwardMostMove; i<endPV; i++){
5460             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5461             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5462             k += strlen(buf+k);
5463         }
5464         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5465         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5466         endPV = savedEnd;
5467         return buf;
5468 }
5469
5470 Boolean
5471 LoadPV (int x, int y)
5472 { // called on right mouse click to load PV
5473   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5474   lastX = x; lastY = y;
5475   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5476   return TRUE;
5477 }
5478
5479 void
5480 UnLoadPV ()
5481 {
5482   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5483   if(endPV < 0) return;
5484   if(appData.autoCopyPV) CopyFENToClipboard();
5485   endPV = -1;
5486   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5487         Boolean saveAnimate = appData.animate;
5488         if(pushed) {
5489             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5490                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5491             } else storedGames--; // abandon shelved tail of original game
5492         }
5493         pushed = FALSE;
5494         forwardMostMove = currentMove;
5495         currentMove = oldFMM;
5496         appData.animate = FALSE;
5497         ToNrEvent(forwardMostMove);
5498         appData.animate = saveAnimate;
5499   }
5500   currentMove = forwardMostMove;
5501   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5502   ClearPremoveHighlights();
5503   DrawPosition(TRUE, boards[currentMove]);
5504 }
5505
5506 void
5507 MovePV (int x, int y, int h)
5508 { // step through PV based on mouse coordinates (called on mouse move)
5509   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5510
5511   // we must somehow check if right button is still down (might be released off board!)
5512   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5513   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5514   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5515   if(!step) return;
5516   lastX = x; lastY = y;
5517
5518   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5519   if(endPV < 0) return;
5520   if(y < margin) step = 1; else
5521   if(y > h - margin) step = -1;
5522   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5523   currentMove += step;
5524   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5525   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5526                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5527   DrawPosition(FALSE, boards[currentMove]);
5528 }
5529
5530
5531 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5532 // All positions will have equal probability, but the current method will not provide a unique
5533 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5534 #define DARK 1
5535 #define LITE 2
5536 #define ANY 3
5537
5538 int squaresLeft[4];
5539 int piecesLeft[(int)BlackPawn];
5540 int seed, nrOfShuffles;
5541
5542 void
5543 GetPositionNumber ()
5544 {       // sets global variable seed
5545         int i;
5546
5547         seed = appData.defaultFrcPosition;
5548         if(seed < 0) { // randomize based on time for negative FRC position numbers
5549                 for(i=0; i<50; i++) seed += random();
5550                 seed = random() ^ random() >> 8 ^ random() << 8;
5551                 if(seed<0) seed = -seed;
5552         }
5553 }
5554
5555 int
5556 put (Board board, int pieceType, int rank, int n, int shade)
5557 // put the piece on the (n-1)-th empty squares of the given shade
5558 {
5559         int i;
5560
5561         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5562                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5563                         board[rank][i] = (ChessSquare) pieceType;
5564                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5565                         squaresLeft[ANY]--;
5566                         piecesLeft[pieceType]--;
5567                         return i;
5568                 }
5569         }
5570         return -1;
5571 }
5572
5573
5574 void
5575 AddOnePiece (Board board, int pieceType, int rank, int shade)
5576 // calculate where the next piece goes, (any empty square), and put it there
5577 {
5578         int i;
5579
5580         i = seed % squaresLeft[shade];
5581         nrOfShuffles *= squaresLeft[shade];
5582         seed /= squaresLeft[shade];
5583         put(board, pieceType, rank, i, shade);
5584 }
5585
5586 void
5587 AddTwoPieces (Board board, int pieceType, int rank)
5588 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5589 {
5590         int i, n=squaresLeft[ANY], j=n-1, k;
5591
5592         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5593         i = seed % k;  // pick one
5594         nrOfShuffles *= k;
5595         seed /= k;
5596         while(i >= j) i -= j--;
5597         j = n - 1 - j; i += j;
5598         put(board, pieceType, rank, j, ANY);
5599         put(board, pieceType, rank, i, ANY);
5600 }
5601
5602 void
5603 SetUpShuffle (Board board, int number)
5604 {
5605         int i, p, first=1;
5606
5607         GetPositionNumber(); nrOfShuffles = 1;
5608
5609         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5610         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5611         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5612
5613         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5614
5615         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5616             p = (int) board[0][i];
5617             if(p < (int) BlackPawn) piecesLeft[p] ++;
5618             board[0][i] = EmptySquare;
5619         }
5620
5621         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5622             // shuffles restricted to allow normal castling put KRR first
5623             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5624                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5625             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5626                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5627             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5628                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5629             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5630                 put(board, WhiteRook, 0, 0, ANY);
5631             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5632         }
5633
5634         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5635             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5636             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5637                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5638                 while(piecesLeft[p] >= 2) {
5639                     AddOnePiece(board, p, 0, LITE);
5640                     AddOnePiece(board, p, 0, DARK);
5641                 }
5642                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5643             }
5644
5645         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5646             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5647             // but we leave King and Rooks for last, to possibly obey FRC restriction
5648             if(p == (int)WhiteRook) continue;
5649             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5650             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5651         }
5652
5653         // now everything is placed, except perhaps King (Unicorn) and Rooks
5654
5655         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5656             // Last King gets castling rights
5657             while(piecesLeft[(int)WhiteUnicorn]) {
5658                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5659                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5660             }
5661
5662             while(piecesLeft[(int)WhiteKing]) {
5663                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5664                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5665             }
5666
5667
5668         } else {
5669             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5670             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5671         }
5672
5673         // Only Rooks can be left; simply place them all
5674         while(piecesLeft[(int)WhiteRook]) {
5675                 i = put(board, WhiteRook, 0, 0, ANY);
5676                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5677                         if(first) {
5678                                 first=0;
5679                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5680                         }
5681                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5682                 }
5683         }
5684         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5685             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5686         }
5687
5688         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5689 }
5690
5691 int
5692 SetCharTable (char *table, const char * map)
5693 /* [HGM] moved here from winboard.c because of its general usefulness */
5694 /*       Basically a safe strcpy that uses the last character as King */
5695 {
5696     int result = FALSE; int NrPieces;
5697
5698     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5699                     && NrPieces >= 12 && !(NrPieces&1)) {
5700         int i; /* [HGM] Accept even length from 12 to 34 */
5701
5702         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5703         for( i=0; i<NrPieces/2-1; i++ ) {
5704             table[i] = map[i];
5705             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5706         }
5707         table[(int) WhiteKing]  = map[NrPieces/2-1];
5708         table[(int) BlackKing]  = map[NrPieces-1];
5709
5710         result = TRUE;
5711     }
5712
5713     return result;
5714 }
5715
5716 void
5717 Prelude (Board board)
5718 {       // [HGM] superchess: random selection of exo-pieces
5719         int i, j, k; ChessSquare p;
5720         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5721
5722         GetPositionNumber(); // use FRC position number
5723
5724         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5725             SetCharTable(pieceToChar, appData.pieceToCharTable);
5726             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5727                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5728         }
5729
5730         j = seed%4;                 seed /= 4;
5731         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5732         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5733         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5734         j = seed%3 + (seed%3 >= j); seed /= 3;
5735         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5736         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5737         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5738         j = seed%3;                 seed /= 3;
5739         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5740         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5741         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5742         j = seed%2 + (seed%2 >= j); seed /= 2;
5743         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5744         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5745         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5746         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5747         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5748         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5749         put(board, exoPieces[0],    0, 0, ANY);
5750         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5751 }
5752
5753 void
5754 InitPosition (int redraw)
5755 {
5756     ChessSquare (* pieces)[BOARD_FILES];
5757     int i, j, pawnRow, overrule,
5758     oldx = gameInfo.boardWidth,
5759     oldy = gameInfo.boardHeight,
5760     oldh = gameInfo.holdingsWidth;
5761     static int oldv;
5762
5763     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5764
5765     /* [AS] Initialize pv info list [HGM] and game status */
5766     {
5767         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5768             pvInfoList[i].depth = 0;
5769             boards[i][EP_STATUS] = EP_NONE;
5770             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5771         }
5772
5773         initialRulePlies = 0; /* 50-move counter start */
5774
5775         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5776         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5777     }
5778
5779
5780     /* [HGM] logic here is completely changed. In stead of full positions */
5781     /* the initialized data only consist of the two backranks. The switch */
5782     /* selects which one we will use, which is than copied to the Board   */
5783     /* initialPosition, which for the rest is initialized by Pawns and    */
5784     /* empty squares. This initial position is then copied to boards[0],  */
5785     /* possibly after shuffling, so that it remains available.            */
5786
5787     gameInfo.holdingsWidth = 0; /* default board sizes */
5788     gameInfo.boardWidth    = 8;
5789     gameInfo.boardHeight   = 8;
5790     gameInfo.holdingsSize  = 0;
5791     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5792     for(i=0; i<BOARD_FILES-2; i++)
5793       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5794     initialPosition[EP_STATUS] = EP_NONE;
5795     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5796     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5797          SetCharTable(pieceNickName, appData.pieceNickNames);
5798     else SetCharTable(pieceNickName, "............");
5799     pieces = FIDEArray;
5800
5801     switch (gameInfo.variant) {
5802     case VariantFischeRandom:
5803       shuffleOpenings = TRUE;
5804     default:
5805       break;
5806     case VariantShatranj:
5807       pieces = ShatranjArray;
5808       nrCastlingRights = 0;
5809       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5810       break;
5811     case VariantMakruk:
5812       pieces = makrukArray;
5813       nrCastlingRights = 0;
5814       startedFromSetupPosition = TRUE;
5815       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5816       break;
5817     case VariantTwoKings:
5818       pieces = twoKingsArray;
5819       break;
5820     case VariantGrand:
5821       pieces = GrandArray;
5822       nrCastlingRights = 0;
5823       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5824       gameInfo.boardWidth = 10;
5825       gameInfo.boardHeight = 10;
5826       gameInfo.holdingsSize = 7;
5827       break;
5828     case VariantCapaRandom:
5829       shuffleOpenings = TRUE;
5830     case VariantCapablanca:
5831       pieces = CapablancaArray;
5832       gameInfo.boardWidth = 10;
5833       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5834       break;
5835     case VariantGothic:
5836       pieces = GothicArray;
5837       gameInfo.boardWidth = 10;
5838       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5839       break;
5840     case VariantSChess:
5841       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5842       gameInfo.holdingsSize = 7;
5843       break;
5844     case VariantJanus:
5845       pieces = JanusArray;
5846       gameInfo.boardWidth = 10;
5847       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5848       nrCastlingRights = 6;
5849         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5850         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5851         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5852         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5853         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5854         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5855       break;
5856     case VariantFalcon:
5857       pieces = FalconArray;
5858       gameInfo.boardWidth = 10;
5859       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5860       break;
5861     case VariantXiangqi:
5862       pieces = XiangqiArray;
5863       gameInfo.boardWidth  = 9;
5864       gameInfo.boardHeight = 10;
5865       nrCastlingRights = 0;
5866       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5867       break;
5868     case VariantShogi:
5869       pieces = ShogiArray;
5870       gameInfo.boardWidth  = 9;
5871       gameInfo.boardHeight = 9;
5872       gameInfo.holdingsSize = 7;
5873       nrCastlingRights = 0;
5874       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5875       break;
5876     case VariantCourier:
5877       pieces = CourierArray;
5878       gameInfo.boardWidth  = 12;
5879       nrCastlingRights = 0;
5880       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5881       break;
5882     case VariantKnightmate:
5883       pieces = KnightmateArray;
5884       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5885       break;
5886     case VariantSpartan:
5887       pieces = SpartanArray;
5888       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5889       break;
5890     case VariantFairy:
5891       pieces = fairyArray;
5892       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5893       break;
5894     case VariantGreat:
5895       pieces = GreatArray;
5896       gameInfo.boardWidth = 10;
5897       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5898       gameInfo.holdingsSize = 8;
5899       break;
5900     case VariantSuper:
5901       pieces = FIDEArray;
5902       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5903       gameInfo.holdingsSize = 8;
5904       startedFromSetupPosition = TRUE;
5905       break;
5906     case VariantCrazyhouse:
5907     case VariantBughouse:
5908       pieces = FIDEArray;
5909       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5910       gameInfo.holdingsSize = 5;
5911       break;
5912     case VariantWildCastle:
5913       pieces = FIDEArray;
5914       /* !!?shuffle with kings guaranteed to be on d or e file */
5915       shuffleOpenings = 1;
5916       break;
5917     case VariantNoCastle:
5918       pieces = FIDEArray;
5919       nrCastlingRights = 0;
5920       /* !!?unconstrained back-rank shuffle */
5921       shuffleOpenings = 1;
5922       break;
5923     }
5924
5925     overrule = 0;
5926     if(appData.NrFiles >= 0) {
5927         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5928         gameInfo.boardWidth = appData.NrFiles;
5929     }
5930     if(appData.NrRanks >= 0) {
5931         gameInfo.boardHeight = appData.NrRanks;
5932     }
5933     if(appData.holdingsSize >= 0) {
5934         i = appData.holdingsSize;
5935         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5936         gameInfo.holdingsSize = i;
5937     }
5938     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5939     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5940         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5941
5942     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5943     if(pawnRow < 1) pawnRow = 1;
5944     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5945
5946     /* User pieceToChar list overrules defaults */
5947     if(appData.pieceToCharTable != NULL)
5948         SetCharTable(pieceToChar, appData.pieceToCharTable);
5949
5950     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5951
5952         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5953             s = (ChessSquare) 0; /* account holding counts in guard band */
5954         for( i=0; i<BOARD_HEIGHT; i++ )
5955             initialPosition[i][j] = s;
5956
5957         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5958         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5959         initialPosition[pawnRow][j] = WhitePawn;
5960         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5961         if(gameInfo.variant == VariantXiangqi) {
5962             if(j&1) {
5963                 initialPosition[pawnRow][j] =
5964                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5965                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5966                    initialPosition[2][j] = WhiteCannon;
5967                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5968                 }
5969             }
5970         }
5971         if(gameInfo.variant == VariantGrand) {
5972             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5973                initialPosition[0][j] = WhiteRook;
5974                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5975             }
5976         }
5977         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5978     }
5979     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5980
5981             j=BOARD_LEFT+1;
5982             initialPosition[1][j] = WhiteBishop;
5983             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5984             j=BOARD_RGHT-2;
5985             initialPosition[1][j] = WhiteRook;
5986             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5987     }
5988
5989     if( nrCastlingRights == -1) {
5990         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5991         /*       This sets default castling rights from none to normal corners   */
5992         /* Variants with other castling rights must set them themselves above    */
5993         nrCastlingRights = 6;
5994
5995         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5996         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5997         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5998         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5999         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6000         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6001      }
6002
6003      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6004      if(gameInfo.variant == VariantGreat) { // promotion commoners
6005         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6006         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6007         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6008         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6009      }
6010      if( gameInfo.variant == VariantSChess ) {
6011       initialPosition[1][0] = BlackMarshall;
6012       initialPosition[2][0] = BlackAngel;
6013       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6014       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6015       initialPosition[1][1] = initialPosition[2][1] = 
6016       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6017      }
6018   if (appData.debugMode) {
6019     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6020   }
6021     if(shuffleOpenings) {
6022         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6023         startedFromSetupPosition = TRUE;
6024     }
6025     if(startedFromPositionFile) {
6026       /* [HGM] loadPos: use PositionFile for every new game */
6027       CopyBoard(initialPosition, filePosition);
6028       for(i=0; i<nrCastlingRights; i++)
6029           initialRights[i] = filePosition[CASTLING][i];
6030       startedFromSetupPosition = TRUE;
6031     }
6032
6033     CopyBoard(boards[0], initialPosition);
6034
6035     if(oldx != gameInfo.boardWidth ||
6036        oldy != gameInfo.boardHeight ||
6037        oldv != gameInfo.variant ||
6038        oldh != gameInfo.holdingsWidth
6039                                          )
6040             InitDrawingSizes(-2 ,0);
6041
6042     oldv = gameInfo.variant;
6043     if (redraw)
6044       DrawPosition(TRUE, boards[currentMove]);
6045 }
6046
6047 void
6048 SendBoard (ChessProgramState *cps, int moveNum)
6049 {
6050     char message[MSG_SIZ];
6051
6052     if (cps->useSetboard) {
6053       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6054       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6055       SendToProgram(message, cps);
6056       free(fen);
6057
6058     } else {
6059       ChessSquare *bp;
6060       int i, j, left=0, right=BOARD_WIDTH;
6061       /* Kludge to set black to move, avoiding the troublesome and now
6062        * deprecated "black" command.
6063        */
6064       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6065         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6066
6067       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6068
6069       SendToProgram("edit\n", cps);
6070       SendToProgram("#\n", cps);
6071       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6072         bp = &boards[moveNum][i][left];
6073         for (j = left; j < right; j++, bp++) {
6074           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6075           if ((int) *bp < (int) BlackPawn) {
6076             if(j == BOARD_RGHT+1)
6077                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6078             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6079             if(message[0] == '+' || message[0] == '~') {
6080               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6081                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6082                         AAA + j, ONE + i);
6083             }
6084             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6085                 message[1] = BOARD_RGHT   - 1 - j + '1';
6086                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6087             }
6088             SendToProgram(message, cps);
6089           }
6090         }
6091       }
6092
6093       SendToProgram("c\n", cps);
6094       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6095         bp = &boards[moveNum][i][left];
6096         for (j = left; j < right; j++, bp++) {
6097           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6098           if (((int) *bp != (int) EmptySquare)
6099               && ((int) *bp >= (int) BlackPawn)) {
6100             if(j == BOARD_LEFT-2)
6101                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6102             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6103                     AAA + j, ONE + i);
6104             if(message[0] == '+' || message[0] == '~') {
6105               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6106                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6107                         AAA + j, ONE + i);
6108             }
6109             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6110                 message[1] = BOARD_RGHT   - 1 - j + '1';
6111                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6112             }
6113             SendToProgram(message, cps);
6114           }
6115         }
6116       }
6117
6118       SendToProgram(".\n", cps);
6119     }
6120     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6121 }
6122
6123 char exclusionHeader[MSG_SIZ];
6124 int exCnt, excludePtr;
6125 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6126 static Exclusion excluTab[200];
6127 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6128
6129 static void
6130 WriteMap (int s)
6131 {
6132     int j;
6133     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6134     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6135 }
6136
6137 static void
6138 ClearMap ()
6139 {
6140     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6141     excludePtr = 24; exCnt = 0;
6142     WriteMap(0);
6143 }
6144
6145 static void
6146 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6147 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6148     char buf[2*MOVE_LEN], *p;
6149     Exclusion *e = excluTab;
6150     int i;
6151     for(i=0; i<exCnt; i++)
6152         if(e[i].ff == fromX && e[i].fr == fromY &&
6153            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6154     if(i == exCnt) { // was not in exclude list; add it
6155         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6156         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6157             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6158             return; // abort
6159         }
6160         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6161         excludePtr++; e[i].mark = excludePtr++;
6162         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6163         exCnt++;
6164     }
6165     exclusionHeader[e[i].mark] = state;
6166 }
6167
6168 static int
6169 ExcludeOneMove (int fromY, int fromX, int toY, int toX, signed char promoChar, char state)
6170 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6171     char buf[MSG_SIZ];
6172     int j, k;
6173     ChessMove moveType;
6174     if(promoChar == -1) { // kludge to indicate best move
6175         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6176             return 1; // if unparsable, abort
6177     }
6178     // update exclusion map (resolving toggle by consulting existing state)
6179     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6180     j = k%8; k >>= 3;
6181     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6182     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6183          excludeMap[k] |=   1<<j;
6184     else excludeMap[k] &= ~(1<<j);
6185     // update header
6186     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6187     // inform engine
6188     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6189     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6190     SendToProgram(buf, &first);
6191     return (state == '+');
6192 }
6193
6194 static void
6195 ExcludeClick (int index)
6196 {
6197     int i, j;
6198     Exclusion *e = excluTab;
6199     if(index < 25) { // none, best or tail clicked
6200         if(index < 13) { // none: include all
6201             WriteMap(0); // clear map
6202             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6203             SendToProgram("include all\n", &first); // and inform engine
6204         } else if(index > 18) { // tail
6205             if(exclusionHeader[19] == '-') { // tail was excluded
6206                 SendToProgram("include all\n", &first);
6207                 WriteMap(0); // clear map completely
6208                 // now re-exclude selected moves
6209                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6210                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6211             } else { // tail was included or in mixed state
6212                 SendToProgram("exclude all\n", &first);
6213                 WriteMap(0xFF); // fill map completely
6214                 // now re-include selected moves
6215                 j = 0; // count them
6216                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6217                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6218                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6219             }
6220         } else { // best
6221             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6222         }
6223     } else {
6224         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6225             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6226             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6227             break;
6228         }
6229     }
6230 }
6231
6232 ChessSquare
6233 DefaultPromoChoice (int white)
6234 {
6235     ChessSquare result;
6236     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6237         result = WhiteFerz; // no choice
6238     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6239         result= WhiteKing; // in Suicide Q is the last thing we want
6240     else if(gameInfo.variant == VariantSpartan)
6241         result = white ? WhiteQueen : WhiteAngel;
6242     else result = WhiteQueen;
6243     if(!white) result = WHITE_TO_BLACK result;
6244     return result;
6245 }
6246
6247 static int autoQueen; // [HGM] oneclick
6248
6249 int
6250 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6251 {
6252     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6253     /* [HGM] add Shogi promotions */
6254     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6255     ChessSquare piece;
6256     ChessMove moveType;
6257     Boolean premove;
6258
6259     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6260     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6261
6262     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6263       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6264         return FALSE;
6265
6266     piece = boards[currentMove][fromY][fromX];
6267     if(gameInfo.variant == VariantShogi) {
6268         promotionZoneSize = BOARD_HEIGHT/3;
6269         highestPromotingPiece = (int)WhiteFerz;
6270     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6271         promotionZoneSize = 3;
6272     }
6273
6274     // Treat Lance as Pawn when it is not representing Amazon
6275     if(gameInfo.variant != VariantSuper) {
6276         if(piece == WhiteLance) piece = WhitePawn; else
6277         if(piece == BlackLance) piece = BlackPawn;
6278     }
6279
6280     // next weed out all moves that do not touch the promotion zone at all
6281     if((int)piece >= BlackPawn) {
6282         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6283              return FALSE;
6284         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6285     } else {
6286         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6287            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6288     }
6289
6290     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6291
6292     // weed out mandatory Shogi promotions
6293     if(gameInfo.variant == VariantShogi) {
6294         if(piece >= BlackPawn) {
6295             if(toY == 0 && piece == BlackPawn ||
6296                toY == 0 && piece == BlackQueen ||
6297                toY <= 1 && piece == BlackKnight) {
6298                 *promoChoice = '+';
6299                 return FALSE;
6300             }
6301         } else {
6302             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6303                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6304                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6305                 *promoChoice = '+';
6306                 return FALSE;
6307             }
6308         }
6309     }
6310
6311     // weed out obviously illegal Pawn moves
6312     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6313         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6314         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6315         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6316         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6317         // note we are not allowed to test for valid (non-)capture, due to premove
6318     }
6319
6320     // we either have a choice what to promote to, or (in Shogi) whether to promote
6321     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6322         *promoChoice = PieceToChar(BlackFerz);  // no choice
6323         return FALSE;
6324     }
6325     // no sense asking what we must promote to if it is going to explode...
6326     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6327         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6328         return FALSE;
6329     }
6330     // give caller the default choice even if we will not make it
6331     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6332     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6333     if(        sweepSelect && gameInfo.variant != VariantGreat
6334                            && gameInfo.variant != VariantGrand
6335                            && gameInfo.variant != VariantSuper) return FALSE;
6336     if(autoQueen) return FALSE; // predetermined
6337
6338     // suppress promotion popup on illegal moves that are not premoves
6339     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6340               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6341     if(appData.testLegality && !premove) {
6342         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6343                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6344         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6345             return FALSE;
6346     }
6347
6348     return TRUE;
6349 }
6350
6351 int
6352 InPalace (int row, int column)
6353 {   /* [HGM] for Xiangqi */
6354     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6355          column < (BOARD_WIDTH + 4)/2 &&
6356          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6357     return FALSE;
6358 }
6359
6360 int
6361 PieceForSquare (int x, int y)
6362 {
6363   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6364      return -1;
6365   else
6366      return boards[currentMove][y][x];
6367 }
6368
6369 int
6370 OKToStartUserMove (int x, int y)
6371 {
6372     ChessSquare from_piece;
6373     int white_piece;
6374
6375     if (matchMode) return FALSE;
6376     if (gameMode == EditPosition) return TRUE;
6377
6378     if (x >= 0 && y >= 0)
6379       from_piece = boards[currentMove][y][x];
6380     else
6381       from_piece = EmptySquare;
6382
6383     if (from_piece == EmptySquare) return FALSE;
6384
6385     white_piece = (int)from_piece >= (int)WhitePawn &&
6386       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6387
6388     switch (gameMode) {
6389       case AnalyzeFile:
6390       case TwoMachinesPlay:
6391       case EndOfGame:
6392         return FALSE;
6393
6394       case IcsObserving:
6395       case IcsIdle:
6396         return FALSE;
6397
6398       case MachinePlaysWhite:
6399       case IcsPlayingBlack:
6400         if (appData.zippyPlay) return FALSE;
6401         if (white_piece) {
6402             DisplayMoveError(_("You are playing Black"));
6403             return FALSE;
6404         }
6405         break;
6406
6407       case MachinePlaysBlack:
6408       case IcsPlayingWhite:
6409         if (appData.zippyPlay) return FALSE;
6410         if (!white_piece) {
6411             DisplayMoveError(_("You are playing White"));
6412             return FALSE;
6413         }
6414         break;
6415
6416       case PlayFromGameFile:
6417             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6418       case EditGame:
6419         if (!white_piece && WhiteOnMove(currentMove)) {
6420             DisplayMoveError(_("It is White's turn"));
6421             return FALSE;
6422         }
6423         if (white_piece && !WhiteOnMove(currentMove)) {
6424             DisplayMoveError(_("It is Black's turn"));
6425             return FALSE;
6426         }
6427         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6428             /* Editing correspondence game history */
6429             /* Could disallow this or prompt for confirmation */
6430             cmailOldMove = -1;
6431         }
6432         break;
6433
6434       case BeginningOfGame:
6435         if (appData.icsActive) return FALSE;
6436         if (!appData.noChessProgram) {
6437             if (!white_piece) {
6438                 DisplayMoveError(_("You are playing White"));
6439                 return FALSE;
6440             }
6441         }
6442         break;
6443
6444       case Training:
6445         if (!white_piece && WhiteOnMove(currentMove)) {
6446             DisplayMoveError(_("It is White's turn"));
6447             return FALSE;
6448         }
6449         if (white_piece && !WhiteOnMove(currentMove)) {
6450             DisplayMoveError(_("It is Black's turn"));
6451             return FALSE;
6452         }
6453         break;
6454
6455       default:
6456       case IcsExamining:
6457         break;
6458     }
6459     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6460         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6461         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6462         && gameMode != AnalyzeFile && gameMode != Training) {
6463         DisplayMoveError(_("Displayed position is not current"));
6464         return FALSE;
6465     }
6466     return TRUE;
6467 }
6468
6469 Boolean
6470 OnlyMove (int *x, int *y, Boolean captures) 
6471 {
6472     DisambiguateClosure cl;
6473     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6474     switch(gameMode) {
6475       case MachinePlaysBlack:
6476       case IcsPlayingWhite:
6477       case BeginningOfGame:
6478         if(!WhiteOnMove(currentMove)) return FALSE;
6479         break;
6480       case MachinePlaysWhite:
6481       case IcsPlayingBlack:
6482         if(WhiteOnMove(currentMove)) return FALSE;
6483         break;
6484       case EditGame:
6485         break;
6486       default:
6487         return FALSE;
6488     }
6489     cl.pieceIn = EmptySquare;
6490     cl.rfIn = *y;
6491     cl.ffIn = *x;
6492     cl.rtIn = -1;
6493     cl.ftIn = -1;
6494     cl.promoCharIn = NULLCHAR;
6495     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6496     if( cl.kind == NormalMove ||
6497         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6498         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6499         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6500       fromX = cl.ff;
6501       fromY = cl.rf;
6502       *x = cl.ft;
6503       *y = cl.rt;
6504       return TRUE;
6505     }
6506     if(cl.kind != ImpossibleMove) return FALSE;
6507     cl.pieceIn = EmptySquare;
6508     cl.rfIn = -1;
6509     cl.ffIn = -1;
6510     cl.rtIn = *y;
6511     cl.ftIn = *x;
6512     cl.promoCharIn = NULLCHAR;
6513     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6514     if( cl.kind == NormalMove ||
6515         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6516         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6517         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6518       fromX = cl.ff;
6519       fromY = cl.rf;
6520       *x = cl.ft;
6521       *y = cl.rt;
6522       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6523       return TRUE;
6524     }
6525     return FALSE;
6526 }
6527
6528 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6529 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6530 int lastLoadGameUseList = FALSE;
6531 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6532 ChessMove lastLoadGameStart = EndOfFile;
6533 int doubleClick;
6534
6535 void
6536 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6537 {
6538     ChessMove moveType;
6539     ChessSquare pdown, pup;
6540     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6541
6542
6543     /* Check if the user is playing in turn.  This is complicated because we
6544        let the user "pick up" a piece before it is his turn.  So the piece he
6545        tried to pick up may have been captured by the time he puts it down!
6546        Therefore we use the color the user is supposed to be playing in this
6547        test, not the color of the piece that is currently on the starting
6548        square---except in EditGame mode, where the user is playing both
6549        sides; fortunately there the capture race can't happen.  (It can
6550        now happen in IcsExamining mode, but that's just too bad.  The user
6551        will get a somewhat confusing message in that case.)
6552        */
6553
6554     switch (gameMode) {
6555       case AnalyzeFile:
6556       case TwoMachinesPlay:
6557       case EndOfGame:
6558       case IcsObserving:
6559       case IcsIdle:
6560         /* We switched into a game mode where moves are not accepted,
6561            perhaps while the mouse button was down. */
6562         return;
6563
6564       case MachinePlaysWhite:
6565         /* User is moving for Black */
6566         if (WhiteOnMove(currentMove)) {
6567             DisplayMoveError(_("It is White's turn"));
6568             return;
6569         }
6570         break;
6571
6572       case MachinePlaysBlack:
6573         /* User is moving for White */
6574         if (!WhiteOnMove(currentMove)) {
6575             DisplayMoveError(_("It is Black's turn"));
6576             return;
6577         }
6578         break;
6579
6580       case PlayFromGameFile:
6581             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6582       case EditGame:
6583       case IcsExamining:
6584       case BeginningOfGame:
6585       case AnalyzeMode:
6586       case Training:
6587         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6588         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6589             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6590             /* User is moving for Black */
6591             if (WhiteOnMove(currentMove)) {
6592                 DisplayMoveError(_("It is White's turn"));
6593                 return;
6594             }
6595         } else {
6596             /* User is moving for White */
6597             if (!WhiteOnMove(currentMove)) {
6598                 DisplayMoveError(_("It is Black's turn"));
6599                 return;
6600             }
6601         }
6602         break;
6603
6604       case IcsPlayingBlack:
6605         /* User is moving for Black */
6606         if (WhiteOnMove(currentMove)) {
6607             if (!appData.premove) {
6608                 DisplayMoveError(_("It is White's turn"));
6609             } else if (toX >= 0 && toY >= 0) {
6610                 premoveToX = toX;
6611                 premoveToY = toY;
6612                 premoveFromX = fromX;
6613                 premoveFromY = fromY;
6614                 premovePromoChar = promoChar;
6615                 gotPremove = 1;
6616                 if (appData.debugMode)
6617                     fprintf(debugFP, "Got premove: fromX %d,"
6618                             "fromY %d, toX %d, toY %d\n",
6619                             fromX, fromY, toX, toY);
6620             }
6621             return;
6622         }
6623         break;
6624
6625       case IcsPlayingWhite:
6626         /* User is moving for White */
6627         if (!WhiteOnMove(currentMove)) {
6628             if (!appData.premove) {
6629                 DisplayMoveError(_("It is Black's turn"));
6630             } else if (toX >= 0 && toY >= 0) {
6631                 premoveToX = toX;
6632                 premoveToY = toY;
6633                 premoveFromX = fromX;
6634                 premoveFromY = fromY;
6635                 premovePromoChar = promoChar;
6636                 gotPremove = 1;
6637                 if (appData.debugMode)
6638                     fprintf(debugFP, "Got premove: fromX %d,"
6639                             "fromY %d, toX %d, toY %d\n",
6640                             fromX, fromY, toX, toY);
6641             }
6642             return;
6643         }
6644         break;
6645
6646       default:
6647         break;
6648
6649       case EditPosition:
6650         /* EditPosition, empty square, or different color piece;
6651            click-click move is possible */
6652         if (toX == -2 || toY == -2) {
6653             boards[0][fromY][fromX] = EmptySquare;
6654             DrawPosition(FALSE, boards[currentMove]);
6655             return;
6656         } else if (toX >= 0 && toY >= 0) {
6657             boards[0][toY][toX] = boards[0][fromY][fromX];
6658             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6659                 if(boards[0][fromY][0] != EmptySquare) {
6660                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6661                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6662                 }
6663             } else
6664             if(fromX == BOARD_RGHT+1) {
6665                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6666                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6667                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6668                 }
6669             } else
6670             boards[0][fromY][fromX] = gatingPiece;
6671             DrawPosition(FALSE, boards[currentMove]);
6672             return;
6673         }
6674         return;
6675     }
6676
6677     if(toX < 0 || toY < 0) return;
6678     pdown = boards[currentMove][fromY][fromX];
6679     pup = boards[currentMove][toY][toX];
6680
6681     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6682     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6683          if( pup != EmptySquare ) return;
6684          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6685            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6686                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6687            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6688            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6689            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6690            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6691          fromY = DROP_RANK;
6692     }
6693
6694     /* [HGM] always test for legality, to get promotion info */
6695     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6696                                          fromY, fromX, toY, toX, promoChar);
6697
6698     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6699
6700     /* [HGM] but possibly ignore an IllegalMove result */
6701     if (appData.testLegality) {
6702         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6703             DisplayMoveError(_("Illegal move"));
6704             return;
6705         }
6706     }
6707
6708     if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6709         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6710              ClearPremoveHighlights(); // was included
6711         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6712         return;
6713     }
6714
6715     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6716 }
6717
6718 /* Common tail of UserMoveEvent and DropMenuEvent */
6719 int
6720 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6721 {
6722     char *bookHit = 0;
6723
6724     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6725         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6726         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6727         if(WhiteOnMove(currentMove)) {
6728             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6729         } else {
6730             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6731         }
6732     }
6733
6734     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6735        move type in caller when we know the move is a legal promotion */
6736     if(moveType == NormalMove && promoChar)
6737         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6738
6739     /* [HGM] <popupFix> The following if has been moved here from
6740        UserMoveEvent(). Because it seemed to belong here (why not allow
6741        piece drops in training games?), and because it can only be
6742        performed after it is known to what we promote. */
6743     if (gameMode == Training) {
6744       /* compare the move played on the board to the next move in the
6745        * game. If they match, display the move and the opponent's response.
6746        * If they don't match, display an error message.
6747        */
6748       int saveAnimate;
6749       Board testBoard;
6750       CopyBoard(testBoard, boards[currentMove]);
6751       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6752
6753       if (CompareBoards(testBoard, boards[currentMove+1])) {
6754         ForwardInner(currentMove+1);
6755
6756         /* Autoplay the opponent's response.
6757          * if appData.animate was TRUE when Training mode was entered,
6758          * the response will be animated.
6759          */
6760         saveAnimate = appData.animate;
6761         appData.animate = animateTraining;
6762         ForwardInner(currentMove+1);
6763         appData.animate = saveAnimate;
6764
6765         /* check for the end of the game */
6766         if (currentMove >= forwardMostMove) {
6767           gameMode = PlayFromGameFile;
6768           ModeHighlight();
6769           SetTrainingModeOff();
6770           DisplayInformation(_("End of game"));
6771         }
6772       } else {
6773         DisplayError(_("Incorrect move"), 0);
6774       }
6775       return 1;
6776     }
6777
6778   /* Ok, now we know that the move is good, so we can kill
6779      the previous line in Analysis Mode */
6780   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6781                                 && currentMove < forwardMostMove) {
6782     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6783     else forwardMostMove = currentMove;
6784   }
6785
6786   ClearMap();
6787
6788   /* If we need the chess program but it's dead, restart it */
6789   ResurrectChessProgram();
6790
6791   /* A user move restarts a paused game*/
6792   if (pausing)
6793     PauseEvent();
6794
6795   thinkOutput[0] = NULLCHAR;
6796
6797   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6798
6799   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6800     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6801     return 1;
6802   }
6803
6804   if (gameMode == BeginningOfGame) {
6805     if (appData.noChessProgram) {
6806       gameMode = EditGame;
6807       SetGameInfo();
6808     } else {
6809       char buf[MSG_SIZ];
6810       gameMode = MachinePlaysBlack;
6811       StartClocks();
6812       SetGameInfo();
6813       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6814       DisplayTitle(buf);
6815       if (first.sendName) {
6816         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6817         SendToProgram(buf, &first);
6818       }
6819       StartClocks();
6820     }
6821     ModeHighlight();
6822   }
6823
6824   /* Relay move to ICS or chess engine */
6825   if (appData.icsActive) {
6826     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6827         gameMode == IcsExamining) {
6828       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6829         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6830         SendToICS("draw ");
6831         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6832       }
6833       // also send plain move, in case ICS does not understand atomic claims
6834       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6835       ics_user_moved = 1;
6836     }
6837   } else {
6838     if (first.sendTime && (gameMode == BeginningOfGame ||
6839                            gameMode == MachinePlaysWhite ||
6840                            gameMode == MachinePlaysBlack)) {
6841       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6842     }
6843     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6844          // [HGM] book: if program might be playing, let it use book
6845         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6846         first.maybeThinking = TRUE;
6847     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6848         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6849         SendBoard(&first, currentMove+1);
6850     } else SendMoveToProgram(forwardMostMove-1, &first);
6851     if (currentMove == cmailOldMove + 1) {
6852       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6853     }
6854   }
6855
6856   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6857
6858   switch (gameMode) {
6859   case EditGame:
6860     if(appData.testLegality)
6861     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6862     case MT_NONE:
6863     case MT_CHECK:
6864       break;
6865     case MT_CHECKMATE:
6866     case MT_STAINMATE:
6867       if (WhiteOnMove(currentMove)) {
6868         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6869       } else {
6870         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6871       }
6872       break;
6873     case MT_STALEMATE:
6874       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6875       break;
6876     }
6877     break;
6878
6879   case MachinePlaysBlack:
6880   case MachinePlaysWhite:
6881     /* disable certain menu options while machine is thinking */
6882     SetMachineThinkingEnables();
6883     break;
6884
6885   default:
6886     break;
6887   }
6888
6889   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6890   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6891
6892   if(bookHit) { // [HGM] book: simulate book reply
6893         static char bookMove[MSG_SIZ]; // a bit generous?
6894
6895         programStats.nodes = programStats.depth = programStats.time =
6896         programStats.score = programStats.got_only_move = 0;
6897         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6898
6899         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6900         strcat(bookMove, bookHit);
6901         HandleMachineMove(bookMove, &first);
6902   }
6903   return 1;
6904 }
6905
6906 void
6907 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6908 {
6909     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6910     Markers *m = (Markers *) closure;
6911     if(rf == fromY && ff == fromX)
6912         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6913                          || kind == WhiteCapturesEnPassant
6914                          || kind == BlackCapturesEnPassant);
6915     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6916 }
6917
6918 void
6919 MarkTargetSquares (int clear)
6920 {
6921   int x, y;
6922   if(clear) // no reason to ever suppress clearing
6923     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6924   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6925      !appData.testLegality || gameMode == EditPosition) return;
6926   if(!clear) {
6927     int capt = 0;
6928     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6929     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6930       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6931       if(capt)
6932       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6933     }
6934   }
6935   DrawPosition(FALSE, NULL);
6936 }
6937
6938 int
6939 Explode (Board board, int fromX, int fromY, int toX, int toY)
6940 {
6941     if(gameInfo.variant == VariantAtomic &&
6942        (board[toY][toX] != EmptySquare ||                     // capture?
6943         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6944                          board[fromY][fromX] == BlackPawn   )
6945       )) {
6946         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6947         return TRUE;
6948     }
6949     return FALSE;
6950 }
6951
6952 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6953
6954 int
6955 CanPromote (ChessSquare piece, int y)
6956 {
6957         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6958         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6959         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6960            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6961            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6962                                                   gameInfo.variant == VariantMakruk) return FALSE;
6963         return (piece == BlackPawn && y == 1 ||
6964                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6965                 piece == BlackLance && y == 1 ||
6966                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6967 }
6968
6969 void
6970 LeftClick (ClickType clickType, int xPix, int yPix)
6971 {
6972     int x, y;
6973     Boolean saveAnimate;
6974     static int second = 0, promotionChoice = 0, clearFlag = 0;
6975     char promoChoice = NULLCHAR;
6976     ChessSquare piece;
6977     static TimeMark lastClickTime, prevClickTime;
6978
6979     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
6980
6981     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
6982
6983     if (clickType == Press) ErrorPopDown();
6984
6985     x = EventToSquare(xPix, BOARD_WIDTH);
6986     y = EventToSquare(yPix, BOARD_HEIGHT);
6987     if (!flipView && y >= 0) {
6988         y = BOARD_HEIGHT - 1 - y;
6989     }
6990     if (flipView && x >= 0) {
6991         x = BOARD_WIDTH - 1 - x;
6992     }
6993
6994     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6995         defaultPromoChoice = promoSweep;
6996         promoSweep = EmptySquare;   // terminate sweep
6997         promoDefaultAltered = TRUE;
6998         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6999     }
7000
7001     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7002         if(clickType == Release) return; // ignore upclick of click-click destination
7003         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7004         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7005         if(gameInfo.holdingsWidth &&
7006                 (WhiteOnMove(currentMove)
7007                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7008                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7009             // click in right holdings, for determining promotion piece
7010             ChessSquare p = boards[currentMove][y][x];
7011             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7012             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7013             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7014                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7015                 fromX = fromY = -1;
7016                 return;
7017             }
7018         }
7019         DrawPosition(FALSE, boards[currentMove]);
7020         return;
7021     }
7022
7023     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7024     if(clickType == Press
7025             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7026               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7027               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7028         return;
7029
7030     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7031         // could be static click on premove from-square: abort premove
7032         gotPremove = 0;
7033         ClearPremoveHighlights();
7034     }
7035
7036     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7037         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7038
7039     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7040         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7041                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7042         defaultPromoChoice = DefaultPromoChoice(side);
7043     }
7044
7045     autoQueen = appData.alwaysPromoteToQueen;
7046
7047     if (fromX == -1) {
7048       int originalY = y;
7049       gatingPiece = EmptySquare;
7050       if (clickType != Press) {
7051         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7052             DragPieceEnd(xPix, yPix); dragging = 0;
7053             DrawPosition(FALSE, NULL);
7054         }
7055         return;
7056       }
7057       doubleClick = FALSE;
7058       fromX = x; fromY = y; toX = toY = -1;
7059       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7060          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7061          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7062             /* First square */
7063             if (OKToStartUserMove(fromX, fromY)) {
7064                 second = 0;
7065                 MarkTargetSquares(0);
7066                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7067                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7068                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7069                     promoSweep = defaultPromoChoice;
7070                     selectFlag = 0; lastX = xPix; lastY = yPix;
7071                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7072                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7073                 }
7074                 if (appData.highlightDragging) {
7075                     SetHighlights(fromX, fromY, -1, -1);
7076                 }
7077             } else fromX = fromY = -1;
7078             return;
7079         }
7080     }
7081
7082     /* fromX != -1 */
7083     if (clickType == Press && gameMode != EditPosition) {
7084         ChessSquare fromP;
7085         ChessSquare toP;
7086         int frc;
7087
7088         // ignore off-board to clicks
7089         if(y < 0 || x < 0) return;
7090
7091         /* Check if clicking again on the same color piece */
7092         fromP = boards[currentMove][fromY][fromX];
7093         toP = boards[currentMove][y][x];
7094         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7095         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7096              WhitePawn <= toP && toP <= WhiteKing &&
7097              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7098              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7099             (BlackPawn <= fromP && fromP <= BlackKing &&
7100              BlackPawn <= toP && toP <= BlackKing &&
7101              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7102              !(fromP == BlackKing && toP == BlackRook && frc))) {
7103             /* Clicked again on same color piece -- changed his mind */
7104             second = (x == fromX && y == fromY);
7105             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7106                 second = FALSE; // first double-click rather than scond click
7107                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7108             }
7109             promoDefaultAltered = FALSE;
7110             MarkTargetSquares(1);
7111            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7112             if (appData.highlightDragging) {
7113                 SetHighlights(x, y, -1, -1);
7114             } else {
7115                 ClearHighlights();
7116             }
7117             if (OKToStartUserMove(x, y)) {
7118                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7119                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7120                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7121                  gatingPiece = boards[currentMove][fromY][fromX];
7122                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7123                 fromX = x;
7124                 fromY = y; dragging = 1;
7125                 MarkTargetSquares(0);
7126                 DragPieceBegin(xPix, yPix, FALSE);
7127                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7128                     promoSweep = defaultPromoChoice;
7129                     selectFlag = 0; lastX = xPix; lastY = yPix;
7130                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7131                 }
7132             }
7133            }
7134            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7135            second = FALSE; 
7136         }
7137         // ignore clicks on holdings
7138         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7139     }
7140
7141     if (clickType == Release && x == fromX && y == fromY) {
7142         DragPieceEnd(xPix, yPix); dragging = 0;
7143         if(clearFlag) {
7144             // a deferred attempt to click-click move an empty square on top of a piece
7145             boards[currentMove][y][x] = EmptySquare;
7146             ClearHighlights();
7147             DrawPosition(FALSE, boards[currentMove]);
7148             fromX = fromY = -1; clearFlag = 0;
7149             return;
7150         }
7151         if (appData.animateDragging) {
7152             /* Undo animation damage if any */
7153             DrawPosition(FALSE, NULL);
7154         }
7155         if (second) {
7156             /* Second up/down in same square; just abort move */
7157             second = 0;
7158             fromX = fromY = -1;
7159             gatingPiece = EmptySquare;
7160             ClearHighlights();
7161             gotPremove = 0;
7162             ClearPremoveHighlights();
7163         } else {
7164             /* First upclick in same square; start click-click mode */
7165             SetHighlights(x, y, -1, -1);
7166         }
7167         return;
7168     }
7169
7170     clearFlag = 0;
7171
7172     /* we now have a different from- and (possibly off-board) to-square */
7173     /* Completed move */
7174     toX = x;
7175     toY = y;
7176     saveAnimate = appData.animate;
7177     if (clickType == Press) {
7178         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7179             // must be Edit Position mode with empty-square selected
7180             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7181             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7182             return;
7183         }
7184         /* Finish clickclick move */
7185         if (appData.animate || appData.highlightLastMove) {
7186             SetHighlights(fromX, fromY, toX, toY);
7187         } else {
7188             ClearHighlights();
7189         }
7190         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7191           if(appData.sweepSelect) {
7192             ChessSquare piece = boards[currentMove][fromY][fromX];
7193             ChessSquare victim = boards[currentMove][toY][toX];
7194             boards[currentMove][toY][toX] = piece; // kludge: make sure there is something to grab for drag
7195             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7196             boards[currentMove][toY][toX] = victim;
7197             promoSweep = defaultPromoChoice;
7198             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7199             selectFlag = 0; lastX = xPix; lastY = yPix;
7200             Sweep(0); // Pawn that is going to promote: preview promotion piece
7201             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7202             DrawPosition(FALSE, boards[currentMove]);
7203           }
7204           return; // promo popup appears on up-click
7205         }
7206     } else {
7207         /* Finish drag move */
7208         if (appData.highlightLastMove) {
7209             SetHighlights(fromX, fromY, toX, toY);
7210         } else {
7211             ClearHighlights();
7212         }
7213         DragPieceEnd(xPix, yPix); dragging = 0;
7214         /* Don't animate move and drag both */
7215         appData.animate = FALSE;
7216     }
7217     MarkTargetSquares(1);
7218
7219     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7220     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7221         ChessSquare piece = boards[currentMove][fromY][fromX];
7222         if(gameMode == EditPosition && piece != EmptySquare &&
7223            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7224             int n;
7225
7226             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7227                 n = PieceToNumber(piece - (int)BlackPawn);
7228                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7229                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7230                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7231             } else
7232             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7233                 n = PieceToNumber(piece);
7234                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7235                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7236                 boards[currentMove][n][BOARD_WIDTH-2]++;
7237             }
7238             boards[currentMove][fromY][fromX] = EmptySquare;
7239         }
7240         ClearHighlights();
7241         fromX = fromY = -1;
7242         DrawPosition(TRUE, boards[currentMove]);
7243         return;
7244     }
7245
7246     // off-board moves should not be highlighted
7247     if(x < 0 || y < 0) ClearHighlights();
7248
7249     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7250
7251     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7252         SetHighlights(fromX, fromY, toX, toY);
7253         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7254             // [HGM] super: promotion to captured piece selected from holdings
7255             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7256             promotionChoice = TRUE;
7257             // kludge follows to temporarily execute move on display, without promoting yet
7258             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7259             boards[currentMove][toY][toX] = p;
7260             DrawPosition(FALSE, boards[currentMove]);
7261             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7262             boards[currentMove][toY][toX] = q;
7263             DisplayMessage("Click in holdings to choose piece", "");
7264             return;
7265         }
7266         PromotionPopUp();
7267     } else {
7268         int oldMove = currentMove;
7269         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7270         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7271         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7272         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7273            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7274             DrawPosition(TRUE, boards[currentMove]);
7275         fromX = fromY = -1;
7276     }
7277     appData.animate = saveAnimate;
7278     if (appData.animate || appData.animateDragging) {
7279         /* Undo animation damage if needed */
7280         DrawPosition(FALSE, NULL);
7281     }
7282 }
7283
7284 int
7285 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7286 {   // front-end-free part taken out of PieceMenuPopup
7287     int whichMenu; int xSqr, ySqr;
7288
7289     if(seekGraphUp) { // [HGM] seekgraph
7290         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7291         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7292         return -2;
7293     }
7294
7295     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7296          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7297         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7298         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7299         if(action == Press)   {
7300             originalFlip = flipView;
7301             flipView = !flipView; // temporarily flip board to see game from partners perspective
7302             DrawPosition(TRUE, partnerBoard);
7303             DisplayMessage(partnerStatus, "");
7304             partnerUp = TRUE;
7305         } else if(action == Release) {
7306             flipView = originalFlip;
7307             DrawPosition(TRUE, boards[currentMove]);
7308             partnerUp = FALSE;
7309         }
7310         return -2;
7311     }
7312
7313     xSqr = EventToSquare(x, BOARD_WIDTH);
7314     ySqr = EventToSquare(y, BOARD_HEIGHT);
7315     if (action == Release) {
7316         if(pieceSweep != EmptySquare) {
7317             EditPositionMenuEvent(pieceSweep, toX, toY);
7318             pieceSweep = EmptySquare;
7319         } else UnLoadPV(); // [HGM] pv
7320     }
7321     if (action != Press) return -2; // return code to be ignored
7322     switch (gameMode) {
7323       case IcsExamining:
7324         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7325       case EditPosition:
7326         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7327         if (xSqr < 0 || ySqr < 0) return -1;
7328         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7329         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7330         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7331         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7332         NextPiece(0);
7333         return 2; // grab
7334       case IcsObserving:
7335         if(!appData.icsEngineAnalyze) return -1;
7336       case IcsPlayingWhite:
7337       case IcsPlayingBlack:
7338         if(!appData.zippyPlay) goto noZip;
7339       case AnalyzeMode:
7340       case AnalyzeFile:
7341       case MachinePlaysWhite:
7342       case MachinePlaysBlack:
7343       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7344         if (!appData.dropMenu) {
7345           LoadPV(x, y);
7346           return 2; // flag front-end to grab mouse events
7347         }
7348         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7349            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7350       case EditGame:
7351       noZip:
7352         if (xSqr < 0 || ySqr < 0) return -1;
7353         if (!appData.dropMenu || appData.testLegality &&
7354             gameInfo.variant != VariantBughouse &&
7355             gameInfo.variant != VariantCrazyhouse) return -1;
7356         whichMenu = 1; // drop menu
7357         break;
7358       default:
7359         return -1;
7360     }
7361
7362     if (((*fromX = xSqr) < 0) ||
7363         ((*fromY = ySqr) < 0)) {
7364         *fromX = *fromY = -1;
7365         return -1;
7366     }
7367     if (flipView)
7368       *fromX = BOARD_WIDTH - 1 - *fromX;
7369     else
7370       *fromY = BOARD_HEIGHT - 1 - *fromY;
7371
7372     return whichMenu;
7373 }
7374
7375 void
7376 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7377 {
7378 //    char * hint = lastHint;
7379     FrontEndProgramStats stats;
7380
7381     stats.which = cps == &first ? 0 : 1;
7382     stats.depth = cpstats->depth;
7383     stats.nodes = cpstats->nodes;
7384     stats.score = cpstats->score;
7385     stats.time = cpstats->time;
7386     stats.pv = cpstats->movelist;
7387     stats.hint = lastHint;
7388     stats.an_move_index = 0;
7389     stats.an_move_count = 0;
7390
7391     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7392         stats.hint = cpstats->move_name;
7393         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7394         stats.an_move_count = cpstats->nr_moves;
7395     }
7396
7397     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
7398
7399     SetProgramStats( &stats );
7400 }
7401
7402 void
7403 ClearEngineOutputPane (int which)
7404 {
7405     static FrontEndProgramStats dummyStats;
7406     dummyStats.which = which;
7407     dummyStats.pv = "#";
7408     SetProgramStats( &dummyStats );
7409 }
7410
7411 #define MAXPLAYERS 500
7412
7413 char *
7414 TourneyStandings (int display)
7415 {
7416     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7417     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7418     char result, *p, *names[MAXPLAYERS];
7419
7420     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7421         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7422     names[0] = p = strdup(appData.participants);
7423     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7424
7425     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7426
7427     while(result = appData.results[nr]) {
7428         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7429         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7430         wScore = bScore = 0;
7431         switch(result) {
7432           case '+': wScore = 2; break;
7433           case '-': bScore = 2; break;
7434           case '=': wScore = bScore = 1; break;
7435           case ' ':
7436           case '*': return strdup("busy"); // tourney not finished
7437         }
7438         score[w] += wScore;
7439         score[b] += bScore;
7440         games[w]++;
7441         games[b]++;
7442         nr++;
7443     }
7444     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7445     for(w=0; w<nPlayers; w++) {
7446         bScore = -1;
7447         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7448         ranking[w] = b; points[w] = bScore; score[b] = -2;
7449     }
7450     p = malloc(nPlayers*34+1);
7451     for(w=0; w<nPlayers && w<display; w++)
7452         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7453     free(names[0]);
7454     return p;
7455 }
7456
7457 void
7458 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7459 {       // count all piece types
7460         int p, f, r;
7461         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7462         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7463         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7464                 p = board[r][f];
7465                 pCnt[p]++;
7466                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7467                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7468                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7469                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7470                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7471                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7472         }
7473 }
7474
7475 int
7476 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7477 {
7478         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7479         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7480
7481         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7482         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7483         if(myPawns == 2 && nMine == 3) // KPP
7484             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7485         if(myPawns == 1 && nMine == 2) // KP
7486             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7487         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7488             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7489         if(myPawns) return FALSE;
7490         if(pCnt[WhiteRook+side])
7491             return pCnt[BlackRook-side] ||
7492                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7493                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7494                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7495         if(pCnt[WhiteCannon+side]) {
7496             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7497             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7498         }
7499         if(pCnt[WhiteKnight+side])
7500             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7501         return FALSE;
7502 }
7503
7504 int
7505 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7506 {
7507         VariantClass v = gameInfo.variant;
7508
7509         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7510         if(v == VariantShatranj) return TRUE; // always winnable through baring
7511         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7512         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7513
7514         if(v == VariantXiangqi) {
7515                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7516
7517                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7518                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7519                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7520                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7521                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7522                 if(stale) // we have at least one last-rank P plus perhaps C
7523                     return majors // KPKX
7524                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7525                 else // KCA*E*
7526                     return pCnt[WhiteFerz+side] // KCAK
7527                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7528                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7529                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7530
7531         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7532                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7533
7534                 if(nMine == 1) return FALSE; // bare King
7535                 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
7536                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7537                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7538                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7539                 if(pCnt[WhiteKnight+side])
7540                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7541                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7542                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7543                 if(nBishops)
7544                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7545                 if(pCnt[WhiteAlfil+side])
7546                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7547                 if(pCnt[WhiteWazir+side])
7548                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7549         }
7550
7551         return TRUE;
7552 }
7553
7554 int
7555 CompareWithRights (Board b1, Board b2)
7556 {
7557     int rights = 0;
7558     if(!CompareBoards(b1, b2)) return FALSE;
7559     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7560     /* compare castling rights */
7561     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7562            rights++; /* King lost rights, while rook still had them */
7563     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7564         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7565            rights++; /* but at least one rook lost them */
7566     }
7567     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7568            rights++;
7569     if( b1[CASTLING][5] != NoRights ) {
7570         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7571            rights++;
7572     }
7573     return rights == 0;
7574 }
7575
7576 int
7577 Adjudicate (ChessProgramState *cps)
7578 {       // [HGM] some adjudications useful with buggy engines
7579         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7580         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7581         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7582         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7583         int k, count = 0; static int bare = 1;
7584         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7585         Boolean canAdjudicate = !appData.icsActive;
7586
7587         // most tests only when we understand the game, i.e. legality-checking on
7588             if( appData.testLegality )
7589             {   /* [HGM] Some more adjudications for obstinate engines */
7590                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7591                 static int moveCount = 6;
7592                 ChessMove result;
7593                 char *reason = NULL;
7594
7595                 /* Count what is on board. */
7596                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7597
7598                 /* Some material-based adjudications that have to be made before stalemate test */
7599                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7600                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7601                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7602                      if(canAdjudicate && appData.checkMates) {
7603                          if(engineOpponent)
7604                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7605                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7606                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7607                          return 1;
7608                      }
7609                 }
7610
7611                 /* Bare King in Shatranj (loses) or Losers (wins) */
7612                 if( nrW == 1 || nrB == 1) {
7613                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7614                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7615                      if(canAdjudicate && appData.checkMates) {
7616                          if(engineOpponent)
7617                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7618                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7619                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7620                          return 1;
7621                      }
7622                   } else
7623                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7624                   {    /* bare King */
7625                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7626                         if(canAdjudicate && appData.checkMates) {
7627                             /* but only adjudicate if adjudication enabled */
7628                             if(engineOpponent)
7629                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7630                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7631                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7632                             return 1;
7633                         }
7634                   }
7635                 } else bare = 1;
7636
7637
7638             // don't wait for engine to announce game end if we can judge ourselves
7639             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7640               case MT_CHECK:
7641                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7642                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7643                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7644                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7645                             checkCnt++;
7646                         if(checkCnt >= 2) {
7647                             reason = "Xboard adjudication: 3rd check";
7648                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7649                             break;
7650                         }
7651                     }
7652                 }
7653               case MT_NONE:
7654               default:
7655                 break;
7656               case MT_STALEMATE:
7657               case MT_STAINMATE:
7658                 reason = "Xboard adjudication: Stalemate";
7659                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7660                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7661                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7662                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7663                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7664                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7665                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7666                                                                         EP_CHECKMATE : EP_WINS);
7667                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7668                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7669                 }
7670                 break;
7671               case MT_CHECKMATE:
7672                 reason = "Xboard adjudication: Checkmate";
7673                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7674                 break;
7675             }
7676
7677                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7678                     case EP_STALEMATE:
7679                         result = GameIsDrawn; break;
7680                     case EP_CHECKMATE:
7681                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7682                     case EP_WINS:
7683                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7684                     default:
7685                         result = EndOfFile;
7686                 }
7687                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7688                     if(engineOpponent)
7689                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7690                     GameEnds( result, reason, GE_XBOARD );
7691                     return 1;
7692                 }
7693
7694                 /* Next absolutely insufficient mating material. */
7695                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7696                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7697                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7698
7699                      /* always flag draws, for judging claims */
7700                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7701
7702                      if(canAdjudicate && appData.materialDraws) {
7703                          /* but only adjudicate them if adjudication enabled */
7704                          if(engineOpponent) {
7705                            SendToProgram("force\n", engineOpponent); // suppress reply
7706                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7707                          }
7708                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7709                          return 1;
7710                      }
7711                 }
7712
7713                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7714                 if(gameInfo.variant == VariantXiangqi ?
7715                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7716                  : nrW + nrB == 4 &&
7717                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7718                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7719                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7720                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7721                    ) ) {
7722                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7723                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7724                           if(engineOpponent) {
7725                             SendToProgram("force\n", engineOpponent); // suppress reply
7726                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7727                           }
7728                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7729                           return 1;
7730                      }
7731                 } else moveCount = 6;
7732             }
7733
7734         // Repetition draws and 50-move rule can be applied independently of legality testing
7735
7736                 /* Check for rep-draws */
7737                 count = 0;
7738                 for(k = forwardMostMove-2;
7739                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7740                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7741                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7742                     k-=2)
7743                 {   int rights=0;
7744                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7745                         /* compare castling rights */
7746                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7747                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7748                                 rights++; /* King lost rights, while rook still had them */
7749                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7750                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7751                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7752                                    rights++; /* but at least one rook lost them */
7753                         }
7754                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7755                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7756                                 rights++;
7757                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7758                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7759                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7760                                    rights++;
7761                         }
7762                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7763                             && appData.drawRepeats > 1) {
7764                              /* adjudicate after user-specified nr of repeats */
7765                              int result = GameIsDrawn;
7766                              char *details = "XBoard adjudication: repetition draw";
7767                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7768                                 // [HGM] xiangqi: check for forbidden perpetuals
7769                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7770                                 for(m=forwardMostMove; m>k; m-=2) {
7771                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7772                                         ourPerpetual = 0; // the current mover did not always check
7773                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7774                                         hisPerpetual = 0; // the opponent did not always check
7775                                 }
7776                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7777                                                                         ourPerpetual, hisPerpetual);
7778                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7779                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7780                                     details = "Xboard adjudication: perpetual checking";
7781                                 } else
7782                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7783                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7784                                 } else
7785                                 // Now check for perpetual chases
7786                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7787                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7788                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7789                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7790                                         static char resdet[MSG_SIZ];
7791                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7792                                         details = resdet;
7793                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7794                                     } else
7795                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7796                                         break; // Abort repetition-checking loop.
7797                                 }
7798                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7799                              }
7800                              if(engineOpponent) {
7801                                SendToProgram("force\n", engineOpponent); // suppress reply
7802                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7803                              }
7804                              GameEnds( result, details, GE_XBOARD );
7805                              return 1;
7806                         }
7807                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7808                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7809                     }
7810                 }
7811
7812                 /* Now we test for 50-move draws. Determine ply count */
7813                 count = forwardMostMove;
7814                 /* look for last irreversble move */
7815                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7816                     count--;
7817                 /* if we hit starting position, add initial plies */
7818                 if( count == backwardMostMove )
7819                     count -= initialRulePlies;
7820                 count = forwardMostMove - count;
7821                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7822                         // adjust reversible move counter for checks in Xiangqi
7823                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7824                         if(i < backwardMostMove) i = backwardMostMove;
7825                         while(i <= forwardMostMove) {
7826                                 lastCheck = inCheck; // check evasion does not count
7827                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7828                                 if(inCheck || lastCheck) count--; // check does not count
7829                                 i++;
7830                         }
7831                 }
7832                 if( count >= 100)
7833                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7834                          /* this is used to judge if draw claims are legal */
7835                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7836                          if(engineOpponent) {
7837                            SendToProgram("force\n", engineOpponent); // suppress reply
7838                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7839                          }
7840                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7841                          return 1;
7842                 }
7843
7844                 /* if draw offer is pending, treat it as a draw claim
7845                  * when draw condition present, to allow engines a way to
7846                  * claim draws before making their move to avoid a race
7847                  * condition occurring after their move
7848                  */
7849                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7850                          char *p = NULL;
7851                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7852                              p = "Draw claim: 50-move rule";
7853                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7854                              p = "Draw claim: 3-fold repetition";
7855                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7856                              p = "Draw claim: insufficient mating material";
7857                          if( p != NULL && canAdjudicate) {
7858                              if(engineOpponent) {
7859                                SendToProgram("force\n", engineOpponent); // suppress reply
7860                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7861                              }
7862                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7863                              return 1;
7864                          }
7865                 }
7866
7867                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7868                     if(engineOpponent) {
7869                       SendToProgram("force\n", engineOpponent); // suppress reply
7870                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7871                     }
7872                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7873                     return 1;
7874                 }
7875         return 0;
7876 }
7877
7878 char *
7879 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7880 {   // [HGM] book: this routine intercepts moves to simulate book replies
7881     char *bookHit = NULL;
7882
7883     //first determine if the incoming move brings opponent into his book
7884     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7885         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7886     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7887     if(bookHit != NULL && !cps->bookSuspend) {
7888         // make sure opponent is not going to reply after receiving move to book position
7889         SendToProgram("force\n", cps);
7890         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7891     }
7892     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7893     // now arrange restart after book miss
7894     if(bookHit) {
7895         // after a book hit we never send 'go', and the code after the call to this routine
7896         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7897         char buf[MSG_SIZ], *move = bookHit;
7898         if(cps->useSAN) {
7899             int fromX, fromY, toX, toY;
7900             char promoChar;
7901             ChessMove moveType;
7902             move = buf + 30;
7903             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7904                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7905                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7906                                     PosFlags(forwardMostMove),
7907                                     fromY, fromX, toY, toX, promoChar, move);
7908             } else {
7909                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7910                 bookHit = NULL;
7911             }
7912         }
7913         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7914         SendToProgram(buf, cps);
7915         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7916     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7917         SendToProgram("go\n", cps);
7918         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7919     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7920         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7921             SendToProgram("go\n", cps);
7922         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7923     }
7924     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7925 }
7926
7927 int
7928 LoadError (char *errmess, ChessProgramState *cps)
7929 {   // unloads engine and switches back to -ncp mode if it was first
7930     if(cps->initDone) return FALSE;
7931     cps->isr = NULL; // this should suppress further error popups from breaking pipes
7932     DestroyChildProcess(cps->pr, 9 ); // just to be sure
7933     cps->pr = NoProc; 
7934     if(cps == &first) {
7935         appData.noChessProgram = TRUE;
7936         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
7937         gameMode = BeginningOfGame; ModeHighlight();
7938         SetNCPMode();
7939     }
7940     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
7941     DisplayMessage("", ""); // erase waiting message
7942     if(errmess) DisplayError(errmess, 0); // announce reason, if given
7943     return TRUE;
7944 }
7945
7946 char *savedMessage;
7947 ChessProgramState *savedState;
7948 void
7949 DeferredBookMove (void)
7950 {
7951         if(savedState->lastPing != savedState->lastPong)
7952                     ScheduleDelayedEvent(DeferredBookMove, 10);
7953         else
7954         HandleMachineMove(savedMessage, savedState);
7955 }
7956
7957 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7958
7959 void
7960 HandleMachineMove (char *message, ChessProgramState *cps)
7961 {
7962     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7963     char realname[MSG_SIZ];
7964     int fromX, fromY, toX, toY;
7965     ChessMove moveType;
7966     char promoChar;
7967     char *p, *pv=buf1;
7968     int machineWhite, oldError;
7969     char *bookHit;
7970
7971     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7972         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7973         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7974             DisplayError(_("Invalid pairing from pairing engine"), 0);
7975             return;
7976         }
7977         pairingReceived = 1;
7978         NextMatchGame();
7979         return; // Skim the pairing messages here.
7980     }
7981
7982     oldError = cps->userError; cps->userError = 0;
7983
7984 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7985     /*
7986      * Kludge to ignore BEL characters
7987      */
7988     while (*message == '\007') message++;
7989
7990     /*
7991      * [HGM] engine debug message: ignore lines starting with '#' character
7992      */
7993     if(cps->debug && *message == '#') return;
7994
7995     /*
7996      * Look for book output
7997      */
7998     if (cps == &first && bookRequested) {
7999         if (message[0] == '\t' || message[0] == ' ') {
8000             /* Part of the book output is here; append it */
8001             strcat(bookOutput, message);
8002             strcat(bookOutput, "  \n");
8003             return;
8004         } else if (bookOutput[0] != NULLCHAR) {
8005             /* All of book output has arrived; display it */
8006             char *p = bookOutput;
8007             while (*p != NULLCHAR) {
8008                 if (*p == '\t') *p = ' ';
8009                 p++;
8010             }
8011             DisplayInformation(bookOutput);
8012             bookRequested = FALSE;
8013             /* Fall through to parse the current output */
8014         }
8015     }
8016
8017     /*
8018      * Look for machine move.
8019      */
8020     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8021         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8022     {
8023         /* This method is only useful on engines that support ping */
8024         if (cps->lastPing != cps->lastPong) {
8025           if (gameMode == BeginningOfGame) {
8026             /* Extra move from before last new; ignore */
8027             if (appData.debugMode) {
8028                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8029             }
8030           } else {
8031             if (appData.debugMode) {
8032                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8033                         cps->which, gameMode);
8034             }
8035
8036             SendToProgram("undo\n", cps);
8037           }
8038           return;
8039         }
8040
8041         switch (gameMode) {
8042           case BeginningOfGame:
8043             /* Extra move from before last reset; ignore */
8044             if (appData.debugMode) {
8045                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8046             }
8047             return;
8048
8049           case EndOfGame:
8050           case IcsIdle:
8051           default:
8052             /* Extra move after we tried to stop.  The mode test is
8053                not a reliable way of detecting this problem, but it's
8054                the best we can do on engines that don't support ping.
8055             */
8056             if (appData.debugMode) {
8057                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8058                         cps->which, gameMode);
8059             }
8060             SendToProgram("undo\n", cps);
8061             return;
8062
8063           case MachinePlaysWhite:
8064           case IcsPlayingWhite:
8065             machineWhite = TRUE;
8066             break;
8067
8068           case MachinePlaysBlack:
8069           case IcsPlayingBlack:
8070             machineWhite = FALSE;
8071             break;
8072
8073           case TwoMachinesPlay:
8074             machineWhite = (cps->twoMachinesColor[0] == 'w');
8075             break;
8076         }
8077         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8078             if (appData.debugMode) {
8079                 fprintf(debugFP,
8080                         "Ignoring move out of turn by %s, gameMode %d"
8081                         ", forwardMost %d\n",
8082                         cps->which, gameMode, forwardMostMove);
8083             }
8084             return;
8085         }
8086
8087         if(cps->alphaRank) AlphaRank(machineMove, 4);
8088         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8089                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8090             /* Machine move could not be parsed; ignore it. */
8091           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8092                     machineMove, _(cps->which));
8093             DisplayError(buf1, 0);
8094             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8095                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8096             if (gameMode == TwoMachinesPlay) {
8097               GameEnds(machineWhite ? BlackWins : WhiteWins,
8098                        buf1, GE_XBOARD);
8099             }
8100             return;
8101         }
8102
8103         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8104         /* So we have to redo legality test with true e.p. status here,  */
8105         /* to make sure an illegal e.p. capture does not slip through,   */
8106         /* to cause a forfeit on a justified illegal-move complaint      */
8107         /* of the opponent.                                              */
8108         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8109            ChessMove moveType;
8110            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8111                              fromY, fromX, toY, toX, promoChar);
8112             if(moveType == IllegalMove) {
8113               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8114                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8115                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8116                            buf1, GE_XBOARD);
8117                 return;
8118            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8119            /* [HGM] Kludge to handle engines that send FRC-style castling
8120               when they shouldn't (like TSCP-Gothic) */
8121            switch(moveType) {
8122              case WhiteASideCastleFR:
8123              case BlackASideCastleFR:
8124                toX+=2;
8125                currentMoveString[2]++;
8126                break;
8127              case WhiteHSideCastleFR:
8128              case BlackHSideCastleFR:
8129                toX--;
8130                currentMoveString[2]--;
8131                break;
8132              default: ; // nothing to do, but suppresses warning of pedantic compilers
8133            }
8134         }
8135         hintRequested = FALSE;
8136         lastHint[0] = NULLCHAR;
8137         bookRequested = FALSE;
8138         /* Program may be pondering now */
8139         cps->maybeThinking = TRUE;
8140         if (cps->sendTime == 2) cps->sendTime = 1;
8141         if (cps->offeredDraw) cps->offeredDraw--;
8142
8143         /* [AS] Save move info*/
8144         pvInfoList[ forwardMostMove ].score = programStats.score;
8145         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8146         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8147
8148         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8149
8150         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8151         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8152             int count = 0;
8153
8154             while( count < adjudicateLossPlies ) {
8155                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8156
8157                 if( count & 1 ) {
8158                     score = -score; /* Flip score for winning side */
8159                 }
8160
8161                 if( score > adjudicateLossThreshold ) {
8162                     break;
8163                 }
8164
8165                 count++;
8166             }
8167
8168             if( count >= adjudicateLossPlies ) {
8169                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8170
8171                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8172                     "Xboard adjudication",
8173                     GE_XBOARD );
8174
8175                 return;
8176             }
8177         }
8178
8179         if(Adjudicate(cps)) {
8180             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8181             return; // [HGM] adjudicate: for all automatic game ends
8182         }
8183
8184 #if ZIPPY
8185         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8186             first.initDone) {
8187           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8188                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8189                 SendToICS("draw ");
8190                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8191           }
8192           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8193           ics_user_moved = 1;
8194           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8195                 char buf[3*MSG_SIZ];
8196
8197                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8198                         programStats.score / 100.,
8199                         programStats.depth,
8200                         programStats.time / 100.,
8201                         (unsigned int)programStats.nodes,
8202                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8203                         programStats.movelist);
8204                 SendToICS(buf);
8205 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8206           }
8207         }
8208 #endif
8209
8210         /* [AS] Clear stats for next move */
8211         ClearProgramStats();
8212         thinkOutput[0] = NULLCHAR;
8213         hiddenThinkOutputState = 0;
8214
8215         bookHit = NULL;
8216         if (gameMode == TwoMachinesPlay) {
8217             /* [HGM] relaying draw offers moved to after reception of move */
8218             /* and interpreting offer as claim if it brings draw condition */
8219             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8220                 SendToProgram("draw\n", cps->other);
8221             }
8222             if (cps->other->sendTime) {
8223                 SendTimeRemaining(cps->other,
8224                                   cps->other->twoMachinesColor[0] == 'w');
8225             }
8226             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8227             if (firstMove && !bookHit) {
8228                 firstMove = FALSE;
8229                 if (cps->other->useColors) {
8230                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8231                 }
8232                 SendToProgram("go\n", cps->other);
8233             }
8234             cps->other->maybeThinking = TRUE;
8235         }
8236
8237         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8238
8239         if (!pausing && appData.ringBellAfterMoves) {
8240             RingBell();
8241         }
8242
8243         /*
8244          * Reenable menu items that were disabled while
8245          * machine was thinking
8246          */
8247         if (gameMode != TwoMachinesPlay)
8248             SetUserThinkingEnables();
8249
8250         // [HGM] book: after book hit opponent has received move and is now in force mode
8251         // force the book reply into it, and then fake that it outputted this move by jumping
8252         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8253         if(bookHit) {
8254                 static char bookMove[MSG_SIZ]; // a bit generous?
8255
8256                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8257                 strcat(bookMove, bookHit);
8258                 message = bookMove;
8259                 cps = cps->other;
8260                 programStats.nodes = programStats.depth = programStats.time =
8261                 programStats.score = programStats.got_only_move = 0;
8262                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8263
8264                 if(cps->lastPing != cps->lastPong) {
8265                     savedMessage = message; // args for deferred call
8266                     savedState = cps;
8267                     ScheduleDelayedEvent(DeferredBookMove, 10);
8268                     return;
8269                 }
8270                 goto FakeBookMove;
8271         }
8272
8273         return;
8274     }
8275
8276     /* Set special modes for chess engines.  Later something general
8277      *  could be added here; for now there is just one kludge feature,
8278      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8279      *  when "xboard" is given as an interactive command.
8280      */
8281     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8282         cps->useSigint = FALSE;
8283         cps->useSigterm = FALSE;
8284     }
8285     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8286       ParseFeatures(message+8, cps);
8287       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8288     }
8289
8290     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8291                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8292       int dummy, s=6; char buf[MSG_SIZ];
8293       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8294       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8295       if(startedFromSetupPosition) return;
8296       ParseFEN(boards[0], &dummy, message+s);
8297       DrawPosition(TRUE, boards[0]);
8298       startedFromSetupPosition = TRUE;
8299       return;
8300     }
8301     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8302      * want this, I was asked to put it in, and obliged.
8303      */
8304     if (!strncmp(message, "setboard ", 9)) {
8305         Board initial_position;
8306
8307         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8308
8309         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8310             DisplayError(_("Bad FEN received from engine"), 0);
8311             return ;
8312         } else {
8313            Reset(TRUE, FALSE);
8314            CopyBoard(boards[0], initial_position);
8315            initialRulePlies = FENrulePlies;
8316            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8317            else gameMode = MachinePlaysBlack;
8318            DrawPosition(FALSE, boards[currentMove]);
8319         }
8320         return;
8321     }
8322
8323     /*
8324      * Look for communication commands
8325      */
8326     if (!strncmp(message, "telluser ", 9)) {
8327         if(message[9] == '\\' && message[10] == '\\')
8328             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8329         PlayTellSound();
8330         DisplayNote(message + 9);
8331         return;
8332     }
8333     if (!strncmp(message, "tellusererror ", 14)) {
8334         cps->userError = 1;
8335         if(message[14] == '\\' && message[15] == '\\')
8336             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8337         PlayTellSound();
8338         DisplayError(message + 14, 0);
8339         return;
8340     }
8341     if (!strncmp(message, "tellopponent ", 13)) {
8342       if (appData.icsActive) {
8343         if (loggedOn) {
8344           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8345           SendToICS(buf1);
8346         }
8347       } else {
8348         DisplayNote(message + 13);
8349       }
8350       return;
8351     }
8352     if (!strncmp(message, "tellothers ", 11)) {
8353       if (appData.icsActive) {
8354         if (loggedOn) {
8355           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8356           SendToICS(buf1);
8357         }
8358       }
8359       return;
8360     }
8361     if (!strncmp(message, "tellall ", 8)) {
8362       if (appData.icsActive) {
8363         if (loggedOn) {
8364           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8365           SendToICS(buf1);
8366         }
8367       } else {
8368         DisplayNote(message + 8);
8369       }
8370       return;
8371     }
8372     if (strncmp(message, "warning", 7) == 0) {
8373         /* Undocumented feature, use tellusererror in new code */
8374         DisplayError(message, 0);
8375         return;
8376     }
8377     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8378         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8379         strcat(realname, " query");
8380         AskQuestion(realname, buf2, buf1, cps->pr);
8381         return;
8382     }
8383     /* Commands from the engine directly to ICS.  We don't allow these to be
8384      *  sent until we are logged on. Crafty kibitzes have been known to
8385      *  interfere with the login process.
8386      */
8387     if (loggedOn) {
8388         if (!strncmp(message, "tellics ", 8)) {
8389             SendToICS(message + 8);
8390             SendToICS("\n");
8391             return;
8392         }
8393         if (!strncmp(message, "tellicsnoalias ", 15)) {
8394             SendToICS(ics_prefix);
8395             SendToICS(message + 15);
8396             SendToICS("\n");
8397             return;
8398         }
8399         /* The following are for backward compatibility only */
8400         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8401             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8402             SendToICS(ics_prefix);
8403             SendToICS(message);
8404             SendToICS("\n");
8405             return;
8406         }
8407     }
8408     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8409         return;
8410     }
8411     /*
8412      * If the move is illegal, cancel it and redraw the board.
8413      * Also deal with other error cases.  Matching is rather loose
8414      * here to accommodate engines written before the spec.
8415      */
8416     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8417         strncmp(message, "Error", 5) == 0) {
8418         if (StrStr(message, "name") ||
8419             StrStr(message, "rating") || StrStr(message, "?") ||
8420             StrStr(message, "result") || StrStr(message, "board") ||
8421             StrStr(message, "bk") || StrStr(message, "computer") ||
8422             StrStr(message, "variant") || StrStr(message, "hint") ||
8423             StrStr(message, "random") || StrStr(message, "depth") ||
8424             StrStr(message, "accepted")) {
8425             return;
8426         }
8427         if (StrStr(message, "protover")) {
8428           /* Program is responding to input, so it's apparently done
8429              initializing, and this error message indicates it is
8430              protocol version 1.  So we don't need to wait any longer
8431              for it to initialize and send feature commands. */
8432           FeatureDone(cps, 1);
8433           cps->protocolVersion = 1;
8434           return;
8435         }
8436         cps->maybeThinking = FALSE;
8437
8438         if (StrStr(message, "draw")) {
8439             /* Program doesn't have "draw" command */
8440             cps->sendDrawOffers = 0;
8441             return;
8442         }
8443         if (cps->sendTime != 1 &&
8444             (StrStr(message, "time") || StrStr(message, "otim"))) {
8445           /* Program apparently doesn't have "time" or "otim" command */
8446           cps->sendTime = 0;
8447           return;
8448         }
8449         if (StrStr(message, "analyze")) {
8450             cps->analysisSupport = FALSE;
8451             cps->analyzing = FALSE;
8452 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8453             EditGameEvent(); // [HGM] try to preserve loaded game
8454             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8455             DisplayError(buf2, 0);
8456             return;
8457         }
8458         if (StrStr(message, "(no matching move)st")) {
8459           /* Special kludge for GNU Chess 4 only */
8460           cps->stKludge = TRUE;
8461           SendTimeControl(cps, movesPerSession, timeControl,
8462                           timeIncrement, appData.searchDepth,
8463                           searchTime);
8464           return;
8465         }
8466         if (StrStr(message, "(no matching move)sd")) {
8467           /* Special kludge for GNU Chess 4 only */
8468           cps->sdKludge = TRUE;
8469           SendTimeControl(cps, movesPerSession, timeControl,
8470                           timeIncrement, appData.searchDepth,
8471                           searchTime);
8472           return;
8473         }
8474         if (!StrStr(message, "llegal")) {
8475             return;
8476         }
8477         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8478             gameMode == IcsIdle) return;
8479         if (forwardMostMove <= backwardMostMove) return;
8480         if (pausing) PauseEvent();
8481       if(appData.forceIllegal) {
8482             // [HGM] illegal: machine refused move; force position after move into it
8483           SendToProgram("force\n", cps);
8484           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8485                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8486                 // when black is to move, while there might be nothing on a2 or black
8487                 // might already have the move. So send the board as if white has the move.
8488                 // But first we must change the stm of the engine, as it refused the last move
8489                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8490                 if(WhiteOnMove(forwardMostMove)) {
8491                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8492                     SendBoard(cps, forwardMostMove); // kludgeless board
8493                 } else {
8494                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8495                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8496                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8497                 }
8498           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8499             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8500                  gameMode == TwoMachinesPlay)
8501               SendToProgram("go\n", cps);
8502             return;
8503       } else
8504         if (gameMode == PlayFromGameFile) {
8505             /* Stop reading this game file */
8506             gameMode = EditGame;
8507             ModeHighlight();
8508         }
8509         /* [HGM] illegal-move claim should forfeit game when Xboard */
8510         /* only passes fully legal moves                            */
8511         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8512             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8513                                 "False illegal-move claim", GE_XBOARD );
8514             return; // do not take back move we tested as valid
8515         }
8516         currentMove = forwardMostMove-1;
8517         DisplayMove(currentMove-1); /* before DisplayMoveError */
8518         SwitchClocks(forwardMostMove-1); // [HGM] race
8519         DisplayBothClocks();
8520         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8521                 parseList[currentMove], _(cps->which));
8522         DisplayMoveError(buf1);
8523         DrawPosition(FALSE, boards[currentMove]);
8524
8525         SetUserThinkingEnables();
8526         return;
8527     }
8528     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8529         /* Program has a broken "time" command that
8530            outputs a string not ending in newline.
8531            Don't use it. */
8532         cps->sendTime = 0;
8533     }
8534
8535     /*
8536      * If chess program startup fails, exit with an error message.
8537      * Attempts to recover here are futile. [HGM] Well, we try anyway
8538      */
8539     if ((StrStr(message, "unknown host") != NULL)
8540         || (StrStr(message, "No remote directory") != NULL)
8541         || (StrStr(message, "not found") != NULL)
8542         || (StrStr(message, "No such file") != NULL)
8543         || (StrStr(message, "can't alloc") != NULL)
8544         || (StrStr(message, "Permission denied") != NULL)) {
8545
8546         cps->maybeThinking = FALSE;
8547         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8548                 _(cps->which), cps->program, cps->host, message);
8549         RemoveInputSource(cps->isr);
8550         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8551             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8552             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8553         }
8554         return;
8555     }
8556
8557     /*
8558      * Look for hint output
8559      */
8560     if (sscanf(message, "Hint: %s", buf1) == 1) {
8561         if (cps == &first && hintRequested) {
8562             hintRequested = FALSE;
8563             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8564                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8565                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8566                                     PosFlags(forwardMostMove),
8567                                     fromY, fromX, toY, toX, promoChar, buf1);
8568                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8569                 DisplayInformation(buf2);
8570             } else {
8571                 /* Hint move could not be parsed!? */
8572               snprintf(buf2, sizeof(buf2),
8573                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8574                         buf1, _(cps->which));
8575                 DisplayError(buf2, 0);
8576             }
8577         } else {
8578           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8579         }
8580         return;
8581     }
8582
8583     /*
8584      * Ignore other messages if game is not in progress
8585      */
8586     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8587         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8588
8589     /*
8590      * look for win, lose, draw, or draw offer
8591      */
8592     if (strncmp(message, "1-0", 3) == 0) {
8593         char *p, *q, *r = "";
8594         p = strchr(message, '{');
8595         if (p) {
8596             q = strchr(p, '}');
8597             if (q) {
8598                 *q = NULLCHAR;
8599                 r = p + 1;
8600             }
8601         }
8602         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8603         return;
8604     } else if (strncmp(message, "0-1", 3) == 0) {
8605         char *p, *q, *r = "";
8606         p = strchr(message, '{');
8607         if (p) {
8608             q = strchr(p, '}');
8609             if (q) {
8610                 *q = NULLCHAR;
8611                 r = p + 1;
8612             }
8613         }
8614         /* Kludge for Arasan 4.1 bug */
8615         if (strcmp(r, "Black resigns") == 0) {
8616             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8617             return;
8618         }
8619         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8620         return;
8621     } else if (strncmp(message, "1/2", 3) == 0) {
8622         char *p, *q, *r = "";
8623         p = strchr(message, '{');
8624         if (p) {
8625             q = strchr(p, '}');
8626             if (q) {
8627                 *q = NULLCHAR;
8628                 r = p + 1;
8629             }
8630         }
8631
8632         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8633         return;
8634
8635     } else if (strncmp(message, "White resign", 12) == 0) {
8636         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8637         return;
8638     } else if (strncmp(message, "Black resign", 12) == 0) {
8639         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8640         return;
8641     } else if (strncmp(message, "White matches", 13) == 0 ||
8642                strncmp(message, "Black matches", 13) == 0   ) {
8643         /* [HGM] ignore GNUShogi noises */
8644         return;
8645     } else if (strncmp(message, "White", 5) == 0 &&
8646                message[5] != '(' &&
8647                StrStr(message, "Black") == NULL) {
8648         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8649         return;
8650     } else if (strncmp(message, "Black", 5) == 0 &&
8651                message[5] != '(') {
8652         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8653         return;
8654     } else if (strcmp(message, "resign") == 0 ||
8655                strcmp(message, "computer resigns") == 0) {
8656         switch (gameMode) {
8657           case MachinePlaysBlack:
8658           case IcsPlayingBlack:
8659             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8660             break;
8661           case MachinePlaysWhite:
8662           case IcsPlayingWhite:
8663             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8664             break;
8665           case TwoMachinesPlay:
8666             if (cps->twoMachinesColor[0] == 'w')
8667               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8668             else
8669               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8670             break;
8671           default:
8672             /* can't happen */
8673             break;
8674         }
8675         return;
8676     } else if (strncmp(message, "opponent mates", 14) == 0) {
8677         switch (gameMode) {
8678           case MachinePlaysBlack:
8679           case IcsPlayingBlack:
8680             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8681             break;
8682           case MachinePlaysWhite:
8683           case IcsPlayingWhite:
8684             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8685             break;
8686           case TwoMachinesPlay:
8687             if (cps->twoMachinesColor[0] == 'w')
8688               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8689             else
8690               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8691             break;
8692           default:
8693             /* can't happen */
8694             break;
8695         }
8696         return;
8697     } else if (strncmp(message, "computer mates", 14) == 0) {
8698         switch (gameMode) {
8699           case MachinePlaysBlack:
8700           case IcsPlayingBlack:
8701             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8702             break;
8703           case MachinePlaysWhite:
8704           case IcsPlayingWhite:
8705             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8706             break;
8707           case TwoMachinesPlay:
8708             if (cps->twoMachinesColor[0] == 'w')
8709               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8710             else
8711               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8712             break;
8713           default:
8714             /* can't happen */
8715             break;
8716         }
8717         return;
8718     } else if (strncmp(message, "checkmate", 9) == 0) {
8719         if (WhiteOnMove(forwardMostMove)) {
8720             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8721         } else {
8722             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8723         }
8724         return;
8725     } else if (strstr(message, "Draw") != NULL ||
8726                strstr(message, "game is a draw") != NULL) {
8727         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8728         return;
8729     } else if (strstr(message, "offer") != NULL &&
8730                strstr(message, "draw") != NULL) {
8731 #if ZIPPY
8732         if (appData.zippyPlay && first.initDone) {
8733             /* Relay offer to ICS */
8734             SendToICS(ics_prefix);
8735             SendToICS("draw\n");
8736         }
8737 #endif
8738         cps->offeredDraw = 2; /* valid until this engine moves twice */
8739         if (gameMode == TwoMachinesPlay) {
8740             if (cps->other->offeredDraw) {
8741                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8742             /* [HGM] in two-machine mode we delay relaying draw offer      */
8743             /* until after we also have move, to see if it is really claim */
8744             }
8745         } else if (gameMode == MachinePlaysWhite ||
8746                    gameMode == MachinePlaysBlack) {
8747           if (userOfferedDraw) {
8748             DisplayInformation(_("Machine accepts your draw offer"));
8749             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8750           } else {
8751             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8752           }
8753         }
8754     }
8755
8756
8757     /*
8758      * Look for thinking output
8759      */
8760     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8761           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8762                                 ) {
8763         int plylev, mvleft, mvtot, curscore, time;
8764         char mvname[MOVE_LEN];
8765         u64 nodes; // [DM]
8766         char plyext;
8767         int ignore = FALSE;
8768         int prefixHint = FALSE;
8769         mvname[0] = NULLCHAR;
8770
8771         switch (gameMode) {
8772           case MachinePlaysBlack:
8773           case IcsPlayingBlack:
8774             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8775             break;
8776           case MachinePlaysWhite:
8777           case IcsPlayingWhite:
8778             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8779             break;
8780           case AnalyzeMode:
8781           case AnalyzeFile:
8782             break;
8783           case IcsObserving: /* [DM] icsEngineAnalyze */
8784             if (!appData.icsEngineAnalyze) ignore = TRUE;
8785             break;
8786           case TwoMachinesPlay:
8787             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8788                 ignore = TRUE;
8789             }
8790             break;
8791           default:
8792             ignore = TRUE;
8793             break;
8794         }
8795
8796         if (!ignore) {
8797             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8798             buf1[0] = NULLCHAR;
8799             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8800                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8801
8802                 if (plyext != ' ' && plyext != '\t') {
8803                     time *= 100;
8804                 }
8805
8806                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8807                 if( cps->scoreIsAbsolute &&
8808                     ( gameMode == MachinePlaysBlack ||
8809                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8810                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8811                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8812                      !WhiteOnMove(currentMove)
8813                     ) )
8814                 {
8815                     curscore = -curscore;
8816                 }
8817
8818                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8819
8820                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8821                         char buf[MSG_SIZ];
8822                         FILE *f;
8823                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8824                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8825                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8826                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8827                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8828                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8829                                 fclose(f);
8830                         } else DisplayError(_("failed writing PV"), 0);
8831                 }
8832
8833                 tempStats.depth = plylev;
8834                 tempStats.nodes = nodes;
8835                 tempStats.time = time;
8836                 tempStats.score = curscore;
8837                 tempStats.got_only_move = 0;
8838
8839                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8840                         int ticklen;
8841
8842                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8843                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8844                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8845                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8846                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8847                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8848                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8849                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8850                 }
8851
8852                 /* Buffer overflow protection */
8853                 if (pv[0] != NULLCHAR) {
8854                     if (strlen(pv) >= sizeof(tempStats.movelist)
8855                         && appData.debugMode) {
8856                         fprintf(debugFP,
8857                                 "PV is too long; using the first %u bytes.\n",
8858                                 (unsigned) sizeof(tempStats.movelist) - 1);
8859                     }
8860
8861                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8862                 } else {
8863                     sprintf(tempStats.movelist, " no PV\n");
8864                 }
8865
8866                 if (tempStats.seen_stat) {
8867                     tempStats.ok_to_send = 1;
8868                 }
8869
8870                 if (strchr(tempStats.movelist, '(') != NULL) {
8871                     tempStats.line_is_book = 1;
8872                     tempStats.nr_moves = 0;
8873                     tempStats.moves_left = 0;
8874                 } else {
8875                     tempStats.line_is_book = 0;
8876                 }
8877
8878                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8879                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8880
8881                 SendProgramStatsToFrontend( cps, &tempStats );
8882
8883                 /*
8884                     [AS] Protect the thinkOutput buffer from overflow... this
8885                     is only useful if buf1 hasn't overflowed first!
8886                 */
8887                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8888                          plylev,
8889                          (gameMode == TwoMachinesPlay ?
8890                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8891                          ((double) curscore) / 100.0,
8892                          prefixHint ? lastHint : "",
8893                          prefixHint ? " " : "" );
8894
8895                 if( buf1[0] != NULLCHAR ) {
8896                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8897
8898                     if( strlen(pv) > max_len ) {
8899                         if( appData.debugMode) {
8900                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8901                         }
8902                         pv[max_len+1] = '\0';
8903                     }
8904
8905                     strcat( thinkOutput, pv);
8906                 }
8907
8908                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8909                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8910                     DisplayMove(currentMove - 1);
8911                 }
8912                 return;
8913
8914             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8915                 /* crafty (9.25+) says "(only move) <move>"
8916                  * if there is only 1 legal move
8917                  */
8918                 sscanf(p, "(only move) %s", buf1);
8919                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8920                 sprintf(programStats.movelist, "%s (only move)", buf1);
8921                 programStats.depth = 1;
8922                 programStats.nr_moves = 1;
8923                 programStats.moves_left = 1;
8924                 programStats.nodes = 1;
8925                 programStats.time = 1;
8926                 programStats.got_only_move = 1;
8927
8928                 /* Not really, but we also use this member to
8929                    mean "line isn't going to change" (Crafty
8930                    isn't searching, so stats won't change) */
8931                 programStats.line_is_book = 1;
8932
8933                 SendProgramStatsToFrontend( cps, &programStats );
8934
8935                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8936                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8937                     DisplayMove(currentMove - 1);
8938                 }
8939                 return;
8940             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8941                               &time, &nodes, &plylev, &mvleft,
8942                               &mvtot, mvname) >= 5) {
8943                 /* The stat01: line is from Crafty (9.29+) in response
8944                    to the "." command */
8945                 programStats.seen_stat = 1;
8946                 cps->maybeThinking = TRUE;
8947
8948                 if (programStats.got_only_move || !appData.periodicUpdates)
8949                   return;
8950
8951                 programStats.depth = plylev;
8952                 programStats.time = time;
8953                 programStats.nodes = nodes;
8954                 programStats.moves_left = mvleft;
8955                 programStats.nr_moves = mvtot;
8956                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8957                 programStats.ok_to_send = 1;
8958                 programStats.movelist[0] = '\0';
8959
8960                 SendProgramStatsToFrontend( cps, &programStats );
8961
8962                 return;
8963
8964             } else if (strncmp(message,"++",2) == 0) {
8965                 /* Crafty 9.29+ outputs this */
8966                 programStats.got_fail = 2;
8967                 return;
8968
8969             } else if (strncmp(message,"--",2) == 0) {
8970                 /* Crafty 9.29+ outputs this */
8971                 programStats.got_fail = 1;
8972                 return;
8973
8974             } else if (thinkOutput[0] != NULLCHAR &&
8975                        strncmp(message, "    ", 4) == 0) {
8976                 unsigned message_len;
8977
8978                 p = message;
8979                 while (*p && *p == ' ') p++;
8980
8981                 message_len = strlen( p );
8982
8983                 /* [AS] Avoid buffer overflow */
8984                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8985                     strcat(thinkOutput, " ");
8986                     strcat(thinkOutput, p);
8987                 }
8988
8989                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8990                     strcat(programStats.movelist, " ");
8991                     strcat(programStats.movelist, p);
8992                 }
8993
8994                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8995                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8996                     DisplayMove(currentMove - 1);
8997                 }
8998                 return;
8999             }
9000         }
9001         else {
9002             buf1[0] = NULLCHAR;
9003
9004             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9005                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9006             {
9007                 ChessProgramStats cpstats;
9008
9009                 if (plyext != ' ' && plyext != '\t') {
9010                     time *= 100;
9011                 }
9012
9013                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9014                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9015                     curscore = -curscore;
9016                 }
9017
9018                 cpstats.depth = plylev;
9019                 cpstats.nodes = nodes;
9020                 cpstats.time = time;
9021                 cpstats.score = curscore;
9022                 cpstats.got_only_move = 0;
9023                 cpstats.movelist[0] = '\0';
9024
9025                 if (buf1[0] != NULLCHAR) {
9026                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9027                 }
9028
9029                 cpstats.ok_to_send = 0;
9030                 cpstats.line_is_book = 0;
9031                 cpstats.nr_moves = 0;
9032                 cpstats.moves_left = 0;
9033
9034                 SendProgramStatsToFrontend( cps, &cpstats );
9035             }
9036         }
9037     }
9038 }
9039
9040
9041 /* Parse a game score from the character string "game", and
9042    record it as the history of the current game.  The game
9043    score is NOT assumed to start from the standard position.
9044    The display is not updated in any way.
9045    */
9046 void
9047 ParseGameHistory (char *game)
9048 {
9049     ChessMove moveType;
9050     int fromX, fromY, toX, toY, boardIndex;
9051     char promoChar;
9052     char *p, *q;
9053     char buf[MSG_SIZ];
9054
9055     if (appData.debugMode)
9056       fprintf(debugFP, "Parsing game history: %s\n", game);
9057
9058     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9059     gameInfo.site = StrSave(appData.icsHost);
9060     gameInfo.date = PGNDate();
9061     gameInfo.round = StrSave("-");
9062
9063     /* Parse out names of players */
9064     while (*game == ' ') game++;
9065     p = buf;
9066     while (*game != ' ') *p++ = *game++;
9067     *p = NULLCHAR;
9068     gameInfo.white = StrSave(buf);
9069     while (*game == ' ') game++;
9070     p = buf;
9071     while (*game != ' ' && *game != '\n') *p++ = *game++;
9072     *p = NULLCHAR;
9073     gameInfo.black = StrSave(buf);
9074
9075     /* Parse moves */
9076     boardIndex = blackPlaysFirst ? 1 : 0;
9077     yynewstr(game);
9078     for (;;) {
9079         yyboardindex = boardIndex;
9080         moveType = (ChessMove) Myylex();
9081         switch (moveType) {
9082           case IllegalMove:             /* maybe suicide chess, etc. */
9083   if (appData.debugMode) {
9084     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9085     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9086     setbuf(debugFP, NULL);
9087   }
9088           case WhitePromotion:
9089           case BlackPromotion:
9090           case WhiteNonPromotion:
9091           case BlackNonPromotion:
9092           case NormalMove:
9093           case WhiteCapturesEnPassant:
9094           case BlackCapturesEnPassant:
9095           case WhiteKingSideCastle:
9096           case WhiteQueenSideCastle:
9097           case BlackKingSideCastle:
9098           case BlackQueenSideCastle:
9099           case WhiteKingSideCastleWild:
9100           case WhiteQueenSideCastleWild:
9101           case BlackKingSideCastleWild:
9102           case BlackQueenSideCastleWild:
9103           /* PUSH Fabien */
9104           case WhiteHSideCastleFR:
9105           case WhiteASideCastleFR:
9106           case BlackHSideCastleFR:
9107           case BlackASideCastleFR:
9108           /* POP Fabien */
9109             fromX = currentMoveString[0] - AAA;
9110             fromY = currentMoveString[1] - ONE;
9111             toX = currentMoveString[2] - AAA;
9112             toY = currentMoveString[3] - ONE;
9113             promoChar = currentMoveString[4];
9114             break;
9115           case WhiteDrop:
9116           case BlackDrop:
9117             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9118             fromX = moveType == WhiteDrop ?
9119               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9120             (int) CharToPiece(ToLower(currentMoveString[0]));
9121             fromY = DROP_RANK;
9122             toX = currentMoveString[2] - AAA;
9123             toY = currentMoveString[3] - ONE;
9124             promoChar = NULLCHAR;
9125             break;
9126           case AmbiguousMove:
9127             /* bug? */
9128             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9129   if (appData.debugMode) {
9130     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9131     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9132     setbuf(debugFP, NULL);
9133   }
9134             DisplayError(buf, 0);
9135             return;
9136           case ImpossibleMove:
9137             /* bug? */
9138             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9139   if (appData.debugMode) {
9140     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9141     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9142     setbuf(debugFP, NULL);
9143   }
9144             DisplayError(buf, 0);
9145             return;
9146           case EndOfFile:
9147             if (boardIndex < backwardMostMove) {
9148                 /* Oops, gap.  How did that happen? */
9149                 DisplayError(_("Gap in move list"), 0);
9150                 return;
9151             }
9152             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9153             if (boardIndex > forwardMostMove) {
9154                 forwardMostMove = boardIndex;
9155             }
9156             return;
9157           case ElapsedTime:
9158             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9159                 strcat(parseList[boardIndex-1], " ");
9160                 strcat(parseList[boardIndex-1], yy_text);
9161             }
9162             continue;
9163           case Comment:
9164           case PGNTag:
9165           case NAG:
9166           default:
9167             /* ignore */
9168             continue;
9169           case WhiteWins:
9170           case BlackWins:
9171           case GameIsDrawn:
9172           case GameUnfinished:
9173             if (gameMode == IcsExamining) {
9174                 if (boardIndex < backwardMostMove) {
9175                     /* Oops, gap.  How did that happen? */
9176                     return;
9177                 }
9178                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9179                 return;
9180             }
9181             gameInfo.result = moveType;
9182             p = strchr(yy_text, '{');
9183             if (p == NULL) p = strchr(yy_text, '(');
9184             if (p == NULL) {
9185                 p = yy_text;
9186                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9187             } else {
9188                 q = strchr(p, *p == '{' ? '}' : ')');
9189                 if (q != NULL) *q = NULLCHAR;
9190                 p++;
9191             }
9192             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9193             gameInfo.resultDetails = StrSave(p);
9194             continue;
9195         }
9196         if (boardIndex >= forwardMostMove &&
9197             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9198             backwardMostMove = blackPlaysFirst ? 1 : 0;
9199             return;
9200         }
9201         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9202                                  fromY, fromX, toY, toX, promoChar,
9203                                  parseList[boardIndex]);
9204         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9205         /* currentMoveString is set as a side-effect of yylex */
9206         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9207         strcat(moveList[boardIndex], "\n");
9208         boardIndex++;
9209         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9210         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9211           case MT_NONE:
9212           case MT_STALEMATE:
9213           default:
9214             break;
9215           case MT_CHECK:
9216             if(gameInfo.variant != VariantShogi)
9217                 strcat(parseList[boardIndex - 1], "+");
9218             break;
9219           case MT_CHECKMATE:
9220           case MT_STAINMATE:
9221             strcat(parseList[boardIndex - 1], "#");
9222             break;
9223         }
9224     }
9225 }
9226
9227
9228 /* Apply a move to the given board  */
9229 void
9230 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9231 {
9232   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9233   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9234
9235     /* [HGM] compute & store e.p. status and castling rights for new position */
9236     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9237
9238       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9239       oldEP = (signed char)board[EP_STATUS];
9240       board[EP_STATUS] = EP_NONE;
9241
9242   if (fromY == DROP_RANK) {
9243         /* must be first */
9244         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9245             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9246             return;
9247         }
9248         piece = board[toY][toX] = (ChessSquare) fromX;
9249   } else {
9250       int i;
9251
9252       if( board[toY][toX] != EmptySquare )
9253            board[EP_STATUS] = EP_CAPTURE;
9254
9255       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9256            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9257                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9258       } else
9259       if( board[fromY][fromX] == WhitePawn ) {
9260            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9261                board[EP_STATUS] = EP_PAWN_MOVE;
9262            if( toY-fromY==2) {
9263                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9264                         gameInfo.variant != VariantBerolina || toX < fromX)
9265                       board[EP_STATUS] = toX | berolina;
9266                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9267                         gameInfo.variant != VariantBerolina || toX > fromX)
9268                       board[EP_STATUS] = toX;
9269            }
9270       } else
9271       if( board[fromY][fromX] == BlackPawn ) {
9272            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9273                board[EP_STATUS] = EP_PAWN_MOVE;
9274            if( toY-fromY== -2) {
9275                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9276                         gameInfo.variant != VariantBerolina || toX < fromX)
9277                       board[EP_STATUS] = toX | berolina;
9278                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9279                         gameInfo.variant != VariantBerolina || toX > fromX)
9280                       board[EP_STATUS] = toX;
9281            }
9282        }
9283
9284        for(i=0; i<nrCastlingRights; i++) {
9285            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9286               board[CASTLING][i] == toX   && castlingRank[i] == toY
9287              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9288        }
9289
9290      if (fromX == toX && fromY == toY) return;
9291
9292      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9293      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9294      if(gameInfo.variant == VariantKnightmate)
9295          king += (int) WhiteUnicorn - (int) WhiteKing;
9296
9297     /* Code added by Tord: */
9298     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9299     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9300         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9301       board[fromY][fromX] = EmptySquare;
9302       board[toY][toX] = EmptySquare;
9303       if((toX > fromX) != (piece == WhiteRook)) {
9304         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9305       } else {
9306         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9307       }
9308     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9309                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9310       board[fromY][fromX] = EmptySquare;
9311       board[toY][toX] = EmptySquare;
9312       if((toX > fromX) != (piece == BlackRook)) {
9313         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9314       } else {
9315         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9316       }
9317     /* End of code added by Tord */
9318
9319     } else if (board[fromY][fromX] == king
9320         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9321         && toY == fromY && toX > fromX+1) {
9322         board[fromY][fromX] = EmptySquare;
9323         board[toY][toX] = king;
9324         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9325         board[fromY][BOARD_RGHT-1] = EmptySquare;
9326     } else if (board[fromY][fromX] == king
9327         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9328                && toY == fromY && toX < fromX-1) {
9329         board[fromY][fromX] = EmptySquare;
9330         board[toY][toX] = king;
9331         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9332         board[fromY][BOARD_LEFT] = EmptySquare;
9333     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9334                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9335                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9336                ) {
9337         /* white pawn promotion */
9338         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9339         if(gameInfo.variant==VariantBughouse ||
9340            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9341             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9342         board[fromY][fromX] = EmptySquare;
9343     } else if ((fromY >= BOARD_HEIGHT>>1)
9344                && (toX != fromX)
9345                && gameInfo.variant != VariantXiangqi
9346                && gameInfo.variant != VariantBerolina
9347                && (board[fromY][fromX] == WhitePawn)
9348                && (board[toY][toX] == EmptySquare)) {
9349         board[fromY][fromX] = EmptySquare;
9350         board[toY][toX] = WhitePawn;
9351         captured = board[toY - 1][toX];
9352         board[toY - 1][toX] = EmptySquare;
9353     } else if ((fromY == BOARD_HEIGHT-4)
9354                && (toX == fromX)
9355                && gameInfo.variant == VariantBerolina
9356                && (board[fromY][fromX] == WhitePawn)
9357                && (board[toY][toX] == EmptySquare)) {
9358         board[fromY][fromX] = EmptySquare;
9359         board[toY][toX] = WhitePawn;
9360         if(oldEP & EP_BEROLIN_A) {
9361                 captured = board[fromY][fromX-1];
9362                 board[fromY][fromX-1] = EmptySquare;
9363         }else{  captured = board[fromY][fromX+1];
9364                 board[fromY][fromX+1] = EmptySquare;
9365         }
9366     } else if (board[fromY][fromX] == king
9367         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9368                && toY == fromY && toX > fromX+1) {
9369         board[fromY][fromX] = EmptySquare;
9370         board[toY][toX] = king;
9371         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9372         board[fromY][BOARD_RGHT-1] = EmptySquare;
9373     } else if (board[fromY][fromX] == king
9374         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9375                && toY == fromY && toX < fromX-1) {
9376         board[fromY][fromX] = EmptySquare;
9377         board[toY][toX] = king;
9378         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9379         board[fromY][BOARD_LEFT] = EmptySquare;
9380     } else if (fromY == 7 && fromX == 3
9381                && board[fromY][fromX] == BlackKing
9382                && toY == 7 && toX == 5) {
9383         board[fromY][fromX] = EmptySquare;
9384         board[toY][toX] = BlackKing;
9385         board[fromY][7] = EmptySquare;
9386         board[toY][4] = BlackRook;
9387     } else if (fromY == 7 && fromX == 3
9388                && board[fromY][fromX] == BlackKing
9389                && toY == 7 && toX == 1) {
9390         board[fromY][fromX] = EmptySquare;
9391         board[toY][toX] = BlackKing;
9392         board[fromY][0] = EmptySquare;
9393         board[toY][2] = BlackRook;
9394     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9395                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9396                && toY < promoRank && promoChar
9397                ) {
9398         /* black pawn promotion */
9399         board[toY][toX] = CharToPiece(ToLower(promoChar));
9400         if(gameInfo.variant==VariantBughouse ||
9401            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9402             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9403         board[fromY][fromX] = EmptySquare;
9404     } else if ((fromY < BOARD_HEIGHT>>1)
9405                && (toX != fromX)
9406                && gameInfo.variant != VariantXiangqi
9407                && gameInfo.variant != VariantBerolina
9408                && (board[fromY][fromX] == BlackPawn)
9409                && (board[toY][toX] == EmptySquare)) {
9410         board[fromY][fromX] = EmptySquare;
9411         board[toY][toX] = BlackPawn;
9412         captured = board[toY + 1][toX];
9413         board[toY + 1][toX] = EmptySquare;
9414     } else if ((fromY == 3)
9415                && (toX == fromX)
9416                && gameInfo.variant == VariantBerolina
9417                && (board[fromY][fromX] == BlackPawn)
9418                && (board[toY][toX] == EmptySquare)) {
9419         board[fromY][fromX] = EmptySquare;
9420         board[toY][toX] = BlackPawn;
9421         if(oldEP & EP_BEROLIN_A) {
9422                 captured = board[fromY][fromX-1];
9423                 board[fromY][fromX-1] = EmptySquare;
9424         }else{  captured = board[fromY][fromX+1];
9425                 board[fromY][fromX+1] = EmptySquare;
9426         }
9427     } else {
9428         board[toY][toX] = board[fromY][fromX];
9429         board[fromY][fromX] = EmptySquare;
9430     }
9431   }
9432
9433     if (gameInfo.holdingsWidth != 0) {
9434
9435       /* !!A lot more code needs to be written to support holdings  */
9436       /* [HGM] OK, so I have written it. Holdings are stored in the */
9437       /* penultimate board files, so they are automaticlly stored   */
9438       /* in the game history.                                       */
9439       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9440                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9441         /* Delete from holdings, by decreasing count */
9442         /* and erasing image if necessary            */
9443         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9444         if(p < (int) BlackPawn) { /* white drop */
9445              p -= (int)WhitePawn;
9446                  p = PieceToNumber((ChessSquare)p);
9447              if(p >= gameInfo.holdingsSize) p = 0;
9448              if(--board[p][BOARD_WIDTH-2] <= 0)
9449                   board[p][BOARD_WIDTH-1] = EmptySquare;
9450              if((int)board[p][BOARD_WIDTH-2] < 0)
9451                         board[p][BOARD_WIDTH-2] = 0;
9452         } else {                  /* black drop */
9453              p -= (int)BlackPawn;
9454                  p = PieceToNumber((ChessSquare)p);
9455              if(p >= gameInfo.holdingsSize) p = 0;
9456              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9457                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9458              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9459                         board[BOARD_HEIGHT-1-p][1] = 0;
9460         }
9461       }
9462       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9463           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9464         /* [HGM] holdings: Add to holdings, if holdings exist */
9465         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9466                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9467                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9468         }
9469         p = (int) captured;
9470         if (p >= (int) BlackPawn) {
9471           p -= (int)BlackPawn;
9472           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9473                   /* in Shogi restore piece to its original  first */
9474                   captured = (ChessSquare) (DEMOTED captured);
9475                   p = DEMOTED p;
9476           }
9477           p = PieceToNumber((ChessSquare)p);
9478           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9479           board[p][BOARD_WIDTH-2]++;
9480           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9481         } else {
9482           p -= (int)WhitePawn;
9483           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9484                   captured = (ChessSquare) (DEMOTED captured);
9485                   p = DEMOTED p;
9486           }
9487           p = PieceToNumber((ChessSquare)p);
9488           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9489           board[BOARD_HEIGHT-1-p][1]++;
9490           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9491         }
9492       }
9493     } else if (gameInfo.variant == VariantAtomic) {
9494       if (captured != EmptySquare) {
9495         int y, x;
9496         for (y = toY-1; y <= toY+1; y++) {
9497           for (x = toX-1; x <= toX+1; x++) {
9498             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9499                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9500               board[y][x] = EmptySquare;
9501             }
9502           }
9503         }
9504         board[toY][toX] = EmptySquare;
9505       }
9506     }
9507     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9508         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9509     } else
9510     if(promoChar == '+') {
9511         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9512         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9513     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9514         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9515         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9516            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9517         board[toY][toX] = newPiece;
9518     }
9519     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9520                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9521         // [HGM] superchess: take promotion piece out of holdings
9522         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9523         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9524             if(!--board[k][BOARD_WIDTH-2])
9525                 board[k][BOARD_WIDTH-1] = EmptySquare;
9526         } else {
9527             if(!--board[BOARD_HEIGHT-1-k][1])
9528                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9529         }
9530     }
9531
9532 }
9533
9534 /* Updates forwardMostMove */
9535 void
9536 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9537 {
9538 //    forwardMostMove++; // [HGM] bare: moved downstream
9539
9540     (void) CoordsToAlgebraic(boards[forwardMostMove],
9541                              PosFlags(forwardMostMove),
9542                              fromY, fromX, toY, toX, promoChar,
9543                              parseList[forwardMostMove]);
9544
9545     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9546         int timeLeft; static int lastLoadFlag=0; int king, piece;
9547         piece = boards[forwardMostMove][fromY][fromX];
9548         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9549         if(gameInfo.variant == VariantKnightmate)
9550             king += (int) WhiteUnicorn - (int) WhiteKing;
9551         if(forwardMostMove == 0) {
9552             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9553                 fprintf(serverMoves, "%s;", UserName());
9554             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9555                 fprintf(serverMoves, "%s;", second.tidy);
9556             fprintf(serverMoves, "%s;", first.tidy);
9557             if(gameMode == MachinePlaysWhite)
9558                 fprintf(serverMoves, "%s;", UserName());
9559             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9560                 fprintf(serverMoves, "%s;", second.tidy);
9561         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9562         lastLoadFlag = loadFlag;
9563         // print base move
9564         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9565         // print castling suffix
9566         if( toY == fromY && piece == king ) {
9567             if(toX-fromX > 1)
9568                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9569             if(fromX-toX >1)
9570                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9571         }
9572         // e.p. suffix
9573         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9574              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9575              boards[forwardMostMove][toY][toX] == EmptySquare
9576              && fromX != toX && fromY != toY)
9577                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9578         // promotion suffix
9579         if(promoChar != NULLCHAR)
9580                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9581         if(!loadFlag) {
9582                 char buf[MOVE_LEN*2], *p; int len;
9583             fprintf(serverMoves, "/%d/%d",
9584                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9585             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9586             else                      timeLeft = blackTimeRemaining/1000;
9587             fprintf(serverMoves, "/%d", timeLeft);
9588                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9589                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9590                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9591             fprintf(serverMoves, "/%s", buf);
9592         }
9593         fflush(serverMoves);
9594     }
9595
9596     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9597         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9598       return;
9599     }
9600     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9601     if (commentList[forwardMostMove+1] != NULL) {
9602         free(commentList[forwardMostMove+1]);
9603         commentList[forwardMostMove+1] = NULL;
9604     }
9605     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9606     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9607     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9608     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9609     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9610     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9611     adjustedClock = FALSE;
9612     gameInfo.result = GameUnfinished;
9613     if (gameInfo.resultDetails != NULL) {
9614         free(gameInfo.resultDetails);
9615         gameInfo.resultDetails = NULL;
9616     }
9617     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9618                               moveList[forwardMostMove - 1]);
9619     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9620       case MT_NONE:
9621       case MT_STALEMATE:
9622       default:
9623         break;
9624       case MT_CHECK:
9625         if(gameInfo.variant != VariantShogi)
9626             strcat(parseList[forwardMostMove - 1], "+");
9627         break;
9628       case MT_CHECKMATE:
9629       case MT_STAINMATE:
9630         strcat(parseList[forwardMostMove - 1], "#");
9631         break;
9632     }
9633
9634 }
9635
9636 /* Updates currentMove if not pausing */
9637 void
9638 ShowMove (int fromX, int fromY, int toX, int toY)
9639 {
9640     int instant = (gameMode == PlayFromGameFile) ?
9641         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9642     if(appData.noGUI) return;
9643     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9644         if (!instant) {
9645             if (forwardMostMove == currentMove + 1) {
9646                 AnimateMove(boards[forwardMostMove - 1],
9647                             fromX, fromY, toX, toY);
9648             }
9649             if (appData.highlightLastMove) {
9650                 SetHighlights(fromX, fromY, toX, toY);
9651             }
9652         }
9653         currentMove = forwardMostMove;
9654     }
9655
9656     if (instant) return;
9657
9658     DisplayMove(currentMove - 1);
9659     DrawPosition(FALSE, boards[currentMove]);
9660     DisplayBothClocks();
9661     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9662 }
9663
9664 void
9665 SendEgtPath (ChessProgramState *cps)
9666 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9667         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9668
9669         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9670
9671         while(*p) {
9672             char c, *q = name+1, *r, *s;
9673
9674             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9675             while(*p && *p != ',') *q++ = *p++;
9676             *q++ = ':'; *q = 0;
9677             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9678                 strcmp(name, ",nalimov:") == 0 ) {
9679                 // take nalimov path from the menu-changeable option first, if it is defined
9680               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9681                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9682             } else
9683             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9684                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9685                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9686                 s = r = StrStr(s, ":") + 1; // beginning of path info
9687                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9688                 c = *r; *r = 0;             // temporarily null-terminate path info
9689                     *--q = 0;               // strip of trailig ':' from name
9690                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9691                 *r = c;
9692                 SendToProgram(buf,cps);     // send egtbpath command for this format
9693             }
9694             if(*p == ',') p++; // read away comma to position for next format name
9695         }
9696 }
9697
9698 void
9699 InitChessProgram (ChessProgramState *cps, int setup)
9700 /* setup needed to setup FRC opening position */
9701 {
9702     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9703     if (appData.noChessProgram) return;
9704     hintRequested = FALSE;
9705     bookRequested = FALSE;
9706
9707     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9708     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9709     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9710     if(cps->memSize) { /* [HGM] memory */
9711       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9712         SendToProgram(buf, cps);
9713     }
9714     SendEgtPath(cps); /* [HGM] EGT */
9715     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9716       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9717         SendToProgram(buf, cps);
9718     }
9719
9720     SendToProgram(cps->initString, cps);
9721     if (gameInfo.variant != VariantNormal &&
9722         gameInfo.variant != VariantLoadable
9723         /* [HGM] also send variant if board size non-standard */
9724         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9725                                             ) {
9726       char *v = VariantName(gameInfo.variant);
9727       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9728         /* [HGM] in protocol 1 we have to assume all variants valid */
9729         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9730         DisplayFatalError(buf, 0, 1);
9731         return;
9732       }
9733
9734       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9735       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9736       if( gameInfo.variant == VariantXiangqi )
9737            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9738       if( gameInfo.variant == VariantShogi )
9739            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9740       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9741            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9742       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9743           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9744            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9745       if( gameInfo.variant == VariantCourier )
9746            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9747       if( gameInfo.variant == VariantSuper )
9748            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9749       if( gameInfo.variant == VariantGreat )
9750            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9751       if( gameInfo.variant == VariantSChess )
9752            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9753       if( gameInfo.variant == VariantGrand )
9754            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9755
9756       if(overruled) {
9757         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9758                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9759            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9760            if(StrStr(cps->variants, b) == NULL) {
9761                // specific sized variant not known, check if general sizing allowed
9762                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9763                    if(StrStr(cps->variants, "boardsize") == NULL) {
9764                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9765                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9766                        DisplayFatalError(buf, 0, 1);
9767                        return;
9768                    }
9769                    /* [HGM] here we really should compare with the maximum supported board size */
9770                }
9771            }
9772       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9773       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9774       SendToProgram(buf, cps);
9775     }
9776     currentlyInitializedVariant = gameInfo.variant;
9777
9778     /* [HGM] send opening position in FRC to first engine */
9779     if(setup) {
9780           SendToProgram("force\n", cps);
9781           SendBoard(cps, 0);
9782           /* engine is now in force mode! Set flag to wake it up after first move. */
9783           setboardSpoiledMachineBlack = 1;
9784     }
9785
9786     if (cps->sendICS) {
9787       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9788       SendToProgram(buf, cps);
9789     }
9790     cps->maybeThinking = FALSE;
9791     cps->offeredDraw = 0;
9792     if (!appData.icsActive) {
9793         SendTimeControl(cps, movesPerSession, timeControl,
9794                         timeIncrement, appData.searchDepth,
9795                         searchTime);
9796     }
9797     if (appData.showThinking
9798         // [HGM] thinking: four options require thinking output to be sent
9799         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9800                                 ) {
9801         SendToProgram("post\n", cps);
9802     }
9803     SendToProgram("hard\n", cps);
9804     if (!appData.ponderNextMove) {
9805         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9806            it without being sure what state we are in first.  "hard"
9807            is not a toggle, so that one is OK.
9808          */
9809         SendToProgram("easy\n", cps);
9810     }
9811     if (cps->usePing) {
9812       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9813       SendToProgram(buf, cps);
9814     }
9815     cps->initDone = TRUE;
9816     ClearEngineOutputPane(cps == &second);
9817 }
9818
9819
9820 void
9821 StartChessProgram (ChessProgramState *cps)
9822 {
9823     char buf[MSG_SIZ];
9824     int err;
9825
9826     if (appData.noChessProgram) return;
9827     cps->initDone = FALSE;
9828
9829     if (strcmp(cps->host, "localhost") == 0) {
9830         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9831     } else if (*appData.remoteShell == NULLCHAR) {
9832         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9833     } else {
9834         if (*appData.remoteUser == NULLCHAR) {
9835           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9836                     cps->program);
9837         } else {
9838           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9839                     cps->host, appData.remoteUser, cps->program);
9840         }
9841         err = StartChildProcess(buf, "", &cps->pr);
9842     }
9843
9844     if (err != 0) {
9845       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9846         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9847         if(cps != &first) return;
9848         appData.noChessProgram = TRUE;
9849         ThawUI();
9850         SetNCPMode();
9851 //      DisplayFatalError(buf, err, 1);
9852 //      cps->pr = NoProc;
9853 //      cps->isr = NULL;
9854         return;
9855     }
9856
9857     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9858     if (cps->protocolVersion > 1) {
9859       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9860       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9861       cps->comboCnt = 0;  //                and values of combo boxes
9862       SendToProgram(buf, cps);
9863     } else {
9864       SendToProgram("xboard\n", cps);
9865     }
9866 }
9867
9868 void
9869 TwoMachinesEventIfReady P((void))
9870 {
9871   static int curMess = 0;
9872   if (first.lastPing != first.lastPong) {
9873     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9874     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9875     return;
9876   }
9877   if (second.lastPing != second.lastPong) {
9878     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9879     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9880     return;
9881   }
9882   DisplayMessage("", ""); curMess = 0;
9883   ThawUI();
9884   TwoMachinesEvent();
9885 }
9886
9887 char *
9888 MakeName (char *template)
9889 {
9890     time_t clock;
9891     struct tm *tm;
9892     static char buf[MSG_SIZ];
9893     char *p = buf;
9894     int i;
9895
9896     clock = time((time_t *)NULL);
9897     tm = localtime(&clock);
9898
9899     while(*p++ = *template++) if(p[-1] == '%') {
9900         switch(*template++) {
9901           case 0:   *p = 0; return buf;
9902           case 'Y': i = tm->tm_year+1900; break;
9903           case 'y': i = tm->tm_year-100; break;
9904           case 'M': i = tm->tm_mon+1; break;
9905           case 'd': i = tm->tm_mday; break;
9906           case 'h': i = tm->tm_hour; break;
9907           case 'm': i = tm->tm_min; break;
9908           case 's': i = tm->tm_sec; break;
9909           default:  i = 0;
9910         }
9911         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9912     }
9913     return buf;
9914 }
9915
9916 int
9917 CountPlayers (char *p)
9918 {
9919     int n = 0;
9920     while(p = strchr(p, '\n')) p++, n++; // count participants
9921     return n;
9922 }
9923
9924 FILE *
9925 WriteTourneyFile (char *results, FILE *f)
9926 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9927     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9928     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9929         // create a file with tournament description
9930         fprintf(f, "-participants {%s}\n", appData.participants);
9931         fprintf(f, "-seedBase %d\n", appData.seedBase);
9932         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9933         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9934         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9935         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9936         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9937         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9938         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9939         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9940         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9941         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9942         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9943         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9944         if(searchTime > 0)
9945                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9946         else {
9947                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9948                 fprintf(f, "-tc %s\n", appData.timeControl);
9949                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9950         }
9951         fprintf(f, "-results \"%s\"\n", results);
9952     }
9953     return f;
9954 }
9955
9956 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9957
9958 void
9959 Substitute (char *participants, int expunge)
9960 {
9961     int i, changed, changes=0, nPlayers=0;
9962     char *p, *q, *r, buf[MSG_SIZ];
9963     if(participants == NULL) return;
9964     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9965     r = p = participants; q = appData.participants;
9966     while(*p && *p == *q) {
9967         if(*p == '\n') r = p+1, nPlayers++;
9968         p++; q++;
9969     }
9970     if(*p) { // difference
9971         while(*p && *p++ != '\n');
9972         while(*q && *q++ != '\n');
9973       changed = nPlayers;
9974         changes = 1 + (strcmp(p, q) != 0);
9975     }
9976     if(changes == 1) { // a single engine mnemonic was changed
9977         q = r; while(*q) nPlayers += (*q++ == '\n');
9978         p = buf; while(*r && (*p = *r++) != '\n') p++;
9979         *p = NULLCHAR;
9980         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9981         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9982         if(mnemonic[i]) { // The substitute is valid
9983             FILE *f;
9984             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9985                 flock(fileno(f), LOCK_EX);
9986                 ParseArgsFromFile(f);
9987                 fseek(f, 0, SEEK_SET);
9988                 FREE(appData.participants); appData.participants = participants;
9989                 if(expunge) { // erase results of replaced engine
9990                     int len = strlen(appData.results), w, b, dummy;
9991                     for(i=0; i<len; i++) {
9992                         Pairing(i, nPlayers, &w, &b, &dummy);
9993                         if((w == changed || b == changed) && appData.results[i] == '*') {
9994                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9995                             fclose(f);
9996                             return;
9997                         }
9998                     }
9999                     for(i=0; i<len; i++) {
10000                         Pairing(i, nPlayers, &w, &b, &dummy);
10001                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10002                     }
10003                 }
10004                 WriteTourneyFile(appData.results, f);
10005                 fclose(f); // release lock
10006                 return;
10007             }
10008         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10009     }
10010     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10011     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10012     free(participants);
10013     return;
10014 }
10015
10016 int
10017 CreateTourney (char *name)
10018 {
10019         FILE *f;
10020         if(matchMode && strcmp(name, appData.tourneyFile)) {
10021              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10022         }
10023         if(name[0] == NULLCHAR) {
10024             if(appData.participants[0])
10025                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10026             return 0;
10027         }
10028         f = fopen(name, "r");
10029         if(f) { // file exists
10030             ASSIGN(appData.tourneyFile, name);
10031             ParseArgsFromFile(f); // parse it
10032         } else {
10033             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10034             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10035                 DisplayError(_("Not enough participants"), 0);
10036                 return 0;
10037             }
10038             ASSIGN(appData.tourneyFile, name);
10039             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10040             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10041         }
10042         fclose(f);
10043         appData.noChessProgram = FALSE;
10044         appData.clockMode = TRUE;
10045         SetGNUMode();
10046         return 1;
10047 }
10048
10049 int
10050 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10051 {
10052     char buf[MSG_SIZ], *p, *q;
10053     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10054     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10055     skip = !all && group[0]; // if group requested, we start in skip mode
10056     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10057         p = names; q = buf; header = 0;
10058         while(*p && *p != '\n') *q++ = *p++;
10059         *q = 0;
10060         if(*p == '\n') p++;
10061         if(buf[0] == '#') {
10062             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10063             depth++; // we must be entering a new group
10064             if(all) continue; // suppress printing group headers when complete list requested
10065             header = 1;
10066             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10067         }
10068         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10069         if(engineList[i]) free(engineList[i]);
10070         engineList[i] = strdup(buf);
10071         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10072         if(engineMnemonic[i]) free(engineMnemonic[i]);
10073         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10074             strcat(buf, " (");
10075             sscanf(q + 8, "%s", buf + strlen(buf));
10076             strcat(buf, ")");
10077         }
10078         engineMnemonic[i] = strdup(buf);
10079         i++;
10080     }
10081     engineList[i] = engineMnemonic[i] = NULL;
10082     return i;
10083 }
10084
10085 // following implemented as macro to avoid type limitations
10086 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10087
10088 void
10089 SwapEngines (int n)
10090 {   // swap settings for first engine and other engine (so far only some selected options)
10091     int h;
10092     char *p;
10093     if(n == 0) return;
10094     SWAP(directory, p)
10095     SWAP(chessProgram, p)
10096     SWAP(isUCI, h)
10097     SWAP(hasOwnBookUCI, h)
10098     SWAP(protocolVersion, h)
10099     SWAP(reuse, h)
10100     SWAP(scoreIsAbsolute, h)
10101     SWAP(timeOdds, h)
10102     SWAP(logo, p)
10103     SWAP(pgnName, p)
10104     SWAP(pvSAN, h)
10105     SWAP(engOptions, p)
10106     SWAP(engInitString, p)
10107     SWAP(computerString, p)
10108     SWAP(features, p)
10109     SWAP(fenOverride, p)
10110     SWAP(NPS, h)
10111     SWAP(accumulateTC, h)
10112     SWAP(host, p)
10113 }
10114
10115 int
10116 SetPlayer (int player, char *p)
10117 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10118     int i;
10119     char buf[MSG_SIZ], *engineName;
10120     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10121     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10122     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10123     if(mnemonic[i]) {
10124         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10125         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10126         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10127         ParseArgsFromString(buf);
10128     }
10129     free(engineName);
10130     return i;
10131 }
10132
10133 char *recentEngines;
10134
10135 void
10136 RecentEngineEvent (int nr)
10137 {
10138     int n;
10139 //    SwapEngines(1); // bump first to second
10140 //    ReplaceEngine(&second, 1); // and load it there
10141     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10142     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10143     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10144         ReplaceEngine(&first, 0);
10145         FloatToFront(&appData.recentEngineList, command[n]);
10146     }
10147 }
10148
10149 int
10150 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10151 {   // determine players from game number
10152     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10153
10154     if(appData.tourneyType == 0) {
10155         roundsPerCycle = (nPlayers - 1) | 1;
10156         pairingsPerRound = nPlayers / 2;
10157     } else if(appData.tourneyType > 0) {
10158         roundsPerCycle = nPlayers - appData.tourneyType;
10159         pairingsPerRound = appData.tourneyType;
10160     }
10161     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10162     gamesPerCycle = gamesPerRound * roundsPerCycle;
10163     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10164     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10165     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10166     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10167     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10168     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10169
10170     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10171     if(appData.roundSync) *syncInterval = gamesPerRound;
10172
10173     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10174
10175     if(appData.tourneyType == 0) {
10176         if(curPairing == (nPlayers-1)/2 ) {
10177             *whitePlayer = curRound;
10178             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10179         } else {
10180             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10181             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10182             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10183             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10184         }
10185     } else if(appData.tourneyType > 1) {
10186         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10187         *whitePlayer = curRound + appData.tourneyType;
10188     } else if(appData.tourneyType > 0) {
10189         *whitePlayer = curPairing;
10190         *blackPlayer = curRound + appData.tourneyType;
10191     }
10192
10193     // take care of white/black alternation per round. 
10194     // For cycles and games this is already taken care of by default, derived from matchGame!
10195     return curRound & 1;
10196 }
10197
10198 int
10199 NextTourneyGame (int nr, int *swapColors)
10200 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10201     char *p, *q;
10202     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10203     FILE *tf;
10204     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10205     tf = fopen(appData.tourneyFile, "r");
10206     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10207     ParseArgsFromFile(tf); fclose(tf);
10208     InitTimeControls(); // TC might be altered from tourney file
10209
10210     nPlayers = CountPlayers(appData.participants); // count participants
10211     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10212     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10213
10214     if(syncInterval) {
10215         p = q = appData.results;
10216         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10217         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10218             DisplayMessage(_("Waiting for other game(s)"),"");
10219             waitingForGame = TRUE;
10220             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10221             return 0;
10222         }
10223         waitingForGame = FALSE;
10224     }
10225
10226     if(appData.tourneyType < 0) {
10227         if(nr>=0 && !pairingReceived) {
10228             char buf[1<<16];
10229             if(pairing.pr == NoProc) {
10230                 if(!appData.pairingEngine[0]) {
10231                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10232                     return 0;
10233                 }
10234                 StartChessProgram(&pairing); // starts the pairing engine
10235             }
10236             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10237             SendToProgram(buf, &pairing);
10238             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10239             SendToProgram(buf, &pairing);
10240             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10241         }
10242         pairingReceived = 0;                              // ... so we continue here 
10243         *swapColors = 0;
10244         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10245         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10246         matchGame = 1; roundNr = nr / syncInterval + 1;
10247     }
10248
10249     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10250
10251     // redefine engines, engine dir, etc.
10252     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10253     if(first.pr == NoProc) {
10254       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10255       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10256     }
10257     if(second.pr == NoProc) {
10258       SwapEngines(1);
10259       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10260       SwapEngines(1);         // and make that valid for second engine by swapping
10261       InitEngine(&second, 1);
10262     }
10263     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10264     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10265     return 1;
10266 }
10267
10268 void
10269 NextMatchGame ()
10270 {   // performs game initialization that does not invoke engines, and then tries to start the game
10271     int res, firstWhite, swapColors = 0;
10272     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10273     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
10274         char buf[MSG_SIZ];
10275         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10276         if(strcmp(buf, currentDebugFile)) { // name has changed
10277             FILE *f = fopen(buf, "w");
10278             if(f) { // if opening the new file failed, just keep using the old one
10279                 ASSIGN(currentDebugFile, buf);
10280                 fclose(debugFP);
10281                 debugFP = f;
10282             }
10283             if(appData.serverFileName) {
10284                 if(serverFP) fclose(serverFP);
10285                 serverFP = fopen(appData.serverFileName, "w");
10286                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10287                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10288             }
10289         }
10290     }
10291     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10292     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10293     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10294     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10295     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10296     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10297     Reset(FALSE, first.pr != NoProc);
10298     res = LoadGameOrPosition(matchGame); // setup game
10299     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10300     if(!res) return; // abort when bad game/pos file
10301     TwoMachinesEvent();
10302 }
10303
10304 void
10305 UserAdjudicationEvent (int result)
10306 {
10307     ChessMove gameResult = GameIsDrawn;
10308
10309     if( result > 0 ) {
10310         gameResult = WhiteWins;
10311     }
10312     else if( result < 0 ) {
10313         gameResult = BlackWins;
10314     }
10315
10316     if( gameMode == TwoMachinesPlay ) {
10317         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10318     }
10319 }
10320
10321
10322 // [HGM] save: calculate checksum of game to make games easily identifiable
10323 int
10324 StringCheckSum (char *s)
10325 {
10326         int i = 0;
10327         if(s==NULL) return 0;
10328         while(*s) i = i*259 + *s++;
10329         return i;
10330 }
10331
10332 int
10333 GameCheckSum ()
10334 {
10335         int i, sum=0;
10336         for(i=backwardMostMove; i<forwardMostMove; i++) {
10337                 sum += pvInfoList[i].depth;
10338                 sum += StringCheckSum(parseList[i]);
10339                 sum += StringCheckSum(commentList[i]);
10340                 sum *= 261;
10341         }
10342         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10343         return sum + StringCheckSum(commentList[i]);
10344 } // end of save patch
10345
10346 void
10347 GameEnds (ChessMove result, char *resultDetails, int whosays)
10348 {
10349     GameMode nextGameMode;
10350     int isIcsGame;
10351     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10352
10353     if(endingGame) return; /* [HGM] crash: forbid recursion */
10354     endingGame = 1;
10355     if(twoBoards) { // [HGM] dual: switch back to one board
10356         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10357         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10358     }
10359     if (appData.debugMode) {
10360       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10361               result, resultDetails ? resultDetails : "(null)", whosays);
10362     }
10363
10364     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10365
10366     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10367         /* If we are playing on ICS, the server decides when the
10368            game is over, but the engine can offer to draw, claim
10369            a draw, or resign.
10370          */
10371 #if ZIPPY
10372         if (appData.zippyPlay && first.initDone) {
10373             if (result == GameIsDrawn) {
10374                 /* In case draw still needs to be claimed */
10375                 SendToICS(ics_prefix);
10376                 SendToICS("draw\n");
10377             } else if (StrCaseStr(resultDetails, "resign")) {
10378                 SendToICS(ics_prefix);
10379                 SendToICS("resign\n");
10380             }
10381         }
10382 #endif
10383         endingGame = 0; /* [HGM] crash */
10384         return;
10385     }
10386
10387     /* If we're loading the game from a file, stop */
10388     if (whosays == GE_FILE) {
10389       (void) StopLoadGameTimer();
10390       gameFileFP = NULL;
10391     }
10392
10393     /* Cancel draw offers */
10394     first.offeredDraw = second.offeredDraw = 0;
10395
10396     /* If this is an ICS game, only ICS can really say it's done;
10397        if not, anyone can. */
10398     isIcsGame = (gameMode == IcsPlayingWhite ||
10399                  gameMode == IcsPlayingBlack ||
10400                  gameMode == IcsObserving    ||
10401                  gameMode == IcsExamining);
10402
10403     if (!isIcsGame || whosays == GE_ICS) {
10404         /* OK -- not an ICS game, or ICS said it was done */
10405         StopClocks();
10406         if (!isIcsGame && !appData.noChessProgram)
10407           SetUserThinkingEnables();
10408
10409         /* [HGM] if a machine claims the game end we verify this claim */
10410         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10411             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10412                 char claimer;
10413                 ChessMove trueResult = (ChessMove) -1;
10414
10415                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10416                                             first.twoMachinesColor[0] :
10417                                             second.twoMachinesColor[0] ;
10418
10419                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10420                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10421                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10422                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10423                 } else
10424                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10425                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10426                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10427                 } else
10428                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10429                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10430                 }
10431
10432                 // now verify win claims, but not in drop games, as we don't understand those yet
10433                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10434                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10435                     (result == WhiteWins && claimer == 'w' ||
10436                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10437                       if (appData.debugMode) {
10438                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10439                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10440                       }
10441                       if(result != trueResult) {
10442                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10443                               result = claimer == 'w' ? BlackWins : WhiteWins;
10444                               resultDetails = buf;
10445                       }
10446                 } else
10447                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10448                     && (forwardMostMove <= backwardMostMove ||
10449                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10450                         (claimer=='b')==(forwardMostMove&1))
10451                                                                                   ) {
10452                       /* [HGM] verify: draws that were not flagged are false claims */
10453                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10454                       result = claimer == 'w' ? BlackWins : WhiteWins;
10455                       resultDetails = buf;
10456                 }
10457                 /* (Claiming a loss is accepted no questions asked!) */
10458             }
10459             /* [HGM] bare: don't allow bare King to win */
10460             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10461                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10462                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10463                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10464                && result != GameIsDrawn)
10465             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10466                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10467                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10468                         if(p >= 0 && p <= (int)WhiteKing) k++;
10469                 }
10470                 if (appData.debugMode) {
10471                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10472                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10473                 }
10474                 if(k <= 1) {
10475                         result = GameIsDrawn;
10476                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10477                         resultDetails = buf;
10478                 }
10479             }
10480         }
10481
10482
10483         if(serverMoves != NULL && !loadFlag) { char c = '=';
10484             if(result==WhiteWins) c = '+';
10485             if(result==BlackWins) c = '-';
10486             if(resultDetails != NULL)
10487                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10488         }
10489         if (resultDetails != NULL) {
10490             gameInfo.result = result;
10491             gameInfo.resultDetails = StrSave(resultDetails);
10492
10493             /* display last move only if game was not loaded from file */
10494             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10495                 DisplayMove(currentMove - 1);
10496
10497             if (forwardMostMove != 0) {
10498                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10499                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10500                                                                 ) {
10501                     if (*appData.saveGameFile != NULLCHAR) {
10502                         SaveGameToFile(appData.saveGameFile, TRUE);
10503                     } else if (appData.autoSaveGames) {
10504                         AutoSaveGame();
10505                     }
10506                     if (*appData.savePositionFile != NULLCHAR) {
10507                         SavePositionToFile(appData.savePositionFile);
10508                     }
10509                 }
10510             }
10511
10512             /* Tell program how game ended in case it is learning */
10513             /* [HGM] Moved this to after saving the PGN, just in case */
10514             /* engine died and we got here through time loss. In that */
10515             /* case we will get a fatal error writing the pipe, which */
10516             /* would otherwise lose us the PGN.                       */
10517             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10518             /* output during GameEnds should never be fatal anymore   */
10519             if (gameMode == MachinePlaysWhite ||
10520                 gameMode == MachinePlaysBlack ||
10521                 gameMode == TwoMachinesPlay ||
10522                 gameMode == IcsPlayingWhite ||
10523                 gameMode == IcsPlayingBlack ||
10524                 gameMode == BeginningOfGame) {
10525                 char buf[MSG_SIZ];
10526                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10527                         resultDetails);
10528                 if (first.pr != NoProc) {
10529                     SendToProgram(buf, &first);
10530                 }
10531                 if (second.pr != NoProc &&
10532                     gameMode == TwoMachinesPlay) {
10533                     SendToProgram(buf, &second);
10534                 }
10535             }
10536         }
10537
10538         if (appData.icsActive) {
10539             if (appData.quietPlay &&
10540                 (gameMode == IcsPlayingWhite ||
10541                  gameMode == IcsPlayingBlack)) {
10542                 SendToICS(ics_prefix);
10543                 SendToICS("set shout 1\n");
10544             }
10545             nextGameMode = IcsIdle;
10546             ics_user_moved = FALSE;
10547             /* clean up premove.  It's ugly when the game has ended and the
10548              * premove highlights are still on the board.
10549              */
10550             if (gotPremove) {
10551               gotPremove = FALSE;
10552               ClearPremoveHighlights();
10553               DrawPosition(FALSE, boards[currentMove]);
10554             }
10555             if (whosays == GE_ICS) {
10556                 switch (result) {
10557                 case WhiteWins:
10558                     if (gameMode == IcsPlayingWhite)
10559                         PlayIcsWinSound();
10560                     else if(gameMode == IcsPlayingBlack)
10561                         PlayIcsLossSound();
10562                     break;
10563                 case BlackWins:
10564                     if (gameMode == IcsPlayingBlack)
10565                         PlayIcsWinSound();
10566                     else if(gameMode == IcsPlayingWhite)
10567                         PlayIcsLossSound();
10568                     break;
10569                 case GameIsDrawn:
10570                     PlayIcsDrawSound();
10571                     break;
10572                 default:
10573                     PlayIcsUnfinishedSound();
10574                 }
10575             }
10576         } else if (gameMode == EditGame ||
10577                    gameMode == PlayFromGameFile ||
10578                    gameMode == AnalyzeMode ||
10579                    gameMode == AnalyzeFile) {
10580             nextGameMode = gameMode;
10581         } else {
10582             nextGameMode = EndOfGame;
10583         }
10584         pausing = FALSE;
10585         ModeHighlight();
10586     } else {
10587         nextGameMode = gameMode;
10588     }
10589
10590     if (appData.noChessProgram) {
10591         gameMode = nextGameMode;
10592         ModeHighlight();
10593         endingGame = 0; /* [HGM] crash */
10594         return;
10595     }
10596
10597     if (first.reuse) {
10598         /* Put first chess program into idle state */
10599         if (first.pr != NoProc &&
10600             (gameMode == MachinePlaysWhite ||
10601              gameMode == MachinePlaysBlack ||
10602              gameMode == TwoMachinesPlay ||
10603              gameMode == IcsPlayingWhite ||
10604              gameMode == IcsPlayingBlack ||
10605              gameMode == BeginningOfGame)) {
10606             SendToProgram("force\n", &first);
10607             if (first.usePing) {
10608               char buf[MSG_SIZ];
10609               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10610               SendToProgram(buf, &first);
10611             }
10612         }
10613     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10614         /* Kill off first chess program */
10615         if (first.isr != NULL)
10616           RemoveInputSource(first.isr);
10617         first.isr = NULL;
10618
10619         if (first.pr != NoProc) {
10620             ExitAnalyzeMode();
10621             DoSleep( appData.delayBeforeQuit );
10622             SendToProgram("quit\n", &first);
10623             DoSleep( appData.delayAfterQuit );
10624             DestroyChildProcess(first.pr, first.useSigterm);
10625         }
10626         first.pr = NoProc;
10627     }
10628     if (second.reuse) {
10629         /* Put second chess program into idle state */
10630         if (second.pr != NoProc &&
10631             gameMode == TwoMachinesPlay) {
10632             SendToProgram("force\n", &second);
10633             if (second.usePing) {
10634               char buf[MSG_SIZ];
10635               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10636               SendToProgram(buf, &second);
10637             }
10638         }
10639     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10640         /* Kill off second chess program */
10641         if (second.isr != NULL)
10642           RemoveInputSource(second.isr);
10643         second.isr = NULL;
10644
10645         if (second.pr != NoProc) {
10646             DoSleep( appData.delayBeforeQuit );
10647             SendToProgram("quit\n", &second);
10648             DoSleep( appData.delayAfterQuit );
10649             DestroyChildProcess(second.pr, second.useSigterm);
10650         }
10651         second.pr = NoProc;
10652     }
10653
10654     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10655         char resChar = '=';
10656         switch (result) {
10657         case WhiteWins:
10658           resChar = '+';
10659           if (first.twoMachinesColor[0] == 'w') {
10660             first.matchWins++;
10661           } else {
10662             second.matchWins++;
10663           }
10664           break;
10665         case BlackWins:
10666           resChar = '-';
10667           if (first.twoMachinesColor[0] == 'b') {
10668             first.matchWins++;
10669           } else {
10670             second.matchWins++;
10671           }
10672           break;
10673         case GameUnfinished:
10674           resChar = ' ';
10675         default:
10676           break;
10677         }
10678
10679         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10680         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10681             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10682             ReserveGame(nextGame, resChar); // sets nextGame
10683             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10684             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10685         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10686
10687         if (nextGame <= appData.matchGames && !abortMatch) {
10688             gameMode = nextGameMode;
10689             matchGame = nextGame; // this will be overruled in tourney mode!
10690             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10691             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10692             endingGame = 0; /* [HGM] crash */
10693             return;
10694         } else {
10695             gameMode = nextGameMode;
10696             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10697                      first.tidy, second.tidy,
10698                      first.matchWins, second.matchWins,
10699                      appData.matchGames - (first.matchWins + second.matchWins));
10700             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10701             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10702             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10703             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10704                 first.twoMachinesColor = "black\n";
10705                 second.twoMachinesColor = "white\n";
10706             } else {
10707                 first.twoMachinesColor = "white\n";
10708                 second.twoMachinesColor = "black\n";
10709             }
10710         }
10711     }
10712     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10713         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10714       ExitAnalyzeMode();
10715     gameMode = nextGameMode;
10716     ModeHighlight();
10717     endingGame = 0;  /* [HGM] crash */
10718     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10719         if(matchMode == TRUE) { // match through command line: exit with or without popup
10720             if(ranking) {
10721                 ToNrEvent(forwardMostMove);
10722                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10723                 else ExitEvent(0);
10724             } else DisplayFatalError(buf, 0, 0);
10725         } else { // match through menu; just stop, with or without popup
10726             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10727             ModeHighlight();
10728             if(ranking){
10729                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10730             } else DisplayNote(buf);
10731       }
10732       if(ranking) free(ranking);
10733     }
10734 }
10735
10736 /* Assumes program was just initialized (initString sent).
10737    Leaves program in force mode. */
10738 void
10739 FeedMovesToProgram (ChessProgramState *cps, int upto)
10740 {
10741     int i;
10742
10743     if (appData.debugMode)
10744       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10745               startedFromSetupPosition ? "position and " : "",
10746               backwardMostMove, upto, cps->which);
10747     if(currentlyInitializedVariant != gameInfo.variant) {
10748       char buf[MSG_SIZ];
10749         // [HGM] variantswitch: make engine aware of new variant
10750         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10751                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10752         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10753         SendToProgram(buf, cps);
10754         currentlyInitializedVariant = gameInfo.variant;
10755     }
10756     SendToProgram("force\n", cps);
10757     if (startedFromSetupPosition) {
10758         SendBoard(cps, backwardMostMove);
10759     if (appData.debugMode) {
10760         fprintf(debugFP, "feedMoves\n");
10761     }
10762     }
10763     for (i = backwardMostMove; i < upto; i++) {
10764         SendMoveToProgram(i, cps);
10765     }
10766 }
10767
10768
10769 int
10770 ResurrectChessProgram ()
10771 {
10772      /* The chess program may have exited.
10773         If so, restart it and feed it all the moves made so far. */
10774     static int doInit = 0;
10775
10776     if (appData.noChessProgram) return 1;
10777
10778     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10779         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10780         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10781         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10782     } else {
10783         if (first.pr != NoProc) return 1;
10784         StartChessProgram(&first);
10785     }
10786     InitChessProgram(&first, FALSE);
10787     FeedMovesToProgram(&first, currentMove);
10788
10789     if (!first.sendTime) {
10790         /* can't tell gnuchess what its clock should read,
10791            so we bow to its notion. */
10792         ResetClocks();
10793         timeRemaining[0][currentMove] = whiteTimeRemaining;
10794         timeRemaining[1][currentMove] = blackTimeRemaining;
10795     }
10796
10797     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10798                 appData.icsEngineAnalyze) && first.analysisSupport) {
10799       SendToProgram("analyze\n", &first);
10800       first.analyzing = TRUE;
10801     }
10802     return 1;
10803 }
10804
10805 /*
10806  * Button procedures
10807  */
10808 void
10809 Reset (int redraw, int init)
10810 {
10811     int i;
10812
10813     if (appData.debugMode) {
10814         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10815                 redraw, init, gameMode);
10816     }
10817     CleanupTail(); // [HGM] vari: delete any stored variations
10818     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10819     pausing = pauseExamInvalid = FALSE;
10820     startedFromSetupPosition = blackPlaysFirst = FALSE;
10821     firstMove = TRUE;
10822     whiteFlag = blackFlag = FALSE;
10823     userOfferedDraw = FALSE;
10824     hintRequested = bookRequested = FALSE;
10825     first.maybeThinking = FALSE;
10826     second.maybeThinking = FALSE;
10827     first.bookSuspend = FALSE; // [HGM] book
10828     second.bookSuspend = FALSE;
10829     thinkOutput[0] = NULLCHAR;
10830     lastHint[0] = NULLCHAR;
10831     ClearGameInfo(&gameInfo);
10832     gameInfo.variant = StringToVariant(appData.variant);
10833     ics_user_moved = ics_clock_paused = FALSE;
10834     ics_getting_history = H_FALSE;
10835     ics_gamenum = -1;
10836     white_holding[0] = black_holding[0] = NULLCHAR;
10837     ClearProgramStats();
10838     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10839
10840     ResetFrontEnd();
10841     ClearHighlights();
10842     flipView = appData.flipView;
10843     ClearPremoveHighlights();
10844     gotPremove = FALSE;
10845     alarmSounded = FALSE;
10846
10847     GameEnds(EndOfFile, NULL, GE_PLAYER);
10848     if(appData.serverMovesName != NULL) {
10849         /* [HGM] prepare to make moves file for broadcasting */
10850         clock_t t = clock();
10851         if(serverMoves != NULL) fclose(serverMoves);
10852         serverMoves = fopen(appData.serverMovesName, "r");
10853         if(serverMoves != NULL) {
10854             fclose(serverMoves);
10855             /* delay 15 sec before overwriting, so all clients can see end */
10856             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10857         }
10858         serverMoves = fopen(appData.serverMovesName, "w");
10859     }
10860
10861     ExitAnalyzeMode();
10862     gameMode = BeginningOfGame;
10863     ModeHighlight();
10864     if(appData.icsActive) gameInfo.variant = VariantNormal;
10865     currentMove = forwardMostMove = backwardMostMove = 0;
10866     MarkTargetSquares(1);
10867     InitPosition(redraw);
10868     for (i = 0; i < MAX_MOVES; i++) {
10869         if (commentList[i] != NULL) {
10870             free(commentList[i]);
10871             commentList[i] = NULL;
10872         }
10873     }
10874     ResetClocks();
10875     timeRemaining[0][0] = whiteTimeRemaining;
10876     timeRemaining[1][0] = blackTimeRemaining;
10877
10878     if (first.pr == NoProc) {
10879         StartChessProgram(&first);
10880     }
10881     if (init) {
10882             InitChessProgram(&first, startedFromSetupPosition);
10883     }
10884     DisplayTitle("");
10885     DisplayMessage("", "");
10886     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10887     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10888     ClearMap();        // [HGM] exclude: invalidate map
10889 }
10890
10891 void
10892 AutoPlayGameLoop ()
10893 {
10894     for (;;) {
10895         if (!AutoPlayOneMove())
10896           return;
10897         if (matchMode || appData.timeDelay == 0)
10898           continue;
10899         if (appData.timeDelay < 0)
10900           return;
10901         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10902         break;
10903     }
10904 }
10905
10906
10907 int
10908 AutoPlayOneMove ()
10909 {
10910     int fromX, fromY, toX, toY;
10911
10912     if (appData.debugMode) {
10913       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10914     }
10915
10916     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10917       return FALSE;
10918
10919     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10920       pvInfoList[currentMove].depth = programStats.depth;
10921       pvInfoList[currentMove].score = programStats.score;
10922       pvInfoList[currentMove].time  = 0;
10923       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10924     }
10925
10926     if (currentMove >= forwardMostMove) {
10927       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10928 //      gameMode = EndOfGame;
10929 //      ModeHighlight();
10930
10931       /* [AS] Clear current move marker at the end of a game */
10932       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10933
10934       return FALSE;
10935     }
10936
10937     toX = moveList[currentMove][2] - AAA;
10938     toY = moveList[currentMove][3] - ONE;
10939
10940     if (moveList[currentMove][1] == '@') {
10941         if (appData.highlightLastMove) {
10942             SetHighlights(-1, -1, toX, toY);
10943         }
10944     } else {
10945         fromX = moveList[currentMove][0] - AAA;
10946         fromY = moveList[currentMove][1] - ONE;
10947
10948         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10949
10950         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10951
10952         if (appData.highlightLastMove) {
10953             SetHighlights(fromX, fromY, toX, toY);
10954         }
10955     }
10956     DisplayMove(currentMove);
10957     SendMoveToProgram(currentMove++, &first);
10958     DisplayBothClocks();
10959     DrawPosition(FALSE, boards[currentMove]);
10960     // [HGM] PV info: always display, routine tests if empty
10961     DisplayComment(currentMove - 1, commentList[currentMove]);
10962     return TRUE;
10963 }
10964
10965
10966 int
10967 LoadGameOneMove (ChessMove readAhead)
10968 {
10969     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10970     char promoChar = NULLCHAR;
10971     ChessMove moveType;
10972     char move[MSG_SIZ];
10973     char *p, *q;
10974
10975     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10976         gameMode != AnalyzeMode && gameMode != Training) {
10977         gameFileFP = NULL;
10978         return FALSE;
10979     }
10980
10981     yyboardindex = forwardMostMove;
10982     if (readAhead != EndOfFile) {
10983       moveType = readAhead;
10984     } else {
10985       if (gameFileFP == NULL)
10986           return FALSE;
10987       moveType = (ChessMove) Myylex();
10988     }
10989
10990     done = FALSE;
10991     switch (moveType) {
10992       case Comment:
10993         if (appData.debugMode)
10994           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10995         p = yy_text;
10996
10997         /* append the comment but don't display it */
10998         AppendComment(currentMove, p, FALSE);
10999         return TRUE;
11000
11001       case WhiteCapturesEnPassant:
11002       case BlackCapturesEnPassant:
11003       case WhitePromotion:
11004       case BlackPromotion:
11005       case WhiteNonPromotion:
11006       case BlackNonPromotion:
11007       case NormalMove:
11008       case WhiteKingSideCastle:
11009       case WhiteQueenSideCastle:
11010       case BlackKingSideCastle:
11011       case BlackQueenSideCastle:
11012       case WhiteKingSideCastleWild:
11013       case WhiteQueenSideCastleWild:
11014       case BlackKingSideCastleWild:
11015       case BlackQueenSideCastleWild:
11016       /* PUSH Fabien */
11017       case WhiteHSideCastleFR:
11018       case WhiteASideCastleFR:
11019       case BlackHSideCastleFR:
11020       case BlackASideCastleFR:
11021       /* POP Fabien */
11022         if (appData.debugMode)
11023           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11024         fromX = currentMoveString[0] - AAA;
11025         fromY = currentMoveString[1] - ONE;
11026         toX = currentMoveString[2] - AAA;
11027         toY = currentMoveString[3] - ONE;
11028         promoChar = currentMoveString[4];
11029         break;
11030
11031       case WhiteDrop:
11032       case BlackDrop:
11033         if (appData.debugMode)
11034           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11035         fromX = moveType == WhiteDrop ?
11036           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11037         (int) CharToPiece(ToLower(currentMoveString[0]));
11038         fromY = DROP_RANK;
11039         toX = currentMoveString[2] - AAA;
11040         toY = currentMoveString[3] - ONE;
11041         break;
11042
11043       case WhiteWins:
11044       case BlackWins:
11045       case GameIsDrawn:
11046       case GameUnfinished:
11047         if (appData.debugMode)
11048           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11049         p = strchr(yy_text, '{');
11050         if (p == NULL) p = strchr(yy_text, '(');
11051         if (p == NULL) {
11052             p = yy_text;
11053             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11054         } else {
11055             q = strchr(p, *p == '{' ? '}' : ')');
11056             if (q != NULL) *q = NULLCHAR;
11057             p++;
11058         }
11059         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11060         GameEnds(moveType, p, GE_FILE);
11061         done = TRUE;
11062         if (cmailMsgLoaded) {
11063             ClearHighlights();
11064             flipView = WhiteOnMove(currentMove);
11065             if (moveType == GameUnfinished) flipView = !flipView;
11066             if (appData.debugMode)
11067               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11068         }
11069         break;
11070
11071       case EndOfFile:
11072         if (appData.debugMode)
11073           fprintf(debugFP, "Parser hit end of file\n");
11074         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11075           case MT_NONE:
11076           case MT_CHECK:
11077             break;
11078           case MT_CHECKMATE:
11079           case MT_STAINMATE:
11080             if (WhiteOnMove(currentMove)) {
11081                 GameEnds(BlackWins, "Black mates", GE_FILE);
11082             } else {
11083                 GameEnds(WhiteWins, "White mates", GE_FILE);
11084             }
11085             break;
11086           case MT_STALEMATE:
11087             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11088             break;
11089         }
11090         done = TRUE;
11091         break;
11092
11093       case MoveNumberOne:
11094         if (lastLoadGameStart == GNUChessGame) {
11095             /* GNUChessGames have numbers, but they aren't move numbers */
11096             if (appData.debugMode)
11097               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11098                       yy_text, (int) moveType);
11099             return LoadGameOneMove(EndOfFile); /* tail recursion */
11100         }
11101         /* else fall thru */
11102
11103       case XBoardGame:
11104       case GNUChessGame:
11105       case PGNTag:
11106         /* Reached start of next game in file */
11107         if (appData.debugMode)
11108           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11109         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11110           case MT_NONE:
11111           case MT_CHECK:
11112             break;
11113           case MT_CHECKMATE:
11114           case MT_STAINMATE:
11115             if (WhiteOnMove(currentMove)) {
11116                 GameEnds(BlackWins, "Black mates", GE_FILE);
11117             } else {
11118                 GameEnds(WhiteWins, "White mates", GE_FILE);
11119             }
11120             break;
11121           case MT_STALEMATE:
11122             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11123             break;
11124         }
11125         done = TRUE;
11126         break;
11127
11128       case PositionDiagram:     /* should not happen; ignore */
11129       case ElapsedTime:         /* ignore */
11130       case NAG:                 /* ignore */
11131         if (appData.debugMode)
11132           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11133                   yy_text, (int) moveType);
11134         return LoadGameOneMove(EndOfFile); /* tail recursion */
11135
11136       case IllegalMove:
11137         if (appData.testLegality) {
11138             if (appData.debugMode)
11139               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11140             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11141                     (forwardMostMove / 2) + 1,
11142                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11143             DisplayError(move, 0);
11144             done = TRUE;
11145         } else {
11146             if (appData.debugMode)
11147               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11148                       yy_text, currentMoveString);
11149             fromX = currentMoveString[0] - AAA;
11150             fromY = currentMoveString[1] - ONE;
11151             toX = currentMoveString[2] - AAA;
11152             toY = currentMoveString[3] - ONE;
11153             promoChar = currentMoveString[4];
11154         }
11155         break;
11156
11157       case AmbiguousMove:
11158         if (appData.debugMode)
11159           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11160         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11161                 (forwardMostMove / 2) + 1,
11162                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11163         DisplayError(move, 0);
11164         done = TRUE;
11165         break;
11166
11167       default:
11168       case ImpossibleMove:
11169         if (appData.debugMode)
11170           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11171         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11172                 (forwardMostMove / 2) + 1,
11173                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11174         DisplayError(move, 0);
11175         done = TRUE;
11176         break;
11177     }
11178
11179     if (done) {
11180         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11181             DrawPosition(FALSE, boards[currentMove]);
11182             DisplayBothClocks();
11183             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11184               DisplayComment(currentMove - 1, commentList[currentMove]);
11185         }
11186         (void) StopLoadGameTimer();
11187         gameFileFP = NULL;
11188         cmailOldMove = forwardMostMove;
11189         return FALSE;
11190     } else {
11191         /* currentMoveString is set as a side-effect of yylex */
11192
11193         thinkOutput[0] = NULLCHAR;
11194         MakeMove(fromX, fromY, toX, toY, promoChar);
11195         currentMove = forwardMostMove;
11196         return TRUE;
11197     }
11198 }
11199
11200 /* Load the nth game from the given file */
11201 int
11202 LoadGameFromFile (char *filename, int n, char *title, int useList)
11203 {
11204     FILE *f;
11205     char buf[MSG_SIZ];
11206
11207     if (strcmp(filename, "-") == 0) {
11208         f = stdin;
11209         title = "stdin";
11210     } else {
11211         f = fopen(filename, "rb");
11212         if (f == NULL) {
11213           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11214             DisplayError(buf, errno);
11215             return FALSE;
11216         }
11217     }
11218     if (fseek(f, 0, 0) == -1) {
11219         /* f is not seekable; probably a pipe */
11220         useList = FALSE;
11221     }
11222     if (useList && n == 0) {
11223         int error = GameListBuild(f);
11224         if (error) {
11225             DisplayError(_("Cannot build game list"), error);
11226         } else if (!ListEmpty(&gameList) &&
11227                    ((ListGame *) gameList.tailPred)->number > 1) {
11228             GameListPopUp(f, title);
11229             return TRUE;
11230         }
11231         GameListDestroy();
11232         n = 1;
11233     }
11234     if (n == 0) n = 1;
11235     return LoadGame(f, n, title, FALSE);
11236 }
11237
11238
11239 void
11240 MakeRegisteredMove ()
11241 {
11242     int fromX, fromY, toX, toY;
11243     char promoChar;
11244     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11245         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11246           case CMAIL_MOVE:
11247           case CMAIL_DRAW:
11248             if (appData.debugMode)
11249               fprintf(debugFP, "Restoring %s for game %d\n",
11250                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11251
11252             thinkOutput[0] = NULLCHAR;
11253             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11254             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11255             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11256             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11257             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11258             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11259             MakeMove(fromX, fromY, toX, toY, promoChar);
11260             ShowMove(fromX, fromY, toX, toY);
11261
11262             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11263               case MT_NONE:
11264               case MT_CHECK:
11265                 break;
11266
11267               case MT_CHECKMATE:
11268               case MT_STAINMATE:
11269                 if (WhiteOnMove(currentMove)) {
11270                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11271                 } else {
11272                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11273                 }
11274                 break;
11275
11276               case MT_STALEMATE:
11277                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11278                 break;
11279             }
11280
11281             break;
11282
11283           case CMAIL_RESIGN:
11284             if (WhiteOnMove(currentMove)) {
11285                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11286             } else {
11287                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11288             }
11289             break;
11290
11291           case CMAIL_ACCEPT:
11292             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11293             break;
11294
11295           default:
11296             break;
11297         }
11298     }
11299
11300     return;
11301 }
11302
11303 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11304 int
11305 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11306 {
11307     int retVal;
11308
11309     if (gameNumber > nCmailGames) {
11310         DisplayError(_("No more games in this message"), 0);
11311         return FALSE;
11312     }
11313     if (f == lastLoadGameFP) {
11314         int offset = gameNumber - lastLoadGameNumber;
11315         if (offset == 0) {
11316             cmailMsg[0] = NULLCHAR;
11317             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11318                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11319                 nCmailMovesRegistered--;
11320             }
11321             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11322             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11323                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11324             }
11325         } else {
11326             if (! RegisterMove()) return FALSE;
11327         }
11328     }
11329
11330     retVal = LoadGame(f, gameNumber, title, useList);
11331
11332     /* Make move registered during previous look at this game, if any */
11333     MakeRegisteredMove();
11334
11335     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11336         commentList[currentMove]
11337           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11338         DisplayComment(currentMove - 1, commentList[currentMove]);
11339     }
11340
11341     return retVal;
11342 }
11343
11344 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11345 int
11346 ReloadGame (int offset)
11347 {
11348     int gameNumber = lastLoadGameNumber + offset;
11349     if (lastLoadGameFP == NULL) {
11350         DisplayError(_("No game has been loaded yet"), 0);
11351         return FALSE;
11352     }
11353     if (gameNumber <= 0) {
11354         DisplayError(_("Can't back up any further"), 0);
11355         return FALSE;
11356     }
11357     if (cmailMsgLoaded) {
11358         return CmailLoadGame(lastLoadGameFP, gameNumber,
11359                              lastLoadGameTitle, lastLoadGameUseList);
11360     } else {
11361         return LoadGame(lastLoadGameFP, gameNumber,
11362                         lastLoadGameTitle, lastLoadGameUseList);
11363     }
11364 }
11365
11366 int keys[EmptySquare+1];
11367
11368 int
11369 PositionMatches (Board b1, Board b2)
11370 {
11371     int r, f, sum=0;
11372     switch(appData.searchMode) {
11373         case 1: return CompareWithRights(b1, b2);
11374         case 2:
11375             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11376                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11377             }
11378             return TRUE;
11379         case 3:
11380             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11381               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11382                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11383             }
11384             return sum==0;
11385         case 4:
11386             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11387                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11388             }
11389             return sum==0;
11390     }
11391     return TRUE;
11392 }
11393
11394 #define Q_PROMO  4
11395 #define Q_EP     3
11396 #define Q_BCASTL 2
11397 #define Q_WCASTL 1
11398
11399 int pieceList[256], quickBoard[256];
11400 ChessSquare pieceType[256] = { EmptySquare };
11401 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11402 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11403 int soughtTotal, turn;
11404 Boolean epOK, flipSearch;
11405
11406 typedef struct {
11407     unsigned char piece, to;
11408 } Move;
11409
11410 #define DSIZE (250000)
11411
11412 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11413 Move *moveDatabase = initialSpace;
11414 unsigned int movePtr, dataSize = DSIZE;
11415
11416 int
11417 MakePieceList (Board board, int *counts)
11418 {
11419     int r, f, n=Q_PROMO, total=0;
11420     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11421     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11422         int sq = f + (r<<4);
11423         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11424             quickBoard[sq] = ++n;
11425             pieceList[n] = sq;
11426             pieceType[n] = board[r][f];
11427             counts[board[r][f]]++;
11428             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11429             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11430             total++;
11431         }
11432     }
11433     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11434     return total;
11435 }
11436
11437 void
11438 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11439 {
11440     int sq = fromX + (fromY<<4);
11441     int piece = quickBoard[sq];
11442     quickBoard[sq] = 0;
11443     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11444     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11445         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11446         moveDatabase[movePtr++].piece = Q_WCASTL;
11447         quickBoard[sq] = piece;
11448         piece = quickBoard[from]; quickBoard[from] = 0;
11449         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11450     } else
11451     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11452         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11453         moveDatabase[movePtr++].piece = Q_BCASTL;
11454         quickBoard[sq] = piece;
11455         piece = quickBoard[from]; quickBoard[from] = 0;
11456         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11457     } else
11458     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11459         quickBoard[(fromY<<4)+toX] = 0;
11460         moveDatabase[movePtr].piece = Q_EP;
11461         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11462         moveDatabase[movePtr].to = sq;
11463     } else
11464     if(promoPiece != pieceType[piece]) {
11465         moveDatabase[movePtr++].piece = Q_PROMO;
11466         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11467     }
11468     moveDatabase[movePtr].piece = piece;
11469     quickBoard[sq] = piece;
11470     movePtr++;
11471 }
11472
11473 int
11474 PackGame (Board board)
11475 {
11476     Move *newSpace = NULL;
11477     moveDatabase[movePtr].piece = 0; // terminate previous game
11478     if(movePtr > dataSize) {
11479         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11480         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11481         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11482         if(newSpace) {
11483             int i;
11484             Move *p = moveDatabase, *q = newSpace;
11485             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11486             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11487             moveDatabase = newSpace;
11488         } else { // calloc failed, we must be out of memory. Too bad...
11489             dataSize = 0; // prevent calloc events for all subsequent games
11490             return 0;     // and signal this one isn't cached
11491         }
11492     }
11493     movePtr++;
11494     MakePieceList(board, counts);
11495     return movePtr;
11496 }
11497
11498 int
11499 QuickCompare (Board board, int *minCounts, int *maxCounts)
11500 {   // compare according to search mode
11501     int r, f;
11502     switch(appData.searchMode)
11503     {
11504       case 1: // exact position match
11505         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11506         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11507             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11508         }
11509         break;
11510       case 2: // can have extra material on empty squares
11511         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11512             if(board[r][f] == EmptySquare) continue;
11513             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11514         }
11515         break;
11516       case 3: // material with exact Pawn structure
11517         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11518             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11519             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11520         } // fall through to material comparison
11521       case 4: // exact material
11522         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11523         break;
11524       case 6: // material range with given imbalance
11525         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11526         // fall through to range comparison
11527       case 5: // material range
11528         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11529     }
11530     return TRUE;
11531 }
11532
11533 int
11534 QuickScan (Board board, Move *move)
11535 {   // reconstruct game,and compare all positions in it
11536     int cnt=0, stretch=0, total = MakePieceList(board, counts), delayedKing = -1;
11537     do {
11538         int piece = move->piece;
11539         int to = move->to, from = pieceList[piece];
11540         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11541           if(!piece) return -1;
11542           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11543             piece = (++move)->piece;
11544             from = pieceList[piece];
11545             counts[pieceType[piece]]--;
11546             pieceType[piece] = (ChessSquare) move->to;
11547             counts[move->to]++;
11548           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11549             counts[pieceType[quickBoard[to]]]--;
11550             quickBoard[to] = 0; total--;
11551             move++;
11552             continue;
11553           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11554             int rook;
11555             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11556             from  = pieceList[piece]; // so this must be King
11557             quickBoard[from] = 0;
11558             pieceList[piece] = to;
11559             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11560             quickBoard[from] = 0; // rook
11561             quickBoard[to] = piece;
11562             to = move->to; piece = move->piece;
11563             goto aftercastle;
11564           }
11565         }
11566         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11567         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11568         quickBoard[from] = 0;
11569       aftercastle:
11570         quickBoard[to] = piece;
11571         pieceList[piece] = to;
11572         cnt++; turn ^= 3;
11573         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11574            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11575            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11576                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11577           ) {
11578             static int lastCounts[EmptySquare+1];
11579             int i;
11580             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11581             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11582         } else stretch = 0;
11583         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11584         move++; delayedKing = -1;
11585     } while(1);
11586 }
11587
11588 void
11589 InitSearch ()
11590 {
11591     int r, f;
11592     flipSearch = FALSE;
11593     CopyBoard(soughtBoard, boards[currentMove]);
11594     soughtTotal = MakePieceList(soughtBoard, maxSought);
11595     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11596     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11597     CopyBoard(reverseBoard, boards[currentMove]);
11598     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11599         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11600         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11601         reverseBoard[r][f] = piece;
11602     }
11603     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11604     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11605     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11606                  || (boards[currentMove][CASTLING][2] == NoRights || 
11607                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11608                  && (boards[currentMove][CASTLING][5] == NoRights || 
11609                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11610       ) {
11611         flipSearch = TRUE;
11612         CopyBoard(flipBoard, soughtBoard);
11613         CopyBoard(rotateBoard, reverseBoard);
11614         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11615             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11616             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11617         }
11618     }
11619     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11620     if(appData.searchMode >= 5) {
11621         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11622         MakePieceList(soughtBoard, minSought);
11623         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11624     }
11625     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11626         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11627 }
11628
11629 GameInfo dummyInfo;
11630
11631 int
11632 GameContainsPosition (FILE *f, ListGame *lg)
11633 {
11634     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11635     int fromX, fromY, toX, toY;
11636     char promoChar;
11637     static int initDone=FALSE;
11638
11639     // weed out games based on numerical tag comparison
11640     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11641     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11642     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11643     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11644     if(!initDone) {
11645         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11646         initDone = TRUE;
11647     }
11648     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11649     else CopyBoard(boards[scratch], initialPosition); // default start position
11650     if(lg->moves) {
11651         turn = btm + 1;
11652         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11653         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11654     }
11655     if(btm) plyNr++;
11656     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11657     fseek(f, lg->offset, 0);
11658     yynewfile(f);
11659     while(1) {
11660         yyboardindex = scratch;
11661         quickFlag = plyNr+1;
11662         next = Myylex();
11663         quickFlag = 0;
11664         switch(next) {
11665             case PGNTag:
11666                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11667             default:
11668                 continue;
11669
11670             case XBoardGame:
11671             case GNUChessGame:
11672                 if(plyNr) return -1; // after we have seen moves, this is for new game
11673               continue;
11674
11675             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11676             case ImpossibleMove:
11677             case WhiteWins: // game ends here with these four
11678             case BlackWins:
11679             case GameIsDrawn:
11680             case GameUnfinished:
11681                 return -1;
11682
11683             case IllegalMove:
11684                 if(appData.testLegality) return -1;
11685             case WhiteCapturesEnPassant:
11686             case BlackCapturesEnPassant:
11687             case WhitePromotion:
11688             case BlackPromotion:
11689             case WhiteNonPromotion:
11690             case BlackNonPromotion:
11691             case NormalMove:
11692             case WhiteKingSideCastle:
11693             case WhiteQueenSideCastle:
11694             case BlackKingSideCastle:
11695             case BlackQueenSideCastle:
11696             case WhiteKingSideCastleWild:
11697             case WhiteQueenSideCastleWild:
11698             case BlackKingSideCastleWild:
11699             case BlackQueenSideCastleWild:
11700             case WhiteHSideCastleFR:
11701             case WhiteASideCastleFR:
11702             case BlackHSideCastleFR:
11703             case BlackASideCastleFR:
11704                 fromX = currentMoveString[0] - AAA;
11705                 fromY = currentMoveString[1] - ONE;
11706                 toX = currentMoveString[2] - AAA;
11707                 toY = currentMoveString[3] - ONE;
11708                 promoChar = currentMoveString[4];
11709                 break;
11710             case WhiteDrop:
11711             case BlackDrop:
11712                 fromX = next == WhiteDrop ?
11713                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11714                   (int) CharToPiece(ToLower(currentMoveString[0]));
11715                 fromY = DROP_RANK;
11716                 toX = currentMoveString[2] - AAA;
11717                 toY = currentMoveString[3] - ONE;
11718                 promoChar = 0;
11719                 break;
11720         }
11721         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11722         plyNr++;
11723         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11724         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11725         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11726         if(appData.findMirror) {
11727             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11728             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11729         }
11730     }
11731 }
11732
11733 /* Load the nth game from open file f */
11734 int
11735 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11736 {
11737     ChessMove cm;
11738     char buf[MSG_SIZ];
11739     int gn = gameNumber;
11740     ListGame *lg = NULL;
11741     int numPGNTags = 0;
11742     int err, pos = -1;
11743     GameMode oldGameMode;
11744     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11745
11746     if (appData.debugMode)
11747         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11748
11749     if (gameMode == Training )
11750         SetTrainingModeOff();
11751
11752     oldGameMode = gameMode;
11753     if (gameMode != BeginningOfGame) {
11754       Reset(FALSE, TRUE);
11755     }
11756
11757     gameFileFP = f;
11758     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11759         fclose(lastLoadGameFP);
11760     }
11761
11762     if (useList) {
11763         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11764
11765         if (lg) {
11766             fseek(f, lg->offset, 0);
11767             GameListHighlight(gameNumber);
11768             pos = lg->position;
11769             gn = 1;
11770         }
11771         else {
11772             DisplayError(_("Game number out of range"), 0);
11773             return FALSE;
11774         }
11775     } else {
11776         GameListDestroy();
11777         if (fseek(f, 0, 0) == -1) {
11778             if (f == lastLoadGameFP ?
11779                 gameNumber == lastLoadGameNumber + 1 :
11780                 gameNumber == 1) {
11781                 gn = 1;
11782             } else {
11783                 DisplayError(_("Can't seek on game file"), 0);
11784                 return FALSE;
11785             }
11786         }
11787     }
11788     lastLoadGameFP = f;
11789     lastLoadGameNumber = gameNumber;
11790     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11791     lastLoadGameUseList = useList;
11792
11793     yynewfile(f);
11794
11795     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11796       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11797                 lg->gameInfo.black);
11798             DisplayTitle(buf);
11799     } else if (*title != NULLCHAR) {
11800         if (gameNumber > 1) {
11801           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11802             DisplayTitle(buf);
11803         } else {
11804             DisplayTitle(title);
11805         }
11806     }
11807
11808     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11809         gameMode = PlayFromGameFile;
11810         ModeHighlight();
11811     }
11812
11813     currentMove = forwardMostMove = backwardMostMove = 0;
11814     CopyBoard(boards[0], initialPosition);
11815     StopClocks();
11816
11817     /*
11818      * Skip the first gn-1 games in the file.
11819      * Also skip over anything that precedes an identifiable
11820      * start of game marker, to avoid being confused by
11821      * garbage at the start of the file.  Currently
11822      * recognized start of game markers are the move number "1",
11823      * the pattern "gnuchess .* game", the pattern
11824      * "^[#;%] [^ ]* game file", and a PGN tag block.
11825      * A game that starts with one of the latter two patterns
11826      * will also have a move number 1, possibly
11827      * following a position diagram.
11828      * 5-4-02: Let's try being more lenient and allowing a game to
11829      * start with an unnumbered move.  Does that break anything?
11830      */
11831     cm = lastLoadGameStart = EndOfFile;
11832     while (gn > 0) {
11833         yyboardindex = forwardMostMove;
11834         cm = (ChessMove) Myylex();
11835         switch (cm) {
11836           case EndOfFile:
11837             if (cmailMsgLoaded) {
11838                 nCmailGames = CMAIL_MAX_GAMES - gn;
11839             } else {
11840                 Reset(TRUE, TRUE);
11841                 DisplayError(_("Game not found in file"), 0);
11842             }
11843             return FALSE;
11844
11845           case GNUChessGame:
11846           case XBoardGame:
11847             gn--;
11848             lastLoadGameStart = cm;
11849             break;
11850
11851           case MoveNumberOne:
11852             switch (lastLoadGameStart) {
11853               case GNUChessGame:
11854               case XBoardGame:
11855               case PGNTag:
11856                 break;
11857               case MoveNumberOne:
11858               case EndOfFile:
11859                 gn--;           /* count this game */
11860                 lastLoadGameStart = cm;
11861                 break;
11862               default:
11863                 /* impossible */
11864                 break;
11865             }
11866             break;
11867
11868           case PGNTag:
11869             switch (lastLoadGameStart) {
11870               case GNUChessGame:
11871               case PGNTag:
11872               case MoveNumberOne:
11873               case EndOfFile:
11874                 gn--;           /* count this game */
11875                 lastLoadGameStart = cm;
11876                 break;
11877               case XBoardGame:
11878                 lastLoadGameStart = cm; /* game counted already */
11879                 break;
11880               default:
11881                 /* impossible */
11882                 break;
11883             }
11884             if (gn > 0) {
11885                 do {
11886                     yyboardindex = forwardMostMove;
11887                     cm = (ChessMove) Myylex();
11888                 } while (cm == PGNTag || cm == Comment);
11889             }
11890             break;
11891
11892           case WhiteWins:
11893           case BlackWins:
11894           case GameIsDrawn:
11895             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11896                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11897                     != CMAIL_OLD_RESULT) {
11898                     nCmailResults ++ ;
11899                     cmailResult[  CMAIL_MAX_GAMES
11900                                 - gn - 1] = CMAIL_OLD_RESULT;
11901                 }
11902             }
11903             break;
11904
11905           case NormalMove:
11906             /* Only a NormalMove can be at the start of a game
11907              * without a position diagram. */
11908             if (lastLoadGameStart == EndOfFile ) {
11909               gn--;
11910               lastLoadGameStart = MoveNumberOne;
11911             }
11912             break;
11913
11914           default:
11915             break;
11916         }
11917     }
11918
11919     if (appData.debugMode)
11920       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11921
11922     if (cm == XBoardGame) {
11923         /* Skip any header junk before position diagram and/or move 1 */
11924         for (;;) {
11925             yyboardindex = forwardMostMove;
11926             cm = (ChessMove) Myylex();
11927
11928             if (cm == EndOfFile ||
11929                 cm == GNUChessGame || cm == XBoardGame) {
11930                 /* Empty game; pretend end-of-file and handle later */
11931                 cm = EndOfFile;
11932                 break;
11933             }
11934
11935             if (cm == MoveNumberOne || cm == PositionDiagram ||
11936                 cm == PGNTag || cm == Comment)
11937               break;
11938         }
11939     } else if (cm == GNUChessGame) {
11940         if (gameInfo.event != NULL) {
11941             free(gameInfo.event);
11942         }
11943         gameInfo.event = StrSave(yy_text);
11944     }
11945
11946     startedFromSetupPosition = FALSE;
11947     while (cm == PGNTag) {
11948         if (appData.debugMode)
11949           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11950         err = ParsePGNTag(yy_text, &gameInfo);
11951         if (!err) numPGNTags++;
11952
11953         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11954         if(gameInfo.variant != oldVariant) {
11955             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11956             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11957             InitPosition(TRUE);
11958             oldVariant = gameInfo.variant;
11959             if (appData.debugMode)
11960               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11961         }
11962
11963
11964         if (gameInfo.fen != NULL) {
11965           Board initial_position;
11966           startedFromSetupPosition = TRUE;
11967           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11968             Reset(TRUE, TRUE);
11969             DisplayError(_("Bad FEN position in file"), 0);
11970             return FALSE;
11971           }
11972           CopyBoard(boards[0], initial_position);
11973           if (blackPlaysFirst) {
11974             currentMove = forwardMostMove = backwardMostMove = 1;
11975             CopyBoard(boards[1], initial_position);
11976             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11977             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11978             timeRemaining[0][1] = whiteTimeRemaining;
11979             timeRemaining[1][1] = blackTimeRemaining;
11980             if (commentList[0] != NULL) {
11981               commentList[1] = commentList[0];
11982               commentList[0] = NULL;
11983             }
11984           } else {
11985             currentMove = forwardMostMove = backwardMostMove = 0;
11986           }
11987           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11988           {   int i;
11989               initialRulePlies = FENrulePlies;
11990               for( i=0; i< nrCastlingRights; i++ )
11991                   initialRights[i] = initial_position[CASTLING][i];
11992           }
11993           yyboardindex = forwardMostMove;
11994           free(gameInfo.fen);
11995           gameInfo.fen = NULL;
11996         }
11997
11998         yyboardindex = forwardMostMove;
11999         cm = (ChessMove) Myylex();
12000
12001         /* Handle comments interspersed among the tags */
12002         while (cm == Comment) {
12003             char *p;
12004             if (appData.debugMode)
12005               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12006             p = yy_text;
12007             AppendComment(currentMove, p, FALSE);
12008             yyboardindex = forwardMostMove;
12009             cm = (ChessMove) Myylex();
12010         }
12011     }
12012
12013     /* don't rely on existence of Event tag since if game was
12014      * pasted from clipboard the Event tag may not exist
12015      */
12016     if (numPGNTags > 0){
12017         char *tags;
12018         if (gameInfo.variant == VariantNormal) {
12019           VariantClass v = StringToVariant(gameInfo.event);
12020           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12021           if(v < VariantShogi) gameInfo.variant = v;
12022         }
12023         if (!matchMode) {
12024           if( appData.autoDisplayTags ) {
12025             tags = PGNTags(&gameInfo);
12026             TagsPopUp(tags, CmailMsg());
12027             free(tags);
12028           }
12029         }
12030     } else {
12031         /* Make something up, but don't display it now */
12032         SetGameInfo();
12033         TagsPopDown();
12034     }
12035
12036     if (cm == PositionDiagram) {
12037         int i, j;
12038         char *p;
12039         Board initial_position;
12040
12041         if (appData.debugMode)
12042           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12043
12044         if (!startedFromSetupPosition) {
12045             p = yy_text;
12046             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12047               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12048                 switch (*p) {
12049                   case '{':
12050                   case '[':
12051                   case '-':
12052                   case ' ':
12053                   case '\t':
12054                   case '\n':
12055                   case '\r':
12056                     break;
12057                   default:
12058                     initial_position[i][j++] = CharToPiece(*p);
12059                     break;
12060                 }
12061             while (*p == ' ' || *p == '\t' ||
12062                    *p == '\n' || *p == '\r') p++;
12063
12064             if (strncmp(p, "black", strlen("black"))==0)
12065               blackPlaysFirst = TRUE;
12066             else
12067               blackPlaysFirst = FALSE;
12068             startedFromSetupPosition = TRUE;
12069
12070             CopyBoard(boards[0], initial_position);
12071             if (blackPlaysFirst) {
12072                 currentMove = forwardMostMove = backwardMostMove = 1;
12073                 CopyBoard(boards[1], initial_position);
12074                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12075                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12076                 timeRemaining[0][1] = whiteTimeRemaining;
12077                 timeRemaining[1][1] = blackTimeRemaining;
12078                 if (commentList[0] != NULL) {
12079                     commentList[1] = commentList[0];
12080                     commentList[0] = NULL;
12081                 }
12082             } else {
12083                 currentMove = forwardMostMove = backwardMostMove = 0;
12084             }
12085         }
12086         yyboardindex = forwardMostMove;
12087         cm = (ChessMove) Myylex();
12088     }
12089
12090     if (first.pr == NoProc) {
12091         StartChessProgram(&first);
12092     }
12093     InitChessProgram(&first, FALSE);
12094     SendToProgram("force\n", &first);
12095     if (startedFromSetupPosition) {
12096         SendBoard(&first, forwardMostMove);
12097     if (appData.debugMode) {
12098         fprintf(debugFP, "Load Game\n");
12099     }
12100         DisplayBothClocks();
12101     }
12102
12103     /* [HGM] server: flag to write setup moves in broadcast file as one */
12104     loadFlag = appData.suppressLoadMoves;
12105
12106     while (cm == Comment) {
12107         char *p;
12108         if (appData.debugMode)
12109           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12110         p = yy_text;
12111         AppendComment(currentMove, p, FALSE);
12112         yyboardindex = forwardMostMove;
12113         cm = (ChessMove) Myylex();
12114     }
12115
12116     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12117         cm == WhiteWins || cm == BlackWins ||
12118         cm == GameIsDrawn || cm == GameUnfinished) {
12119         DisplayMessage("", _("No moves in game"));
12120         if (cmailMsgLoaded) {
12121             if (appData.debugMode)
12122               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12123             ClearHighlights();
12124             flipView = FALSE;
12125         }
12126         DrawPosition(FALSE, boards[currentMove]);
12127         DisplayBothClocks();
12128         gameMode = EditGame;
12129         ModeHighlight();
12130         gameFileFP = NULL;
12131         cmailOldMove = 0;
12132         return TRUE;
12133     }
12134
12135     // [HGM] PV info: routine tests if comment empty
12136     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12137         DisplayComment(currentMove - 1, commentList[currentMove]);
12138     }
12139     if (!matchMode && appData.timeDelay != 0)
12140       DrawPosition(FALSE, boards[currentMove]);
12141
12142     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12143       programStats.ok_to_send = 1;
12144     }
12145
12146     /* if the first token after the PGN tags is a move
12147      * and not move number 1, retrieve it from the parser
12148      */
12149     if (cm != MoveNumberOne)
12150         LoadGameOneMove(cm);
12151
12152     /* load the remaining moves from the file */
12153     while (LoadGameOneMove(EndOfFile)) {
12154       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12155       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12156     }
12157
12158     /* rewind to the start of the game */
12159     currentMove = backwardMostMove;
12160
12161     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12162
12163     if (oldGameMode == AnalyzeFile ||
12164         oldGameMode == AnalyzeMode) {
12165       AnalyzeFileEvent();
12166     }
12167
12168     if (!matchMode && pos > 0) {
12169         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12170     } else
12171     if (matchMode || appData.timeDelay == 0) {
12172       ToEndEvent();
12173     } else if (appData.timeDelay > 0) {
12174       AutoPlayGameLoop();
12175     }
12176
12177     if (appData.debugMode)
12178         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12179
12180     loadFlag = 0; /* [HGM] true game starts */
12181     return TRUE;
12182 }
12183
12184 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12185 int
12186 ReloadPosition (int offset)
12187 {
12188     int positionNumber = lastLoadPositionNumber + offset;
12189     if (lastLoadPositionFP == NULL) {
12190         DisplayError(_("No position has been loaded yet"), 0);
12191         return FALSE;
12192     }
12193     if (positionNumber <= 0) {
12194         DisplayError(_("Can't back up any further"), 0);
12195         return FALSE;
12196     }
12197     return LoadPosition(lastLoadPositionFP, positionNumber,
12198                         lastLoadPositionTitle);
12199 }
12200
12201 /* Load the nth position from the given file */
12202 int
12203 LoadPositionFromFile (char *filename, int n, char *title)
12204 {
12205     FILE *f;
12206     char buf[MSG_SIZ];
12207
12208     if (strcmp(filename, "-") == 0) {
12209         return LoadPosition(stdin, n, "stdin");
12210     } else {
12211         f = fopen(filename, "rb");
12212         if (f == NULL) {
12213             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12214             DisplayError(buf, errno);
12215             return FALSE;
12216         } else {
12217             return LoadPosition(f, n, title);
12218         }
12219     }
12220 }
12221
12222 /* Load the nth position from the given open file, and close it */
12223 int
12224 LoadPosition (FILE *f, int positionNumber, char *title)
12225 {
12226     char *p, line[MSG_SIZ];
12227     Board initial_position;
12228     int i, j, fenMode, pn;
12229
12230     if (gameMode == Training )
12231         SetTrainingModeOff();
12232
12233     if (gameMode != BeginningOfGame) {
12234         Reset(FALSE, TRUE);
12235     }
12236     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12237         fclose(lastLoadPositionFP);
12238     }
12239     if (positionNumber == 0) positionNumber = 1;
12240     lastLoadPositionFP = f;
12241     lastLoadPositionNumber = positionNumber;
12242     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12243     if (first.pr == NoProc && !appData.noChessProgram) {
12244       StartChessProgram(&first);
12245       InitChessProgram(&first, FALSE);
12246     }
12247     pn = positionNumber;
12248     if (positionNumber < 0) {
12249         /* Negative position number means to seek to that byte offset */
12250         if (fseek(f, -positionNumber, 0) == -1) {
12251             DisplayError(_("Can't seek on position file"), 0);
12252             return FALSE;
12253         };
12254         pn = 1;
12255     } else {
12256         if (fseek(f, 0, 0) == -1) {
12257             if (f == lastLoadPositionFP ?
12258                 positionNumber == lastLoadPositionNumber + 1 :
12259                 positionNumber == 1) {
12260                 pn = 1;
12261             } else {
12262                 DisplayError(_("Can't seek on position file"), 0);
12263                 return FALSE;
12264             }
12265         }
12266     }
12267     /* See if this file is FEN or old-style xboard */
12268     if (fgets(line, MSG_SIZ, f) == NULL) {
12269         DisplayError(_("Position not found in file"), 0);
12270         return FALSE;
12271     }
12272     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12273     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12274
12275     if (pn >= 2) {
12276         if (fenMode || line[0] == '#') pn--;
12277         while (pn > 0) {
12278             /* skip positions before number pn */
12279             if (fgets(line, MSG_SIZ, f) == NULL) {
12280                 Reset(TRUE, TRUE);
12281                 DisplayError(_("Position not found in file"), 0);
12282                 return FALSE;
12283             }
12284             if (fenMode || line[0] == '#') pn--;
12285         }
12286     }
12287
12288     if (fenMode) {
12289         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12290             DisplayError(_("Bad FEN position in file"), 0);
12291             return FALSE;
12292         }
12293     } else {
12294         (void) fgets(line, MSG_SIZ, f);
12295         (void) fgets(line, MSG_SIZ, f);
12296
12297         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12298             (void) fgets(line, MSG_SIZ, f);
12299             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12300                 if (*p == ' ')
12301                   continue;
12302                 initial_position[i][j++] = CharToPiece(*p);
12303             }
12304         }
12305
12306         blackPlaysFirst = FALSE;
12307         if (!feof(f)) {
12308             (void) fgets(line, MSG_SIZ, f);
12309             if (strncmp(line, "black", strlen("black"))==0)
12310               blackPlaysFirst = TRUE;
12311         }
12312     }
12313     startedFromSetupPosition = TRUE;
12314
12315     CopyBoard(boards[0], initial_position);
12316     if (blackPlaysFirst) {
12317         currentMove = forwardMostMove = backwardMostMove = 1;
12318         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12319         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12320         CopyBoard(boards[1], initial_position);
12321         DisplayMessage("", _("Black to play"));
12322     } else {
12323         currentMove = forwardMostMove = backwardMostMove = 0;
12324         DisplayMessage("", _("White to play"));
12325     }
12326     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12327     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12328         SendToProgram("force\n", &first);
12329         SendBoard(&first, forwardMostMove);
12330     }
12331     if (appData.debugMode) {
12332 int i, j;
12333   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12334   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12335         fprintf(debugFP, "Load Position\n");
12336     }
12337
12338     if (positionNumber > 1) {
12339       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12340         DisplayTitle(line);
12341     } else {
12342         DisplayTitle(title);
12343     }
12344     gameMode = EditGame;
12345     ModeHighlight();
12346     ResetClocks();
12347     timeRemaining[0][1] = whiteTimeRemaining;
12348     timeRemaining[1][1] = blackTimeRemaining;
12349     DrawPosition(FALSE, boards[currentMove]);
12350
12351     return TRUE;
12352 }
12353
12354
12355 void
12356 CopyPlayerNameIntoFileName (char **dest, char *src)
12357 {
12358     while (*src != NULLCHAR && *src != ',') {
12359         if (*src == ' ') {
12360             *(*dest)++ = '_';
12361             src++;
12362         } else {
12363             *(*dest)++ = *src++;
12364         }
12365     }
12366 }
12367
12368 char *
12369 DefaultFileName (char *ext)
12370 {
12371     static char def[MSG_SIZ];
12372     char *p;
12373
12374     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12375         p = def;
12376         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12377         *p++ = '-';
12378         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12379         *p++ = '.';
12380         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12381     } else {
12382         def[0] = NULLCHAR;
12383     }
12384     return def;
12385 }
12386
12387 /* Save the current game to the given file */
12388 int
12389 SaveGameToFile (char *filename, int append)
12390 {
12391     FILE *f;
12392     char buf[MSG_SIZ];
12393     int result, i, t,tot=0;
12394
12395     if (strcmp(filename, "-") == 0) {
12396         return SaveGame(stdout, 0, NULL);
12397     } else {
12398         for(i=0; i<10; i++) { // upto 10 tries
12399              f = fopen(filename, append ? "a" : "w");
12400              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12401              if(f || errno != 13) break;
12402              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12403              tot += t;
12404         }
12405         if (f == NULL) {
12406             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12407             DisplayError(buf, errno);
12408             return FALSE;
12409         } else {
12410             safeStrCpy(buf, lastMsg, MSG_SIZ);
12411             DisplayMessage(_("Waiting for access to save file"), "");
12412             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12413             DisplayMessage(_("Saving game"), "");
12414             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12415             result = SaveGame(f, 0, NULL);
12416             DisplayMessage(buf, "");
12417             return result;
12418         }
12419     }
12420 }
12421
12422 char *
12423 SavePart (char *str)
12424 {
12425     static char buf[MSG_SIZ];
12426     char *p;
12427
12428     p = strchr(str, ' ');
12429     if (p == NULL) return str;
12430     strncpy(buf, str, p - str);
12431     buf[p - str] = NULLCHAR;
12432     return buf;
12433 }
12434
12435 #define PGN_MAX_LINE 75
12436
12437 #define PGN_SIDE_WHITE  0
12438 #define PGN_SIDE_BLACK  1
12439
12440 static int
12441 FindFirstMoveOutOfBook (int side)
12442 {
12443     int result = -1;
12444
12445     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12446         int index = backwardMostMove;
12447         int has_book_hit = 0;
12448
12449         if( (index % 2) != side ) {
12450             index++;
12451         }
12452
12453         while( index < forwardMostMove ) {
12454             /* Check to see if engine is in book */
12455             int depth = pvInfoList[index].depth;
12456             int score = pvInfoList[index].score;
12457             int in_book = 0;
12458
12459             if( depth <= 2 ) {
12460                 in_book = 1;
12461             }
12462             else if( score == 0 && depth == 63 ) {
12463                 in_book = 1; /* Zappa */
12464             }
12465             else if( score == 2 && depth == 99 ) {
12466                 in_book = 1; /* Abrok */
12467             }
12468
12469             has_book_hit += in_book;
12470
12471             if( ! in_book ) {
12472                 result = index;
12473
12474                 break;
12475             }
12476
12477             index += 2;
12478         }
12479     }
12480
12481     return result;
12482 }
12483
12484 void
12485 GetOutOfBookInfo (char * buf)
12486 {
12487     int oob[2];
12488     int i;
12489     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12490
12491     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12492     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12493
12494     *buf = '\0';
12495
12496     if( oob[0] >= 0 || oob[1] >= 0 ) {
12497         for( i=0; i<2; i++ ) {
12498             int idx = oob[i];
12499
12500             if( idx >= 0 ) {
12501                 if( i > 0 && oob[0] >= 0 ) {
12502                     strcat( buf, "   " );
12503                 }
12504
12505                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12506                 sprintf( buf+strlen(buf), "%s%.2f",
12507                     pvInfoList[idx].score >= 0 ? "+" : "",
12508                     pvInfoList[idx].score / 100.0 );
12509             }
12510         }
12511     }
12512 }
12513
12514 /* Save game in PGN style and close the file */
12515 int
12516 SaveGamePGN (FILE *f)
12517 {
12518     int i, offset, linelen, newblock;
12519     time_t tm;
12520 //    char *movetext;
12521     char numtext[32];
12522     int movelen, numlen, blank;
12523     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12524
12525     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12526
12527     tm = time((time_t *) NULL);
12528
12529     PrintPGNTags(f, &gameInfo);
12530
12531     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12532
12533     if (backwardMostMove > 0 || startedFromSetupPosition) {
12534         char *fen = PositionToFEN(backwardMostMove, NULL);
12535         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12536         fprintf(f, "\n{--------------\n");
12537         PrintPosition(f, backwardMostMove);
12538         fprintf(f, "--------------}\n");
12539         free(fen);
12540     }
12541     else {
12542         /* [AS] Out of book annotation */
12543         if( appData.saveOutOfBookInfo ) {
12544             char buf[64];
12545
12546             GetOutOfBookInfo( buf );
12547
12548             if( buf[0] != '\0' ) {
12549                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12550             }
12551         }
12552
12553         fprintf(f, "\n");
12554     }
12555
12556     i = backwardMostMove;
12557     linelen = 0;
12558     newblock = TRUE;
12559
12560     while (i < forwardMostMove) {
12561         /* Print comments preceding this move */
12562         if (commentList[i] != NULL) {
12563             if (linelen > 0) fprintf(f, "\n");
12564             fprintf(f, "%s", commentList[i]);
12565             linelen = 0;
12566             newblock = TRUE;
12567         }
12568
12569         /* Format move number */
12570         if ((i % 2) == 0)
12571           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12572         else
12573           if (newblock)
12574             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12575           else
12576             numtext[0] = NULLCHAR;
12577
12578         numlen = strlen(numtext);
12579         newblock = FALSE;
12580
12581         /* Print move number */
12582         blank = linelen > 0 && numlen > 0;
12583         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12584             fprintf(f, "\n");
12585             linelen = 0;
12586             blank = 0;
12587         }
12588         if (blank) {
12589             fprintf(f, " ");
12590             linelen++;
12591         }
12592         fprintf(f, "%s", numtext);
12593         linelen += numlen;
12594
12595         /* Get move */
12596         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12597         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12598
12599         /* Print move */
12600         blank = linelen > 0 && movelen > 0;
12601         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12602             fprintf(f, "\n");
12603             linelen = 0;
12604             blank = 0;
12605         }
12606         if (blank) {
12607             fprintf(f, " ");
12608             linelen++;
12609         }
12610         fprintf(f, "%s", move_buffer);
12611         linelen += movelen;
12612
12613         /* [AS] Add PV info if present */
12614         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12615             /* [HGM] add time */
12616             char buf[MSG_SIZ]; int seconds;
12617
12618             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12619
12620             if( seconds <= 0)
12621               buf[0] = 0;
12622             else
12623               if( seconds < 30 )
12624                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12625               else
12626                 {
12627                   seconds = (seconds + 4)/10; // round to full seconds
12628                   if( seconds < 60 )
12629                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12630                   else
12631                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12632                 }
12633
12634             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12635                       pvInfoList[i].score >= 0 ? "+" : "",
12636                       pvInfoList[i].score / 100.0,
12637                       pvInfoList[i].depth,
12638                       buf );
12639
12640             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12641
12642             /* Print score/depth */
12643             blank = linelen > 0 && movelen > 0;
12644             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12645                 fprintf(f, "\n");
12646                 linelen = 0;
12647                 blank = 0;
12648             }
12649             if (blank) {
12650                 fprintf(f, " ");
12651                 linelen++;
12652             }
12653             fprintf(f, "%s", move_buffer);
12654             linelen += movelen;
12655         }
12656
12657         i++;
12658     }
12659
12660     /* Start a new line */
12661     if (linelen > 0) fprintf(f, "\n");
12662
12663     /* Print comments after last move */
12664     if (commentList[i] != NULL) {
12665         fprintf(f, "%s\n", commentList[i]);
12666     }
12667
12668     /* Print result */
12669     if (gameInfo.resultDetails != NULL &&
12670         gameInfo.resultDetails[0] != NULLCHAR) {
12671         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12672                 PGNResult(gameInfo.result));
12673     } else {
12674         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12675     }
12676
12677     fclose(f);
12678     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12679     return TRUE;
12680 }
12681
12682 /* Save game in old style and close the file */
12683 int
12684 SaveGameOldStyle (FILE *f)
12685 {
12686     int i, offset;
12687     time_t tm;
12688
12689     tm = time((time_t *) NULL);
12690
12691     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12692     PrintOpponents(f);
12693
12694     if (backwardMostMove > 0 || startedFromSetupPosition) {
12695         fprintf(f, "\n[--------------\n");
12696         PrintPosition(f, backwardMostMove);
12697         fprintf(f, "--------------]\n");
12698     } else {
12699         fprintf(f, "\n");
12700     }
12701
12702     i = backwardMostMove;
12703     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12704
12705     while (i < forwardMostMove) {
12706         if (commentList[i] != NULL) {
12707             fprintf(f, "[%s]\n", commentList[i]);
12708         }
12709
12710         if ((i % 2) == 1) {
12711             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12712             i++;
12713         } else {
12714             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12715             i++;
12716             if (commentList[i] != NULL) {
12717                 fprintf(f, "\n");
12718                 continue;
12719             }
12720             if (i >= forwardMostMove) {
12721                 fprintf(f, "\n");
12722                 break;
12723             }
12724             fprintf(f, "%s\n", parseList[i]);
12725             i++;
12726         }
12727     }
12728
12729     if (commentList[i] != NULL) {
12730         fprintf(f, "[%s]\n", commentList[i]);
12731     }
12732
12733     /* This isn't really the old style, but it's close enough */
12734     if (gameInfo.resultDetails != NULL &&
12735         gameInfo.resultDetails[0] != NULLCHAR) {
12736         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12737                 gameInfo.resultDetails);
12738     } else {
12739         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12740     }
12741
12742     fclose(f);
12743     return TRUE;
12744 }
12745
12746 /* Save the current game to open file f and close the file */
12747 int
12748 SaveGame (FILE *f, int dummy, char *dummy2)
12749 {
12750     if (gameMode == EditPosition) EditPositionDone(TRUE);
12751     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12752     if (appData.oldSaveStyle)
12753       return SaveGameOldStyle(f);
12754     else
12755       return SaveGamePGN(f);
12756 }
12757
12758 /* Save the current position to the given file */
12759 int
12760 SavePositionToFile (char *filename)
12761 {
12762     FILE *f;
12763     char buf[MSG_SIZ];
12764
12765     if (strcmp(filename, "-") == 0) {
12766         return SavePosition(stdout, 0, NULL);
12767     } else {
12768         f = fopen(filename, "a");
12769         if (f == NULL) {
12770             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12771             DisplayError(buf, errno);
12772             return FALSE;
12773         } else {
12774             safeStrCpy(buf, lastMsg, MSG_SIZ);
12775             DisplayMessage(_("Waiting for access to save file"), "");
12776             flock(fileno(f), LOCK_EX); // [HGM] lock
12777             DisplayMessage(_("Saving position"), "");
12778             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12779             SavePosition(f, 0, NULL);
12780             DisplayMessage(buf, "");
12781             return TRUE;
12782         }
12783     }
12784 }
12785
12786 /* Save the current position to the given open file and close the file */
12787 int
12788 SavePosition (FILE *f, int dummy, char *dummy2)
12789 {
12790     time_t tm;
12791     char *fen;
12792
12793     if (gameMode == EditPosition) EditPositionDone(TRUE);
12794     if (appData.oldSaveStyle) {
12795         tm = time((time_t *) NULL);
12796
12797         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12798         PrintOpponents(f);
12799         fprintf(f, "[--------------\n");
12800         PrintPosition(f, currentMove);
12801         fprintf(f, "--------------]\n");
12802     } else {
12803         fen = PositionToFEN(currentMove, NULL);
12804         fprintf(f, "%s\n", fen);
12805         free(fen);
12806     }
12807     fclose(f);
12808     return TRUE;
12809 }
12810
12811 void
12812 ReloadCmailMsgEvent (int unregister)
12813 {
12814 #if !WIN32
12815     static char *inFilename = NULL;
12816     static char *outFilename;
12817     int i;
12818     struct stat inbuf, outbuf;
12819     int status;
12820
12821     /* Any registered moves are unregistered if unregister is set, */
12822     /* i.e. invoked by the signal handler */
12823     if (unregister) {
12824         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12825             cmailMoveRegistered[i] = FALSE;
12826             if (cmailCommentList[i] != NULL) {
12827                 free(cmailCommentList[i]);
12828                 cmailCommentList[i] = NULL;
12829             }
12830         }
12831         nCmailMovesRegistered = 0;
12832     }
12833
12834     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12835         cmailResult[i] = CMAIL_NOT_RESULT;
12836     }
12837     nCmailResults = 0;
12838
12839     if (inFilename == NULL) {
12840         /* Because the filenames are static they only get malloced once  */
12841         /* and they never get freed                                      */
12842         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12843         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12844
12845         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12846         sprintf(outFilename, "%s.out", appData.cmailGameName);
12847     }
12848
12849     status = stat(outFilename, &outbuf);
12850     if (status < 0) {
12851         cmailMailedMove = FALSE;
12852     } else {
12853         status = stat(inFilename, &inbuf);
12854         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12855     }
12856
12857     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12858        counts the games, notes how each one terminated, etc.
12859
12860        It would be nice to remove this kludge and instead gather all
12861        the information while building the game list.  (And to keep it
12862        in the game list nodes instead of having a bunch of fixed-size
12863        parallel arrays.)  Note this will require getting each game's
12864        termination from the PGN tags, as the game list builder does
12865        not process the game moves.  --mann
12866        */
12867     cmailMsgLoaded = TRUE;
12868     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12869
12870     /* Load first game in the file or popup game menu */
12871     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12872
12873 #endif /* !WIN32 */
12874     return;
12875 }
12876
12877 int
12878 RegisterMove ()
12879 {
12880     FILE *f;
12881     char string[MSG_SIZ];
12882
12883     if (   cmailMailedMove
12884         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12885         return TRUE;            /* Allow free viewing  */
12886     }
12887
12888     /* Unregister move to ensure that we don't leave RegisterMove        */
12889     /* with the move registered when the conditions for registering no   */
12890     /* longer hold                                                       */
12891     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12892         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12893         nCmailMovesRegistered --;
12894
12895         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12896           {
12897               free(cmailCommentList[lastLoadGameNumber - 1]);
12898               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12899           }
12900     }
12901
12902     if (cmailOldMove == -1) {
12903         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12904         return FALSE;
12905     }
12906
12907     if (currentMove > cmailOldMove + 1) {
12908         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12909         return FALSE;
12910     }
12911
12912     if (currentMove < cmailOldMove) {
12913         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12914         return FALSE;
12915     }
12916
12917     if (forwardMostMove > currentMove) {
12918         /* Silently truncate extra moves */
12919         TruncateGame();
12920     }
12921
12922     if (   (currentMove == cmailOldMove + 1)
12923         || (   (currentMove == cmailOldMove)
12924             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12925                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12926         if (gameInfo.result != GameUnfinished) {
12927             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12928         }
12929
12930         if (commentList[currentMove] != NULL) {
12931             cmailCommentList[lastLoadGameNumber - 1]
12932               = StrSave(commentList[currentMove]);
12933         }
12934         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12935
12936         if (appData.debugMode)
12937           fprintf(debugFP, "Saving %s for game %d\n",
12938                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12939
12940         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12941
12942         f = fopen(string, "w");
12943         if (appData.oldSaveStyle) {
12944             SaveGameOldStyle(f); /* also closes the file */
12945
12946             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12947             f = fopen(string, "w");
12948             SavePosition(f, 0, NULL); /* also closes the file */
12949         } else {
12950             fprintf(f, "{--------------\n");
12951             PrintPosition(f, currentMove);
12952             fprintf(f, "--------------}\n\n");
12953
12954             SaveGame(f, 0, NULL); /* also closes the file*/
12955         }
12956
12957         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12958         nCmailMovesRegistered ++;
12959     } else if (nCmailGames == 1) {
12960         DisplayError(_("You have not made a move yet"), 0);
12961         return FALSE;
12962     }
12963
12964     return TRUE;
12965 }
12966
12967 void
12968 MailMoveEvent ()
12969 {
12970 #if !WIN32
12971     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12972     FILE *commandOutput;
12973     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12974     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12975     int nBuffers;
12976     int i;
12977     int archived;
12978     char *arcDir;
12979
12980     if (! cmailMsgLoaded) {
12981         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12982         return;
12983     }
12984
12985     if (nCmailGames == nCmailResults) {
12986         DisplayError(_("No unfinished games"), 0);
12987         return;
12988     }
12989
12990 #if CMAIL_PROHIBIT_REMAIL
12991     if (cmailMailedMove) {
12992       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);
12993         DisplayError(msg, 0);
12994         return;
12995     }
12996 #endif
12997
12998     if (! (cmailMailedMove || RegisterMove())) return;
12999
13000     if (   cmailMailedMove
13001         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13002       snprintf(string, MSG_SIZ, partCommandString,
13003                appData.debugMode ? " -v" : "", appData.cmailGameName);
13004         commandOutput = popen(string, "r");
13005
13006         if (commandOutput == NULL) {
13007             DisplayError(_("Failed to invoke cmail"), 0);
13008         } else {
13009             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13010                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13011             }
13012             if (nBuffers > 1) {
13013                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13014                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13015                 nBytes = MSG_SIZ - 1;
13016             } else {
13017                 (void) memcpy(msg, buffer, nBytes);
13018             }
13019             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13020
13021             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13022                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13023
13024                 archived = TRUE;
13025                 for (i = 0; i < nCmailGames; i ++) {
13026                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13027                         archived = FALSE;
13028                     }
13029                 }
13030                 if (   archived
13031                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13032                         != NULL)) {
13033                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13034                            arcDir,
13035                            appData.cmailGameName,
13036                            gameInfo.date);
13037                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13038                     cmailMsgLoaded = FALSE;
13039                 }
13040             }
13041
13042             DisplayInformation(msg);
13043             pclose(commandOutput);
13044         }
13045     } else {
13046         if ((*cmailMsg) != '\0') {
13047             DisplayInformation(cmailMsg);
13048         }
13049     }
13050
13051     return;
13052 #endif /* !WIN32 */
13053 }
13054
13055 char *
13056 CmailMsg ()
13057 {
13058 #if WIN32
13059     return NULL;
13060 #else
13061     int  prependComma = 0;
13062     char number[5];
13063     char string[MSG_SIZ];       /* Space for game-list */
13064     int  i;
13065
13066     if (!cmailMsgLoaded) return "";
13067
13068     if (cmailMailedMove) {
13069       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13070     } else {
13071         /* Create a list of games left */
13072       snprintf(string, MSG_SIZ, "[");
13073         for (i = 0; i < nCmailGames; i ++) {
13074             if (! (   cmailMoveRegistered[i]
13075                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13076                 if (prependComma) {
13077                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13078                 } else {
13079                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13080                     prependComma = 1;
13081                 }
13082
13083                 strcat(string, number);
13084             }
13085         }
13086         strcat(string, "]");
13087
13088         if (nCmailMovesRegistered + nCmailResults == 0) {
13089             switch (nCmailGames) {
13090               case 1:
13091                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13092                 break;
13093
13094               case 2:
13095                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13096                 break;
13097
13098               default:
13099                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13100                          nCmailGames);
13101                 break;
13102             }
13103         } else {
13104             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13105               case 1:
13106                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13107                          string);
13108                 break;
13109
13110               case 0:
13111                 if (nCmailResults == nCmailGames) {
13112                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13113                 } else {
13114                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13115                 }
13116                 break;
13117
13118               default:
13119                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13120                          string);
13121             }
13122         }
13123     }
13124     return cmailMsg;
13125 #endif /* WIN32 */
13126 }
13127
13128 void
13129 ResetGameEvent ()
13130 {
13131     if (gameMode == Training)
13132       SetTrainingModeOff();
13133
13134     Reset(TRUE, TRUE);
13135     cmailMsgLoaded = FALSE;
13136     if (appData.icsActive) {
13137       SendToICS(ics_prefix);
13138       SendToICS("refresh\n");
13139     }
13140 }
13141
13142 void
13143 ExitEvent (int status)
13144 {
13145     exiting++;
13146     if (exiting > 2) {
13147       /* Give up on clean exit */
13148       exit(status);
13149     }
13150     if (exiting > 1) {
13151       /* Keep trying for clean exit */
13152       return;
13153     }
13154
13155     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13156
13157     if (telnetISR != NULL) {
13158       RemoveInputSource(telnetISR);
13159     }
13160     if (icsPR != NoProc) {
13161       DestroyChildProcess(icsPR, TRUE);
13162     }
13163
13164     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13165     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13166
13167     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13168     /* make sure this other one finishes before killing it!                  */
13169     if(endingGame) { int count = 0;
13170         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13171         while(endingGame && count++ < 10) DoSleep(1);
13172         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13173     }
13174
13175     /* Kill off chess programs */
13176     if (first.pr != NoProc) {
13177         ExitAnalyzeMode();
13178
13179         DoSleep( appData.delayBeforeQuit );
13180         SendToProgram("quit\n", &first);
13181         DoSleep( appData.delayAfterQuit );
13182         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13183     }
13184     if (second.pr != NoProc) {
13185         DoSleep( appData.delayBeforeQuit );
13186         SendToProgram("quit\n", &second);
13187         DoSleep( appData.delayAfterQuit );
13188         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13189     }
13190     if (first.isr != NULL) {
13191         RemoveInputSource(first.isr);
13192     }
13193     if (second.isr != NULL) {
13194         RemoveInputSource(second.isr);
13195     }
13196
13197     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13198     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13199
13200     ShutDownFrontEnd();
13201     exit(status);
13202 }
13203
13204 void
13205 PauseEvent ()
13206 {
13207     if (appData.debugMode)
13208         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13209     if (pausing) {
13210         pausing = FALSE;
13211         ModeHighlight();
13212         if (gameMode == MachinePlaysWhite ||
13213             gameMode == MachinePlaysBlack) {
13214             StartClocks();
13215         } else {
13216             DisplayBothClocks();
13217         }
13218         if (gameMode == PlayFromGameFile) {
13219             if (appData.timeDelay >= 0)
13220                 AutoPlayGameLoop();
13221         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13222             Reset(FALSE, TRUE);
13223             SendToICS(ics_prefix);
13224             SendToICS("refresh\n");
13225         } else if (currentMove < forwardMostMove) {
13226             ForwardInner(forwardMostMove);
13227         }
13228         pauseExamInvalid = FALSE;
13229     } else {
13230         switch (gameMode) {
13231           default:
13232             return;
13233           case IcsExamining:
13234             pauseExamForwardMostMove = forwardMostMove;
13235             pauseExamInvalid = FALSE;
13236             /* fall through */
13237           case IcsObserving:
13238           case IcsPlayingWhite:
13239           case IcsPlayingBlack:
13240             pausing = TRUE;
13241             ModeHighlight();
13242             return;
13243           case PlayFromGameFile:
13244             (void) StopLoadGameTimer();
13245             pausing = TRUE;
13246             ModeHighlight();
13247             break;
13248           case BeginningOfGame:
13249             if (appData.icsActive) return;
13250             /* else fall through */
13251           case MachinePlaysWhite:
13252           case MachinePlaysBlack:
13253           case TwoMachinesPlay:
13254             if (forwardMostMove == 0)
13255               return;           /* don't pause if no one has moved */
13256             if ((gameMode == MachinePlaysWhite &&
13257                  !WhiteOnMove(forwardMostMove)) ||
13258                 (gameMode == MachinePlaysBlack &&
13259                  WhiteOnMove(forwardMostMove))) {
13260                 StopClocks();
13261             }
13262             pausing = TRUE;
13263             ModeHighlight();
13264             break;
13265         }
13266     }
13267 }
13268
13269 void
13270 EditCommentEvent ()
13271 {
13272     char title[MSG_SIZ];
13273
13274     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13275       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13276     } else {
13277       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13278                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13279                parseList[currentMove - 1]);
13280     }
13281
13282     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13283 }
13284
13285
13286 void
13287 EditTagsEvent ()
13288 {
13289     char *tags = PGNTags(&gameInfo);
13290     bookUp = FALSE;
13291     EditTagsPopUp(tags, NULL);
13292     free(tags);
13293 }
13294
13295 void
13296 AnalyzeModeEvent ()
13297 {
13298     if (appData.noChessProgram || gameMode == AnalyzeMode)
13299       return;
13300
13301     if (gameMode != AnalyzeFile) {
13302         if (!appData.icsEngineAnalyze) {
13303                EditGameEvent();
13304                if (gameMode != EditGame) return;
13305         }
13306         ResurrectChessProgram();
13307         SendToProgram("analyze\n", &first);
13308         first.analyzing = TRUE;
13309         /*first.maybeThinking = TRUE;*/
13310         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13311         EngineOutputPopUp();
13312     }
13313     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13314     pausing = FALSE;
13315     ModeHighlight();
13316     SetGameInfo();
13317
13318     StartAnalysisClock();
13319     GetTimeMark(&lastNodeCountTime);
13320     lastNodeCount = 0;
13321 }
13322
13323 void
13324 AnalyzeFileEvent ()
13325 {
13326     if (appData.noChessProgram || gameMode == AnalyzeFile)
13327       return;
13328
13329     if (gameMode != AnalyzeMode) {
13330         EditGameEvent();
13331         if (gameMode != EditGame) return;
13332         ResurrectChessProgram();
13333         SendToProgram("analyze\n", &first);
13334         first.analyzing = TRUE;
13335         /*first.maybeThinking = TRUE;*/
13336         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13337         EngineOutputPopUp();
13338     }
13339     gameMode = AnalyzeFile;
13340     pausing = FALSE;
13341     ModeHighlight();
13342     SetGameInfo();
13343
13344     StartAnalysisClock();
13345     GetTimeMark(&lastNodeCountTime);
13346     lastNodeCount = 0;
13347     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13348 }
13349
13350 void
13351 MachineWhiteEvent ()
13352 {
13353     char buf[MSG_SIZ];
13354     char *bookHit = NULL;
13355
13356     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13357       return;
13358
13359
13360     if (gameMode == PlayFromGameFile ||
13361         gameMode == TwoMachinesPlay  ||
13362         gameMode == Training         ||
13363         gameMode == AnalyzeMode      ||
13364         gameMode == EndOfGame)
13365         EditGameEvent();
13366
13367     if (gameMode == EditPosition)
13368         EditPositionDone(TRUE);
13369
13370     if (!WhiteOnMove(currentMove)) {
13371         DisplayError(_("It is not White's turn"), 0);
13372         return;
13373     }
13374
13375     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13376       ExitAnalyzeMode();
13377
13378     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13379         gameMode == AnalyzeFile)
13380         TruncateGame();
13381
13382     ResurrectChessProgram();    /* in case it isn't running */
13383     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13384         gameMode = MachinePlaysWhite;
13385         ResetClocks();
13386     } else
13387     gameMode = MachinePlaysWhite;
13388     pausing = FALSE;
13389     ModeHighlight();
13390     SetGameInfo();
13391     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13392     DisplayTitle(buf);
13393     if (first.sendName) {
13394       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13395       SendToProgram(buf, &first);
13396     }
13397     if (first.sendTime) {
13398       if (first.useColors) {
13399         SendToProgram("black\n", &first); /*gnu kludge*/
13400       }
13401       SendTimeRemaining(&first, TRUE);
13402     }
13403     if (first.useColors) {
13404       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13405     }
13406     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13407     SetMachineThinkingEnables();
13408     first.maybeThinking = TRUE;
13409     StartClocks();
13410     firstMove = FALSE;
13411
13412     if (appData.autoFlipView && !flipView) {
13413       flipView = !flipView;
13414       DrawPosition(FALSE, NULL);
13415       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13416     }
13417
13418     if(bookHit) { // [HGM] book: simulate book reply
13419         static char bookMove[MSG_SIZ]; // a bit generous?
13420
13421         programStats.nodes = programStats.depth = programStats.time =
13422         programStats.score = programStats.got_only_move = 0;
13423         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13424
13425         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13426         strcat(bookMove, bookHit);
13427         HandleMachineMove(bookMove, &first);
13428     }
13429 }
13430
13431 void
13432 MachineBlackEvent ()
13433 {
13434   char buf[MSG_SIZ];
13435   char *bookHit = NULL;
13436
13437     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13438         return;
13439
13440
13441     if (gameMode == PlayFromGameFile ||
13442         gameMode == TwoMachinesPlay  ||
13443         gameMode == Training         ||
13444         gameMode == AnalyzeMode      ||
13445         gameMode == EndOfGame)
13446         EditGameEvent();
13447
13448     if (gameMode == EditPosition)
13449         EditPositionDone(TRUE);
13450
13451     if (WhiteOnMove(currentMove)) {
13452         DisplayError(_("It is not Black's turn"), 0);
13453         return;
13454     }
13455
13456     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13457       ExitAnalyzeMode();
13458
13459     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13460         gameMode == AnalyzeFile)
13461         TruncateGame();
13462
13463     ResurrectChessProgram();    /* in case it isn't running */
13464     gameMode = MachinePlaysBlack;
13465     pausing = FALSE;
13466     ModeHighlight();
13467     SetGameInfo();
13468     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13469     DisplayTitle(buf);
13470     if (first.sendName) {
13471       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13472       SendToProgram(buf, &first);
13473     }
13474     if (first.sendTime) {
13475       if (first.useColors) {
13476         SendToProgram("white\n", &first); /*gnu kludge*/
13477       }
13478       SendTimeRemaining(&first, FALSE);
13479     }
13480     if (first.useColors) {
13481       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13482     }
13483     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13484     SetMachineThinkingEnables();
13485     first.maybeThinking = TRUE;
13486     StartClocks();
13487
13488     if (appData.autoFlipView && flipView) {
13489       flipView = !flipView;
13490       DrawPosition(FALSE, NULL);
13491       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13492     }
13493     if(bookHit) { // [HGM] book: simulate book reply
13494         static char bookMove[MSG_SIZ]; // a bit generous?
13495
13496         programStats.nodes = programStats.depth = programStats.time =
13497         programStats.score = programStats.got_only_move = 0;
13498         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13499
13500         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13501         strcat(bookMove, bookHit);
13502         HandleMachineMove(bookMove, &first);
13503     }
13504 }
13505
13506
13507 void
13508 DisplayTwoMachinesTitle ()
13509 {
13510     char buf[MSG_SIZ];
13511     if (appData.matchGames > 0) {
13512         if(appData.tourneyFile[0]) {
13513           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13514                    gameInfo.white, _("vs."), gameInfo.black,
13515                    nextGame+1, appData.matchGames+1,
13516                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13517         } else 
13518         if (first.twoMachinesColor[0] == 'w') {
13519           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13520                    gameInfo.white, _("vs."),  gameInfo.black,
13521                    first.matchWins, second.matchWins,
13522                    matchGame - 1 - (first.matchWins + second.matchWins));
13523         } else {
13524           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13525                    gameInfo.white, _("vs."), gameInfo.black,
13526                    second.matchWins, first.matchWins,
13527                    matchGame - 1 - (first.matchWins + second.matchWins));
13528         }
13529     } else {
13530       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13531     }
13532     DisplayTitle(buf);
13533 }
13534
13535 void
13536 SettingsMenuIfReady ()
13537 {
13538   if (second.lastPing != second.lastPong) {
13539     DisplayMessage("", _("Waiting for second chess program"));
13540     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13541     return;
13542   }
13543   ThawUI();
13544   DisplayMessage("", "");
13545   SettingsPopUp(&second);
13546 }
13547
13548 int
13549 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13550 {
13551     char buf[MSG_SIZ];
13552     if (cps->pr == NoProc) {
13553         StartChessProgram(cps);
13554         if (cps->protocolVersion == 1) {
13555           retry();
13556         } else {
13557           /* kludge: allow timeout for initial "feature" command */
13558           FreezeUI();
13559           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13560           DisplayMessage("", buf);
13561           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13562         }
13563         return 1;
13564     }
13565     return 0;
13566 }
13567
13568 void
13569 TwoMachinesEvent P((void))
13570 {
13571     int i;
13572     char buf[MSG_SIZ];
13573     ChessProgramState *onmove;
13574     char *bookHit = NULL;
13575     static int stalling = 0;
13576     TimeMark now;
13577     long wait;
13578
13579     if (appData.noChessProgram) return;
13580
13581     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13582         DisplayError("second engine does not play this", 0);
13583         return;
13584     }
13585
13586     switch (gameMode) {
13587       case TwoMachinesPlay:
13588         return;
13589       case MachinePlaysWhite:
13590       case MachinePlaysBlack:
13591         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13592             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13593             return;
13594         }
13595         /* fall through */
13596       case BeginningOfGame:
13597       case PlayFromGameFile:
13598       case EndOfGame:
13599         EditGameEvent();
13600         if (gameMode != EditGame) return;
13601         break;
13602       case EditPosition:
13603         EditPositionDone(TRUE);
13604         break;
13605       case AnalyzeMode:
13606       case AnalyzeFile:
13607         ExitAnalyzeMode();
13608         break;
13609       case EditGame:
13610       default:
13611         break;
13612     }
13613
13614 //    forwardMostMove = currentMove;
13615     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13616
13617     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13618
13619     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13620     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13621       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13622       return;
13623     }
13624     if(!stalling) {
13625       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13626       SendToProgram("force\n", &second);
13627       stalling = 1;
13628       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13629       return;
13630     }
13631     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13632     if(appData.matchPause>10000 || appData.matchPause<10)
13633                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13634     wait = SubtractTimeMarks(&now, &pauseStart);
13635     if(wait < appData.matchPause) {
13636         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13637         return;
13638     }
13639     // we are now committed to starting the game
13640     stalling = 0;
13641     DisplayMessage("", "");
13642     if (startedFromSetupPosition) {
13643         SendBoard(&second, backwardMostMove);
13644     if (appData.debugMode) {
13645         fprintf(debugFP, "Two Machines\n");
13646     }
13647     }
13648     for (i = backwardMostMove; i < forwardMostMove; i++) {
13649         SendMoveToProgram(i, &second);
13650     }
13651
13652     gameMode = TwoMachinesPlay;
13653     pausing = FALSE;
13654     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13655     SetGameInfo();
13656     DisplayTwoMachinesTitle();
13657     firstMove = TRUE;
13658     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13659         onmove = &first;
13660     } else {
13661         onmove = &second;
13662     }
13663     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13664     SendToProgram(first.computerString, &first);
13665     if (first.sendName) {
13666       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13667       SendToProgram(buf, &first);
13668     }
13669     SendToProgram(second.computerString, &second);
13670     if (second.sendName) {
13671       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13672       SendToProgram(buf, &second);
13673     }
13674
13675     ResetClocks();
13676     if (!first.sendTime || !second.sendTime) {
13677         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13678         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13679     }
13680     if (onmove->sendTime) {
13681       if (onmove->useColors) {
13682         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13683       }
13684       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13685     }
13686     if (onmove->useColors) {
13687       SendToProgram(onmove->twoMachinesColor, onmove);
13688     }
13689     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13690 //    SendToProgram("go\n", onmove);
13691     onmove->maybeThinking = TRUE;
13692     SetMachineThinkingEnables();
13693
13694     StartClocks();
13695
13696     if(bookHit) { // [HGM] book: simulate book reply
13697         static char bookMove[MSG_SIZ]; // a bit generous?
13698
13699         programStats.nodes = programStats.depth = programStats.time =
13700         programStats.score = programStats.got_only_move = 0;
13701         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13702
13703         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13704         strcat(bookMove, bookHit);
13705         savedMessage = bookMove; // args for deferred call
13706         savedState = onmove;
13707         ScheduleDelayedEvent(DeferredBookMove, 1);
13708     }
13709 }
13710
13711 void
13712 TrainingEvent ()
13713 {
13714     if (gameMode == Training) {
13715       SetTrainingModeOff();
13716       gameMode = PlayFromGameFile;
13717       DisplayMessage("", _("Training mode off"));
13718     } else {
13719       gameMode = Training;
13720       animateTraining = appData.animate;
13721
13722       /* make sure we are not already at the end of the game */
13723       if (currentMove < forwardMostMove) {
13724         SetTrainingModeOn();
13725         DisplayMessage("", _("Training mode on"));
13726       } else {
13727         gameMode = PlayFromGameFile;
13728         DisplayError(_("Already at end of game"), 0);
13729       }
13730     }
13731     ModeHighlight();
13732 }
13733
13734 void
13735 IcsClientEvent ()
13736 {
13737     if (!appData.icsActive) return;
13738     switch (gameMode) {
13739       case IcsPlayingWhite:
13740       case IcsPlayingBlack:
13741       case IcsObserving:
13742       case IcsIdle:
13743       case BeginningOfGame:
13744       case IcsExamining:
13745         return;
13746
13747       case EditGame:
13748         break;
13749
13750       case EditPosition:
13751         EditPositionDone(TRUE);
13752         break;
13753
13754       case AnalyzeMode:
13755       case AnalyzeFile:
13756         ExitAnalyzeMode();
13757         break;
13758
13759       default:
13760         EditGameEvent();
13761         break;
13762     }
13763
13764     gameMode = IcsIdle;
13765     ModeHighlight();
13766     return;
13767 }
13768
13769 void
13770 EditGameEvent ()
13771 {
13772     int i;
13773
13774     switch (gameMode) {
13775       case Training:
13776         SetTrainingModeOff();
13777         break;
13778       case MachinePlaysWhite:
13779       case MachinePlaysBlack:
13780       case BeginningOfGame:
13781         SendToProgram("force\n", &first);
13782         SetUserThinkingEnables();
13783         break;
13784       case PlayFromGameFile:
13785         (void) StopLoadGameTimer();
13786         if (gameFileFP != NULL) {
13787             gameFileFP = NULL;
13788         }
13789         break;
13790       case EditPosition:
13791         EditPositionDone(TRUE);
13792         break;
13793       case AnalyzeMode:
13794       case AnalyzeFile:
13795         ExitAnalyzeMode();
13796         SendToProgram("force\n", &first);
13797         break;
13798       case TwoMachinesPlay:
13799         GameEnds(EndOfFile, NULL, GE_PLAYER);
13800         ResurrectChessProgram();
13801         SetUserThinkingEnables();
13802         break;
13803       case EndOfGame:
13804         ResurrectChessProgram();
13805         break;
13806       case IcsPlayingBlack:
13807       case IcsPlayingWhite:
13808         DisplayError(_("Warning: You are still playing a game"), 0);
13809         break;
13810       case IcsObserving:
13811         DisplayError(_("Warning: You are still observing a game"), 0);
13812         break;
13813       case IcsExamining:
13814         DisplayError(_("Warning: You are still examining a game"), 0);
13815         break;
13816       case IcsIdle:
13817         break;
13818       case EditGame:
13819       default:
13820         return;
13821     }
13822
13823     pausing = FALSE;
13824     StopClocks();
13825     first.offeredDraw = second.offeredDraw = 0;
13826
13827     if (gameMode == PlayFromGameFile) {
13828         whiteTimeRemaining = timeRemaining[0][currentMove];
13829         blackTimeRemaining = timeRemaining[1][currentMove];
13830         DisplayTitle("");
13831     }
13832
13833     if (gameMode == MachinePlaysWhite ||
13834         gameMode == MachinePlaysBlack ||
13835         gameMode == TwoMachinesPlay ||
13836         gameMode == EndOfGame) {
13837         i = forwardMostMove;
13838         while (i > currentMove) {
13839             SendToProgram("undo\n", &first);
13840             i--;
13841         }
13842         if(!adjustedClock) {
13843         whiteTimeRemaining = timeRemaining[0][currentMove];
13844         blackTimeRemaining = timeRemaining[1][currentMove];
13845         DisplayBothClocks();
13846         }
13847         if (whiteFlag || blackFlag) {
13848             whiteFlag = blackFlag = 0;
13849         }
13850         DisplayTitle("");
13851     }
13852
13853     gameMode = EditGame;
13854     ModeHighlight();
13855     SetGameInfo();
13856 }
13857
13858
13859 void
13860 EditPositionEvent ()
13861 {
13862     if (gameMode == EditPosition) {
13863         EditGameEvent();
13864         return;
13865     }
13866
13867     EditGameEvent();
13868     if (gameMode != EditGame) return;
13869
13870     gameMode = EditPosition;
13871     ModeHighlight();
13872     SetGameInfo();
13873     if (currentMove > 0)
13874       CopyBoard(boards[0], boards[currentMove]);
13875
13876     blackPlaysFirst = !WhiteOnMove(currentMove);
13877     ResetClocks();
13878     currentMove = forwardMostMove = backwardMostMove = 0;
13879     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13880     DisplayMove(-1);
13881     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13882 }
13883
13884 void
13885 ExitAnalyzeMode ()
13886 {
13887     /* [DM] icsEngineAnalyze - possible call from other functions */
13888     if (appData.icsEngineAnalyze) {
13889         appData.icsEngineAnalyze = FALSE;
13890
13891         DisplayMessage("",_("Close ICS engine analyze..."));
13892     }
13893     if (first.analysisSupport && first.analyzing) {
13894       SendToProgram("exit\n", &first);
13895       first.analyzing = FALSE;
13896     }
13897     thinkOutput[0] = NULLCHAR;
13898 }
13899
13900 void
13901 EditPositionDone (Boolean fakeRights)
13902 {
13903     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13904
13905     startedFromSetupPosition = TRUE;
13906     InitChessProgram(&first, FALSE);
13907     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13908       boards[0][EP_STATUS] = EP_NONE;
13909       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13910     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13911         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13912         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13913       } else boards[0][CASTLING][2] = NoRights;
13914     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13915         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13916         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13917       } else boards[0][CASTLING][5] = NoRights;
13918     }
13919     SendToProgram("force\n", &first);
13920     if (blackPlaysFirst) {
13921         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13922         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13923         currentMove = forwardMostMove = backwardMostMove = 1;
13924         CopyBoard(boards[1], boards[0]);
13925     } else {
13926         currentMove = forwardMostMove = backwardMostMove = 0;
13927     }
13928     SendBoard(&first, forwardMostMove);
13929     if (appData.debugMode) {
13930         fprintf(debugFP, "EditPosDone\n");
13931     }
13932     DisplayTitle("");
13933     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13934     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13935     gameMode = EditGame;
13936     ModeHighlight();
13937     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13938     ClearHighlights(); /* [AS] */
13939 }
13940
13941 /* Pause for `ms' milliseconds */
13942 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13943 void
13944 TimeDelay (long ms)
13945 {
13946     TimeMark m1, m2;
13947
13948     GetTimeMark(&m1);
13949     do {
13950         GetTimeMark(&m2);
13951     } while (SubtractTimeMarks(&m2, &m1) < ms);
13952 }
13953
13954 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13955 void
13956 SendMultiLineToICS (char *buf)
13957 {
13958     char temp[MSG_SIZ+1], *p;
13959     int len;
13960
13961     len = strlen(buf);
13962     if (len > MSG_SIZ)
13963       len = MSG_SIZ;
13964
13965     strncpy(temp, buf, len);
13966     temp[len] = 0;
13967
13968     p = temp;
13969     while (*p) {
13970         if (*p == '\n' || *p == '\r')
13971           *p = ' ';
13972         ++p;
13973     }
13974
13975     strcat(temp, "\n");
13976     SendToICS(temp);
13977     SendToPlayer(temp, strlen(temp));
13978 }
13979
13980 void
13981 SetWhiteToPlayEvent ()
13982 {
13983     if (gameMode == EditPosition) {
13984         blackPlaysFirst = FALSE;
13985         DisplayBothClocks();    /* works because currentMove is 0 */
13986     } else if (gameMode == IcsExamining) {
13987         SendToICS(ics_prefix);
13988         SendToICS("tomove white\n");
13989     }
13990 }
13991
13992 void
13993 SetBlackToPlayEvent ()
13994 {
13995     if (gameMode == EditPosition) {
13996         blackPlaysFirst = TRUE;
13997         currentMove = 1;        /* kludge */
13998         DisplayBothClocks();
13999         currentMove = 0;
14000     } else if (gameMode == IcsExamining) {
14001         SendToICS(ics_prefix);
14002         SendToICS("tomove black\n");
14003     }
14004 }
14005
14006 void
14007 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14008 {
14009     char buf[MSG_SIZ];
14010     ChessSquare piece = boards[0][y][x];
14011
14012     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14013
14014     switch (selection) {
14015       case ClearBoard:
14016         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14017             SendToICS(ics_prefix);
14018             SendToICS("bsetup clear\n");
14019         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14020             SendToICS(ics_prefix);
14021             SendToICS("clearboard\n");
14022         } else {
14023             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14024                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14025                 for (y = 0; y < BOARD_HEIGHT; y++) {
14026                     if (gameMode == IcsExamining) {
14027                         if (boards[currentMove][y][x] != EmptySquare) {
14028                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14029                                     AAA + x, ONE + y);
14030                             SendToICS(buf);
14031                         }
14032                     } else {
14033                         boards[0][y][x] = p;
14034                     }
14035                 }
14036             }
14037         }
14038         if (gameMode == EditPosition) {
14039             DrawPosition(FALSE, boards[0]);
14040         }
14041         break;
14042
14043       case WhitePlay:
14044         SetWhiteToPlayEvent();
14045         break;
14046
14047       case BlackPlay:
14048         SetBlackToPlayEvent();
14049         break;
14050
14051       case EmptySquare:
14052         if (gameMode == IcsExamining) {
14053             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14054             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14055             SendToICS(buf);
14056         } else {
14057             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14058                 if(x == BOARD_LEFT-2) {
14059                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14060                     boards[0][y][1] = 0;
14061                 } else
14062                 if(x == BOARD_RGHT+1) {
14063                     if(y >= gameInfo.holdingsSize) break;
14064                     boards[0][y][BOARD_WIDTH-2] = 0;
14065                 } else break;
14066             }
14067             boards[0][y][x] = EmptySquare;
14068             DrawPosition(FALSE, boards[0]);
14069         }
14070         break;
14071
14072       case PromotePiece:
14073         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14074            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14075             selection = (ChessSquare) (PROMOTED piece);
14076         } else if(piece == EmptySquare) selection = WhiteSilver;
14077         else selection = (ChessSquare)((int)piece - 1);
14078         goto defaultlabel;
14079
14080       case DemotePiece:
14081         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14082            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14083             selection = (ChessSquare) (DEMOTED piece);
14084         } else if(piece == EmptySquare) selection = BlackSilver;
14085         else selection = (ChessSquare)((int)piece + 1);
14086         goto defaultlabel;
14087
14088       case WhiteQueen:
14089       case BlackQueen:
14090         if(gameInfo.variant == VariantShatranj ||
14091            gameInfo.variant == VariantXiangqi  ||
14092            gameInfo.variant == VariantCourier  ||
14093            gameInfo.variant == VariantMakruk     )
14094             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14095         goto defaultlabel;
14096
14097       case WhiteKing:
14098       case BlackKing:
14099         if(gameInfo.variant == VariantXiangqi)
14100             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14101         if(gameInfo.variant == VariantKnightmate)
14102             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14103       default:
14104         defaultlabel:
14105         if (gameMode == IcsExamining) {
14106             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14107             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14108                      PieceToChar(selection), AAA + x, ONE + y);
14109             SendToICS(buf);
14110         } else {
14111             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14112                 int n;
14113                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14114                     n = PieceToNumber(selection - BlackPawn);
14115                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14116                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14117                     boards[0][BOARD_HEIGHT-1-n][1]++;
14118                 } else
14119                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14120                     n = PieceToNumber(selection);
14121                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14122                     boards[0][n][BOARD_WIDTH-1] = selection;
14123                     boards[0][n][BOARD_WIDTH-2]++;
14124                 }
14125             } else
14126             boards[0][y][x] = selection;
14127             DrawPosition(TRUE, boards[0]);
14128             ClearHighlights();
14129             fromX = fromY = -1;
14130         }
14131         break;
14132     }
14133 }
14134
14135
14136 void
14137 DropMenuEvent (ChessSquare selection, int x, int y)
14138 {
14139     ChessMove moveType;
14140
14141     switch (gameMode) {
14142       case IcsPlayingWhite:
14143       case MachinePlaysBlack:
14144         if (!WhiteOnMove(currentMove)) {
14145             DisplayMoveError(_("It is Black's turn"));
14146             return;
14147         }
14148         moveType = WhiteDrop;
14149         break;
14150       case IcsPlayingBlack:
14151       case MachinePlaysWhite:
14152         if (WhiteOnMove(currentMove)) {
14153             DisplayMoveError(_("It is White's turn"));
14154             return;
14155         }
14156         moveType = BlackDrop;
14157         break;
14158       case EditGame:
14159         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14160         break;
14161       default:
14162         return;
14163     }
14164
14165     if (moveType == BlackDrop && selection < BlackPawn) {
14166       selection = (ChessSquare) ((int) selection
14167                                  + (int) BlackPawn - (int) WhitePawn);
14168     }
14169     if (boards[currentMove][y][x] != EmptySquare) {
14170         DisplayMoveError(_("That square is occupied"));
14171         return;
14172     }
14173
14174     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14175 }
14176
14177 void
14178 AcceptEvent ()
14179 {
14180     /* Accept a pending offer of any kind from opponent */
14181
14182     if (appData.icsActive) {
14183         SendToICS(ics_prefix);
14184         SendToICS("accept\n");
14185     } else if (cmailMsgLoaded) {
14186         if (currentMove == cmailOldMove &&
14187             commentList[cmailOldMove] != NULL &&
14188             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14189                    "Black offers a draw" : "White offers a draw")) {
14190             TruncateGame();
14191             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14192             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14193         } else {
14194             DisplayError(_("There is no pending offer on this move"), 0);
14195             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14196         }
14197     } else {
14198         /* Not used for offers from chess program */
14199     }
14200 }
14201
14202 void
14203 DeclineEvent ()
14204 {
14205     /* Decline a pending offer of any kind from opponent */
14206
14207     if (appData.icsActive) {
14208         SendToICS(ics_prefix);
14209         SendToICS("decline\n");
14210     } else if (cmailMsgLoaded) {
14211         if (currentMove == cmailOldMove &&
14212             commentList[cmailOldMove] != NULL &&
14213             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14214                    "Black offers a draw" : "White offers a draw")) {
14215 #ifdef NOTDEF
14216             AppendComment(cmailOldMove, "Draw declined", TRUE);
14217             DisplayComment(cmailOldMove - 1, "Draw declined");
14218 #endif /*NOTDEF*/
14219         } else {
14220             DisplayError(_("There is no pending offer on this move"), 0);
14221         }
14222     } else {
14223         /* Not used for offers from chess program */
14224     }
14225 }
14226
14227 void
14228 RematchEvent ()
14229 {
14230     /* Issue ICS rematch command */
14231     if (appData.icsActive) {
14232         SendToICS(ics_prefix);
14233         SendToICS("rematch\n");
14234     }
14235 }
14236
14237 void
14238 CallFlagEvent ()
14239 {
14240     /* Call your opponent's flag (claim a win on time) */
14241     if (appData.icsActive) {
14242         SendToICS(ics_prefix);
14243         SendToICS("flag\n");
14244     } else {
14245         switch (gameMode) {
14246           default:
14247             return;
14248           case MachinePlaysWhite:
14249             if (whiteFlag) {
14250                 if (blackFlag)
14251                   GameEnds(GameIsDrawn, "Both players ran out of time",
14252                            GE_PLAYER);
14253                 else
14254                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14255             } else {
14256                 DisplayError(_("Your opponent is not out of time"), 0);
14257             }
14258             break;
14259           case MachinePlaysBlack:
14260             if (blackFlag) {
14261                 if (whiteFlag)
14262                   GameEnds(GameIsDrawn, "Both players ran out of time",
14263                            GE_PLAYER);
14264                 else
14265                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14266             } else {
14267                 DisplayError(_("Your opponent is not out of time"), 0);
14268             }
14269             break;
14270         }
14271     }
14272 }
14273
14274 void
14275 ClockClick (int which)
14276 {       // [HGM] code moved to back-end from winboard.c
14277         if(which) { // black clock
14278           if (gameMode == EditPosition || gameMode == IcsExamining) {
14279             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14280             SetBlackToPlayEvent();
14281           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14282           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14283           } else if (shiftKey) {
14284             AdjustClock(which, -1);
14285           } else if (gameMode == IcsPlayingWhite ||
14286                      gameMode == MachinePlaysBlack) {
14287             CallFlagEvent();
14288           }
14289         } else { // white clock
14290           if (gameMode == EditPosition || gameMode == IcsExamining) {
14291             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14292             SetWhiteToPlayEvent();
14293           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14294           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14295           } else if (shiftKey) {
14296             AdjustClock(which, -1);
14297           } else if (gameMode == IcsPlayingBlack ||
14298                    gameMode == MachinePlaysWhite) {
14299             CallFlagEvent();
14300           }
14301         }
14302 }
14303
14304 void
14305 DrawEvent ()
14306 {
14307     /* Offer draw or accept pending draw offer from opponent */
14308
14309     if (appData.icsActive) {
14310         /* Note: tournament rules require draw offers to be
14311            made after you make your move but before you punch
14312            your clock.  Currently ICS doesn't let you do that;
14313            instead, you immediately punch your clock after making
14314            a move, but you can offer a draw at any time. */
14315
14316         SendToICS(ics_prefix);
14317         SendToICS("draw\n");
14318         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14319     } else if (cmailMsgLoaded) {
14320         if (currentMove == cmailOldMove &&
14321             commentList[cmailOldMove] != NULL &&
14322             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14323                    "Black offers a draw" : "White offers a draw")) {
14324             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14325             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14326         } else if (currentMove == cmailOldMove + 1) {
14327             char *offer = WhiteOnMove(cmailOldMove) ?
14328               "White offers a draw" : "Black offers a draw";
14329             AppendComment(currentMove, offer, TRUE);
14330             DisplayComment(currentMove - 1, offer);
14331             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14332         } else {
14333             DisplayError(_("You must make your move before offering a draw"), 0);
14334             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14335         }
14336     } else if (first.offeredDraw) {
14337         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14338     } else {
14339         if (first.sendDrawOffers) {
14340             SendToProgram("draw\n", &first);
14341             userOfferedDraw = TRUE;
14342         }
14343     }
14344 }
14345
14346 void
14347 AdjournEvent ()
14348 {
14349     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14350
14351     if (appData.icsActive) {
14352         SendToICS(ics_prefix);
14353         SendToICS("adjourn\n");
14354     } else {
14355         /* Currently GNU Chess doesn't offer or accept Adjourns */
14356     }
14357 }
14358
14359
14360 void
14361 AbortEvent ()
14362 {
14363     /* Offer Abort or accept pending Abort offer from opponent */
14364
14365     if (appData.icsActive) {
14366         SendToICS(ics_prefix);
14367         SendToICS("abort\n");
14368     } else {
14369         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14370     }
14371 }
14372
14373 void
14374 ResignEvent ()
14375 {
14376     /* Resign.  You can do this even if it's not your turn. */
14377
14378     if (appData.icsActive) {
14379         SendToICS(ics_prefix);
14380         SendToICS("resign\n");
14381     } else {
14382         switch (gameMode) {
14383           case MachinePlaysWhite:
14384             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14385             break;
14386           case MachinePlaysBlack:
14387             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14388             break;
14389           case EditGame:
14390             if (cmailMsgLoaded) {
14391                 TruncateGame();
14392                 if (WhiteOnMove(cmailOldMove)) {
14393                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14394                 } else {
14395                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14396                 }
14397                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14398             }
14399             break;
14400           default:
14401             break;
14402         }
14403     }
14404 }
14405
14406
14407 void
14408 StopObservingEvent ()
14409 {
14410     /* Stop observing current games */
14411     SendToICS(ics_prefix);
14412     SendToICS("unobserve\n");
14413 }
14414
14415 void
14416 StopExaminingEvent ()
14417 {
14418     /* Stop observing current game */
14419     SendToICS(ics_prefix);
14420     SendToICS("unexamine\n");
14421 }
14422
14423 void
14424 ForwardInner (int target)
14425 {
14426     int limit; int oldSeekGraphUp = seekGraphUp;
14427
14428     if (appData.debugMode)
14429         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14430                 target, currentMove, forwardMostMove);
14431
14432     if (gameMode == EditPosition)
14433       return;
14434
14435     seekGraphUp = FALSE;
14436     MarkTargetSquares(1);
14437
14438     if (gameMode == PlayFromGameFile && !pausing)
14439       PauseEvent();
14440
14441     if (gameMode == IcsExamining && pausing)
14442       limit = pauseExamForwardMostMove;
14443     else
14444       limit = forwardMostMove;
14445
14446     if (target > limit) target = limit;
14447
14448     if (target > 0 && moveList[target - 1][0]) {
14449         int fromX, fromY, toX, toY;
14450         toX = moveList[target - 1][2] - AAA;
14451         toY = moveList[target - 1][3] - ONE;
14452         if (moveList[target - 1][1] == '@') {
14453             if (appData.highlightLastMove) {
14454                 SetHighlights(-1, -1, toX, toY);
14455             }
14456         } else {
14457             fromX = moveList[target - 1][0] - AAA;
14458             fromY = moveList[target - 1][1] - ONE;
14459             if (target == currentMove + 1) {
14460                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14461             }
14462             if (appData.highlightLastMove) {
14463                 SetHighlights(fromX, fromY, toX, toY);
14464             }
14465         }
14466     }
14467     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14468         gameMode == Training || gameMode == PlayFromGameFile ||
14469         gameMode == AnalyzeFile) {
14470         while (currentMove < target) {
14471             SendMoveToProgram(currentMove++, &first);
14472         }
14473     } else {
14474         currentMove = target;
14475     }
14476
14477     if (gameMode == EditGame || gameMode == EndOfGame) {
14478         whiteTimeRemaining = timeRemaining[0][currentMove];
14479         blackTimeRemaining = timeRemaining[1][currentMove];
14480     }
14481     DisplayBothClocks();
14482     DisplayMove(currentMove - 1);
14483     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14484     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14485     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14486         DisplayComment(currentMove - 1, commentList[currentMove]);
14487     }
14488     ClearMap(); // [HGM] exclude: invalidate map
14489 }
14490
14491
14492 void
14493 ForwardEvent ()
14494 {
14495     if (gameMode == IcsExamining && !pausing) {
14496         SendToICS(ics_prefix);
14497         SendToICS("forward\n");
14498     } else {
14499         ForwardInner(currentMove + 1);
14500     }
14501 }
14502
14503 void
14504 ToEndEvent ()
14505 {
14506     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14507         /* to optimze, we temporarily turn off analysis mode while we feed
14508          * the remaining moves to the engine. Otherwise we get analysis output
14509          * after each move.
14510          */
14511         if (first.analysisSupport) {
14512           SendToProgram("exit\nforce\n", &first);
14513           first.analyzing = FALSE;
14514         }
14515     }
14516
14517     if (gameMode == IcsExamining && !pausing) {
14518         SendToICS(ics_prefix);
14519         SendToICS("forward 999999\n");
14520     } else {
14521         ForwardInner(forwardMostMove);
14522     }
14523
14524     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14525         /* we have fed all the moves, so reactivate analysis mode */
14526         SendToProgram("analyze\n", &first);
14527         first.analyzing = TRUE;
14528         /*first.maybeThinking = TRUE;*/
14529         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14530     }
14531 }
14532
14533 void
14534 BackwardInner (int target)
14535 {
14536     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14537
14538     if (appData.debugMode)
14539         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14540                 target, currentMove, forwardMostMove);
14541
14542     if (gameMode == EditPosition) return;
14543     seekGraphUp = FALSE;
14544     MarkTargetSquares(1);
14545     if (currentMove <= backwardMostMove) {
14546         ClearHighlights();
14547         DrawPosition(full_redraw, boards[currentMove]);
14548         return;
14549     }
14550     if (gameMode == PlayFromGameFile && !pausing)
14551       PauseEvent();
14552
14553     if (moveList[target][0]) {
14554         int fromX, fromY, toX, toY;
14555         toX = moveList[target][2] - AAA;
14556         toY = moveList[target][3] - ONE;
14557         if (moveList[target][1] == '@') {
14558             if (appData.highlightLastMove) {
14559                 SetHighlights(-1, -1, toX, toY);
14560             }
14561         } else {
14562             fromX = moveList[target][0] - AAA;
14563             fromY = moveList[target][1] - ONE;
14564             if (target == currentMove - 1) {
14565                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14566             }
14567             if (appData.highlightLastMove) {
14568                 SetHighlights(fromX, fromY, toX, toY);
14569             }
14570         }
14571     }
14572     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14573         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14574         while (currentMove > target) {
14575             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14576                 // null move cannot be undone. Reload program with move history before it.
14577                 int i;
14578                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14579                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14580                 }
14581                 SendBoard(&first, i); 
14582                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14583                 break;
14584             }
14585             SendToProgram("undo\n", &first);
14586             currentMove--;
14587         }
14588     } else {
14589         currentMove = target;
14590     }
14591
14592     if (gameMode == EditGame || gameMode == EndOfGame) {
14593         whiteTimeRemaining = timeRemaining[0][currentMove];
14594         blackTimeRemaining = timeRemaining[1][currentMove];
14595     }
14596     DisplayBothClocks();
14597     DisplayMove(currentMove - 1);
14598     DrawPosition(full_redraw, boards[currentMove]);
14599     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14600     // [HGM] PV info: routine tests if comment empty
14601     DisplayComment(currentMove - 1, commentList[currentMove]);
14602     ClearMap(); // [HGM] exclude: invalidate map
14603 }
14604
14605 void
14606 BackwardEvent ()
14607 {
14608     if (gameMode == IcsExamining && !pausing) {
14609         SendToICS(ics_prefix);
14610         SendToICS("backward\n");
14611     } else {
14612         BackwardInner(currentMove - 1);
14613     }
14614 }
14615
14616 void
14617 ToStartEvent ()
14618 {
14619     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14620         /* to optimize, we temporarily turn off analysis mode while we undo
14621          * all the moves. Otherwise we get analysis output after each undo.
14622          */
14623         if (first.analysisSupport) {
14624           SendToProgram("exit\nforce\n", &first);
14625           first.analyzing = FALSE;
14626         }
14627     }
14628
14629     if (gameMode == IcsExamining && !pausing) {
14630         SendToICS(ics_prefix);
14631         SendToICS("backward 999999\n");
14632     } else {
14633         BackwardInner(backwardMostMove);
14634     }
14635
14636     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14637         /* we have fed all the moves, so reactivate analysis mode */
14638         SendToProgram("analyze\n", &first);
14639         first.analyzing = TRUE;
14640         /*first.maybeThinking = TRUE;*/
14641         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14642     }
14643 }
14644
14645 void
14646 ToNrEvent (int to)
14647 {
14648   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14649   if (to >= forwardMostMove) to = forwardMostMove;
14650   if (to <= backwardMostMove) to = backwardMostMove;
14651   if (to < currentMove) {
14652     BackwardInner(to);
14653   } else {
14654     ForwardInner(to);
14655   }
14656 }
14657
14658 void
14659 RevertEvent (Boolean annotate)
14660 {
14661     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14662         return;
14663     }
14664     if (gameMode != IcsExamining) {
14665         DisplayError(_("You are not examining a game"), 0);
14666         return;
14667     }
14668     if (pausing) {
14669         DisplayError(_("You can't revert while pausing"), 0);
14670         return;
14671     }
14672     SendToICS(ics_prefix);
14673     SendToICS("revert\n");
14674 }
14675
14676 void
14677 RetractMoveEvent ()
14678 {
14679     switch (gameMode) {
14680       case MachinePlaysWhite:
14681       case MachinePlaysBlack:
14682         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14683             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14684             return;
14685         }
14686         if (forwardMostMove < 2) return;
14687         currentMove = forwardMostMove = forwardMostMove - 2;
14688         whiteTimeRemaining = timeRemaining[0][currentMove];
14689         blackTimeRemaining = timeRemaining[1][currentMove];
14690         DisplayBothClocks();
14691         DisplayMove(currentMove - 1);
14692         ClearHighlights();/*!! could figure this out*/
14693         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14694         SendToProgram("remove\n", &first);
14695         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14696         break;
14697
14698       case BeginningOfGame:
14699       default:
14700         break;
14701
14702       case IcsPlayingWhite:
14703       case IcsPlayingBlack:
14704         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14705             SendToICS(ics_prefix);
14706             SendToICS("takeback 2\n");
14707         } else {
14708             SendToICS(ics_prefix);
14709             SendToICS("takeback 1\n");
14710         }
14711         break;
14712     }
14713 }
14714
14715 void
14716 MoveNowEvent ()
14717 {
14718     ChessProgramState *cps;
14719
14720     switch (gameMode) {
14721       case MachinePlaysWhite:
14722         if (!WhiteOnMove(forwardMostMove)) {
14723             DisplayError(_("It is your turn"), 0);
14724             return;
14725         }
14726         cps = &first;
14727         break;
14728       case MachinePlaysBlack:
14729         if (WhiteOnMove(forwardMostMove)) {
14730             DisplayError(_("It is your turn"), 0);
14731             return;
14732         }
14733         cps = &first;
14734         break;
14735       case TwoMachinesPlay:
14736         if (WhiteOnMove(forwardMostMove) ==
14737             (first.twoMachinesColor[0] == 'w')) {
14738             cps = &first;
14739         } else {
14740             cps = &second;
14741         }
14742         break;
14743       case BeginningOfGame:
14744       default:
14745         return;
14746     }
14747     SendToProgram("?\n", cps);
14748 }
14749
14750 void
14751 TruncateGameEvent ()
14752 {
14753     EditGameEvent();
14754     if (gameMode != EditGame) return;
14755     TruncateGame();
14756 }
14757
14758 void
14759 TruncateGame ()
14760 {
14761     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14762     if (forwardMostMove > currentMove) {
14763         if (gameInfo.resultDetails != NULL) {
14764             free(gameInfo.resultDetails);
14765             gameInfo.resultDetails = NULL;
14766             gameInfo.result = GameUnfinished;
14767         }
14768         forwardMostMove = currentMove;
14769         HistorySet(parseList, backwardMostMove, forwardMostMove,
14770                    currentMove-1);
14771     }
14772 }
14773
14774 void
14775 HintEvent ()
14776 {
14777     if (appData.noChessProgram) return;
14778     switch (gameMode) {
14779       case MachinePlaysWhite:
14780         if (WhiteOnMove(forwardMostMove)) {
14781             DisplayError(_("Wait until your turn"), 0);
14782             return;
14783         }
14784         break;
14785       case BeginningOfGame:
14786       case MachinePlaysBlack:
14787         if (!WhiteOnMove(forwardMostMove)) {
14788             DisplayError(_("Wait until your turn"), 0);
14789             return;
14790         }
14791         break;
14792       default:
14793         DisplayError(_("No hint available"), 0);
14794         return;
14795     }
14796     SendToProgram("hint\n", &first);
14797     hintRequested = TRUE;
14798 }
14799
14800 void
14801 BookEvent ()
14802 {
14803     if (appData.noChessProgram) return;
14804     switch (gameMode) {
14805       case MachinePlaysWhite:
14806         if (WhiteOnMove(forwardMostMove)) {
14807             DisplayError(_("Wait until your turn"), 0);
14808             return;
14809         }
14810         break;
14811       case BeginningOfGame:
14812       case MachinePlaysBlack:
14813         if (!WhiteOnMove(forwardMostMove)) {
14814             DisplayError(_("Wait until your turn"), 0);
14815             return;
14816         }
14817         break;
14818       case EditPosition:
14819         EditPositionDone(TRUE);
14820         break;
14821       case TwoMachinesPlay:
14822         return;
14823       default:
14824         break;
14825     }
14826     SendToProgram("bk\n", &first);
14827     bookOutput[0] = NULLCHAR;
14828     bookRequested = TRUE;
14829 }
14830
14831 void
14832 AboutGameEvent ()
14833 {
14834     char *tags = PGNTags(&gameInfo);
14835     TagsPopUp(tags, CmailMsg());
14836     free(tags);
14837 }
14838
14839 /* end button procedures */
14840
14841 void
14842 PrintPosition (FILE *fp, int move)
14843 {
14844     int i, j;
14845
14846     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14847         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14848             char c = PieceToChar(boards[move][i][j]);
14849             fputc(c == 'x' ? '.' : c, fp);
14850             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14851         }
14852     }
14853     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14854       fprintf(fp, "white to play\n");
14855     else
14856       fprintf(fp, "black to play\n");
14857 }
14858
14859 void
14860 PrintOpponents (FILE *fp)
14861 {
14862     if (gameInfo.white != NULL) {
14863         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14864     } else {
14865         fprintf(fp, "\n");
14866     }
14867 }
14868
14869 /* Find last component of program's own name, using some heuristics */
14870 void
14871 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14872 {
14873     char *p, *q, c;
14874     int local = (strcmp(host, "localhost") == 0);
14875     while (!local && (p = strchr(prog, ';')) != NULL) {
14876         p++;
14877         while (*p == ' ') p++;
14878         prog = p;
14879     }
14880     if (*prog == '"' || *prog == '\'') {
14881         q = strchr(prog + 1, *prog);
14882     } else {
14883         q = strchr(prog, ' ');
14884     }
14885     if (q == NULL) q = prog + strlen(prog);
14886     p = q;
14887     while (p >= prog && *p != '/' && *p != '\\') p--;
14888     p++;
14889     if(p == prog && *p == '"') p++;
14890     c = *q; *q = 0;
14891     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14892     memcpy(buf, p, q - p);
14893     buf[q - p] = NULLCHAR;
14894     if (!local) {
14895         strcat(buf, "@");
14896         strcat(buf, host);
14897     }
14898 }
14899
14900 char *
14901 TimeControlTagValue ()
14902 {
14903     char buf[MSG_SIZ];
14904     if (!appData.clockMode) {
14905       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14906     } else if (movesPerSession > 0) {
14907       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14908     } else if (timeIncrement == 0) {
14909       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14910     } else {
14911       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14912     }
14913     return StrSave(buf);
14914 }
14915
14916 void
14917 SetGameInfo ()
14918 {
14919     /* This routine is used only for certain modes */
14920     VariantClass v = gameInfo.variant;
14921     ChessMove r = GameUnfinished;
14922     char *p = NULL;
14923
14924     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14925         r = gameInfo.result;
14926         p = gameInfo.resultDetails;
14927         gameInfo.resultDetails = NULL;
14928     }
14929     ClearGameInfo(&gameInfo);
14930     gameInfo.variant = v;
14931
14932     switch (gameMode) {
14933       case MachinePlaysWhite:
14934         gameInfo.event = StrSave( appData.pgnEventHeader );
14935         gameInfo.site = StrSave(HostName());
14936         gameInfo.date = PGNDate();
14937         gameInfo.round = StrSave("-");
14938         gameInfo.white = StrSave(first.tidy);
14939         gameInfo.black = StrSave(UserName());
14940         gameInfo.timeControl = TimeControlTagValue();
14941         break;
14942
14943       case MachinePlaysBlack:
14944         gameInfo.event = StrSave( appData.pgnEventHeader );
14945         gameInfo.site = StrSave(HostName());
14946         gameInfo.date = PGNDate();
14947         gameInfo.round = StrSave("-");
14948         gameInfo.white = StrSave(UserName());
14949         gameInfo.black = StrSave(first.tidy);
14950         gameInfo.timeControl = TimeControlTagValue();
14951         break;
14952
14953       case TwoMachinesPlay:
14954         gameInfo.event = StrSave( appData.pgnEventHeader );
14955         gameInfo.site = StrSave(HostName());
14956         gameInfo.date = PGNDate();
14957         if (roundNr > 0) {
14958             char buf[MSG_SIZ];
14959             snprintf(buf, MSG_SIZ, "%d", roundNr);
14960             gameInfo.round = StrSave(buf);
14961         } else {
14962             gameInfo.round = StrSave("-");
14963         }
14964         if (first.twoMachinesColor[0] == 'w') {
14965             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14966             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14967         } else {
14968             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14969             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14970         }
14971         gameInfo.timeControl = TimeControlTagValue();
14972         break;
14973
14974       case EditGame:
14975         gameInfo.event = StrSave("Edited game");
14976         gameInfo.site = StrSave(HostName());
14977         gameInfo.date = PGNDate();
14978         gameInfo.round = StrSave("-");
14979         gameInfo.white = StrSave("-");
14980         gameInfo.black = StrSave("-");
14981         gameInfo.result = r;
14982         gameInfo.resultDetails = p;
14983         break;
14984
14985       case EditPosition:
14986         gameInfo.event = StrSave("Edited position");
14987         gameInfo.site = StrSave(HostName());
14988         gameInfo.date = PGNDate();
14989         gameInfo.round = StrSave("-");
14990         gameInfo.white = StrSave("-");
14991         gameInfo.black = StrSave("-");
14992         break;
14993
14994       case IcsPlayingWhite:
14995       case IcsPlayingBlack:
14996       case IcsObserving:
14997       case IcsExamining:
14998         break;
14999
15000       case PlayFromGameFile:
15001         gameInfo.event = StrSave("Game from non-PGN file");
15002         gameInfo.site = StrSave(HostName());
15003         gameInfo.date = PGNDate();
15004         gameInfo.round = StrSave("-");
15005         gameInfo.white = StrSave("?");
15006         gameInfo.black = StrSave("?");
15007         break;
15008
15009       default:
15010         break;
15011     }
15012 }
15013
15014 void
15015 ReplaceComment (int index, char *text)
15016 {
15017     int len;
15018     char *p;
15019     float score;
15020
15021     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15022        pvInfoList[index-1].depth == len &&
15023        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15024        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15025     while (*text == '\n') text++;
15026     len = strlen(text);
15027     while (len > 0 && text[len - 1] == '\n') len--;
15028
15029     if (commentList[index] != NULL)
15030       free(commentList[index]);
15031
15032     if (len == 0) {
15033         commentList[index] = NULL;
15034         return;
15035     }
15036   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15037       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15038       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15039     commentList[index] = (char *) malloc(len + 2);
15040     strncpy(commentList[index], text, len);
15041     commentList[index][len] = '\n';
15042     commentList[index][len + 1] = NULLCHAR;
15043   } else {
15044     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15045     char *p;
15046     commentList[index] = (char *) malloc(len + 7);
15047     safeStrCpy(commentList[index], "{\n", 3);
15048     safeStrCpy(commentList[index]+2, text, len+1);
15049     commentList[index][len+2] = NULLCHAR;
15050     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15051     strcat(commentList[index], "\n}\n");
15052   }
15053 }
15054
15055 void
15056 CrushCRs (char *text)
15057 {
15058   char *p = text;
15059   char *q = text;
15060   char ch;
15061
15062   do {
15063     ch = *p++;
15064     if (ch == '\r') continue;
15065     *q++ = ch;
15066   } while (ch != '\0');
15067 }
15068
15069 void
15070 AppendComment (int index, char *text, Boolean addBraces)
15071 /* addBraces  tells if we should add {} */
15072 {
15073     int oldlen, len;
15074     char *old;
15075
15076 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15077     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15078
15079     CrushCRs(text);
15080     while (*text == '\n') text++;
15081     len = strlen(text);
15082     while (len > 0 && text[len - 1] == '\n') len--;
15083     text[len] = NULLCHAR;
15084
15085     if (len == 0) return;
15086
15087     if (commentList[index] != NULL) {
15088       Boolean addClosingBrace = addBraces;
15089         old = commentList[index];
15090         oldlen = strlen(old);
15091         while(commentList[index][oldlen-1] ==  '\n')
15092           commentList[index][--oldlen] = NULLCHAR;
15093         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15094         safeStrCpy(commentList[index], old, oldlen + len + 6);
15095         free(old);
15096         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15097         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15098           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15099           while (*text == '\n') { text++; len--; }
15100           commentList[index][--oldlen] = NULLCHAR;
15101       }
15102         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15103         else          strcat(commentList[index], "\n");
15104         strcat(commentList[index], text);
15105         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15106         else          strcat(commentList[index], "\n");
15107     } else {
15108         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15109         if(addBraces)
15110           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15111         else commentList[index][0] = NULLCHAR;
15112         strcat(commentList[index], text);
15113         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15114         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15115     }
15116 }
15117
15118 static char *
15119 FindStr (char * text, char * sub_text)
15120 {
15121     char * result = strstr( text, sub_text );
15122
15123     if( result != NULL ) {
15124         result += strlen( sub_text );
15125     }
15126
15127     return result;
15128 }
15129
15130 /* [AS] Try to extract PV info from PGN comment */
15131 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15132 char *
15133 GetInfoFromComment (int index, char * text)
15134 {
15135     char * sep = text, *p;
15136
15137     if( text != NULL && index > 0 ) {
15138         int score = 0;
15139         int depth = 0;
15140         int time = -1, sec = 0, deci;
15141         char * s_eval = FindStr( text, "[%eval " );
15142         char * s_emt = FindStr( text, "[%emt " );
15143
15144         if( s_eval != NULL || s_emt != NULL ) {
15145             /* New style */
15146             char delim;
15147
15148             if( s_eval != NULL ) {
15149                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15150                     return text;
15151                 }
15152
15153                 if( delim != ']' ) {
15154                     return text;
15155                 }
15156             }
15157
15158             if( s_emt != NULL ) {
15159             }
15160                 return text;
15161         }
15162         else {
15163             /* We expect something like: [+|-]nnn.nn/dd */
15164             int score_lo = 0;
15165
15166             if(*text != '{') return text; // [HGM] braces: must be normal comment
15167
15168             sep = strchr( text, '/' );
15169             if( sep == NULL || sep < (text+4) ) {
15170                 return text;
15171             }
15172
15173             p = text;
15174             if(p[1] == '(') { // comment starts with PV
15175                p = strchr(p, ')'); // locate end of PV
15176                if(p == NULL || sep < p+5) return text;
15177                // at this point we have something like "{(.*) +0.23/6 ..."
15178                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15179                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15180                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15181             }
15182             time = -1; sec = -1; deci = -1;
15183             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15184                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15185                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15186                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15187                 return text;
15188             }
15189
15190             if( score_lo < 0 || score_lo >= 100 ) {
15191                 return text;
15192             }
15193
15194             if(sec >= 0) time = 600*time + 10*sec; else
15195             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15196
15197             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15198
15199             /* [HGM] PV time: now locate end of PV info */
15200             while( *++sep >= '0' && *sep <= '9'); // strip depth
15201             if(time >= 0)
15202             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15203             if(sec >= 0)
15204             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15205             if(deci >= 0)
15206             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15207             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15208         }
15209
15210         if( depth <= 0 ) {
15211             return text;
15212         }
15213
15214         if( time < 0 ) {
15215             time = -1;
15216         }
15217
15218         pvInfoList[index-1].depth = depth;
15219         pvInfoList[index-1].score = score;
15220         pvInfoList[index-1].time  = 10*time; // centi-sec
15221         if(*sep == '}') *sep = 0; else *--sep = '{';
15222         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15223     }
15224     return sep;
15225 }
15226
15227 void
15228 SendToProgram (char *message, ChessProgramState *cps)
15229 {
15230     int count, outCount, error;
15231     char buf[MSG_SIZ];
15232
15233     if (cps->pr == NoProc) return;
15234     Attention(cps);
15235
15236     if (appData.debugMode) {
15237         TimeMark now;
15238         GetTimeMark(&now);
15239         fprintf(debugFP, "%ld >%-6s: %s",
15240                 SubtractTimeMarks(&now, &programStartTime),
15241                 cps->which, message);
15242         if(serverFP)
15243             fprintf(serverFP, "%ld >%-6s: %s",
15244                 SubtractTimeMarks(&now, &programStartTime),
15245                 cps->which, message), fflush(serverFP);
15246     }
15247
15248     count = strlen(message);
15249     outCount = OutputToProcess(cps->pr, message, count, &error);
15250     if (outCount < count && !exiting
15251                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15252       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15253       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15254         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15255             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15256                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15257                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15258                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15259             } else {
15260                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15261                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15262                 gameInfo.result = res;
15263             }
15264             gameInfo.resultDetails = StrSave(buf);
15265         }
15266         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15267         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15268     }
15269 }
15270
15271 void
15272 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15273 {
15274     char *end_str;
15275     char buf[MSG_SIZ];
15276     ChessProgramState *cps = (ChessProgramState *)closure;
15277
15278     if (isr != cps->isr) return; /* Killed intentionally */
15279     if (count <= 0) {
15280         if (count == 0) {
15281             RemoveInputSource(cps->isr);
15282             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15283                     _(cps->which), cps->program);
15284             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15285             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15286                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15287                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15288                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15289                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15290                 } else {
15291                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15292                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15293                     gameInfo.result = res;
15294                 }
15295                 gameInfo.resultDetails = StrSave(buf);
15296             }
15297             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15298             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15299         } else {
15300             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15301                     _(cps->which), cps->program);
15302             RemoveInputSource(cps->isr);
15303
15304             /* [AS] Program is misbehaving badly... kill it */
15305             if( count == -2 ) {
15306                 DestroyChildProcess( cps->pr, 9 );
15307                 cps->pr = NoProc;
15308             }
15309
15310             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15311         }
15312         return;
15313     }
15314
15315     if ((end_str = strchr(message, '\r')) != NULL)
15316       *end_str = NULLCHAR;
15317     if ((end_str = strchr(message, '\n')) != NULL)
15318       *end_str = NULLCHAR;
15319
15320     if (appData.debugMode) {
15321         TimeMark now; int print = 1;
15322         char *quote = ""; char c; int i;
15323
15324         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15325                 char start = message[0];
15326                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15327                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15328                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15329                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15330                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15331                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15332                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15333                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15334                    sscanf(message, "hint: %c", &c)!=1 && 
15335                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15336                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15337                     print = (appData.engineComments >= 2);
15338                 }
15339                 message[0] = start; // restore original message
15340         }
15341         if(print) {
15342                 GetTimeMark(&now);
15343                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15344                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15345                         quote,
15346                         message);
15347                 if(serverFP)
15348                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15349                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15350                         quote,
15351                         message), fflush(serverFP);
15352         }
15353     }
15354
15355     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15356     if (appData.icsEngineAnalyze) {
15357         if (strstr(message, "whisper") != NULL ||
15358              strstr(message, "kibitz") != NULL ||
15359             strstr(message, "tellics") != NULL) return;
15360     }
15361
15362     HandleMachineMove(message, cps);
15363 }
15364
15365
15366 void
15367 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15368 {
15369     char buf[MSG_SIZ];
15370     int seconds;
15371
15372     if( timeControl_2 > 0 ) {
15373         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15374             tc = timeControl_2;
15375         }
15376     }
15377     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15378     inc /= cps->timeOdds;
15379     st  /= cps->timeOdds;
15380
15381     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15382
15383     if (st > 0) {
15384       /* Set exact time per move, normally using st command */
15385       if (cps->stKludge) {
15386         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15387         seconds = st % 60;
15388         if (seconds == 0) {
15389           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15390         } else {
15391           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15392         }
15393       } else {
15394         snprintf(buf, MSG_SIZ, "st %d\n", st);
15395       }
15396     } else {
15397       /* Set conventional or incremental time control, using level command */
15398       if (seconds == 0) {
15399         /* Note old gnuchess bug -- minutes:seconds used to not work.
15400            Fixed in later versions, but still avoid :seconds
15401            when seconds is 0. */
15402         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15403       } else {
15404         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15405                  seconds, inc/1000.);
15406       }
15407     }
15408     SendToProgram(buf, cps);
15409
15410     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15411     /* Orthogonally, limit search to given depth */
15412     if (sd > 0) {
15413       if (cps->sdKludge) {
15414         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15415       } else {
15416         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15417       }
15418       SendToProgram(buf, cps);
15419     }
15420
15421     if(cps->nps >= 0) { /* [HGM] nps */
15422         if(cps->supportsNPS == FALSE)
15423           cps->nps = -1; // don't use if engine explicitly says not supported!
15424         else {
15425           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15426           SendToProgram(buf, cps);
15427         }
15428     }
15429 }
15430
15431 ChessProgramState *
15432 WhitePlayer ()
15433 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15434 {
15435     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15436        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15437         return &second;
15438     return &first;
15439 }
15440
15441 void
15442 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15443 {
15444     char message[MSG_SIZ];
15445     long time, otime;
15446
15447     /* Note: this routine must be called when the clocks are stopped
15448        or when they have *just* been set or switched; otherwise
15449        it will be off by the time since the current tick started.
15450     */
15451     if (machineWhite) {
15452         time = whiteTimeRemaining / 10;
15453         otime = blackTimeRemaining / 10;
15454     } else {
15455         time = blackTimeRemaining / 10;
15456         otime = whiteTimeRemaining / 10;
15457     }
15458     /* [HGM] translate opponent's time by time-odds factor */
15459     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15460
15461     if (time <= 0) time = 1;
15462     if (otime <= 0) otime = 1;
15463
15464     snprintf(message, MSG_SIZ, "time %ld\n", time);
15465     SendToProgram(message, cps);
15466
15467     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15468     SendToProgram(message, cps);
15469 }
15470
15471 int
15472 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15473 {
15474   char buf[MSG_SIZ];
15475   int len = strlen(name);
15476   int val;
15477
15478   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15479     (*p) += len + 1;
15480     sscanf(*p, "%d", &val);
15481     *loc = (val != 0);
15482     while (**p && **p != ' ')
15483       (*p)++;
15484     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15485     SendToProgram(buf, cps);
15486     return TRUE;
15487   }
15488   return FALSE;
15489 }
15490
15491 int
15492 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15493 {
15494   char buf[MSG_SIZ];
15495   int len = strlen(name);
15496   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15497     (*p) += len + 1;
15498     sscanf(*p, "%d", loc);
15499     while (**p && **p != ' ') (*p)++;
15500     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15501     SendToProgram(buf, cps);
15502     return TRUE;
15503   }
15504   return FALSE;
15505 }
15506
15507 int
15508 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15509 {
15510   char buf[MSG_SIZ];
15511   int len = strlen(name);
15512   if (strncmp((*p), name, len) == 0
15513       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15514     (*p) += len + 2;
15515     sscanf(*p, "%[^\"]", loc);
15516     while (**p && **p != '\"') (*p)++;
15517     if (**p == '\"') (*p)++;
15518     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15519     SendToProgram(buf, cps);
15520     return TRUE;
15521   }
15522   return FALSE;
15523 }
15524
15525 int
15526 ParseOption (Option *opt, ChessProgramState *cps)
15527 // [HGM] options: process the string that defines an engine option, and determine
15528 // name, type, default value, and allowed value range
15529 {
15530         char *p, *q, buf[MSG_SIZ];
15531         int n, min = (-1)<<31, max = 1<<31, def;
15532
15533         if(p = strstr(opt->name, " -spin ")) {
15534             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15535             if(max < min) max = min; // enforce consistency
15536             if(def < min) def = min;
15537             if(def > max) def = max;
15538             opt->value = def;
15539             opt->min = min;
15540             opt->max = max;
15541             opt->type = Spin;
15542         } else if((p = strstr(opt->name, " -slider "))) {
15543             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15544             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15545             if(max < min) max = min; // enforce consistency
15546             if(def < min) def = min;
15547             if(def > max) def = max;
15548             opt->value = def;
15549             opt->min = min;
15550             opt->max = max;
15551             opt->type = Spin; // Slider;
15552         } else if((p = strstr(opt->name, " -string "))) {
15553             opt->textValue = p+9;
15554             opt->type = TextBox;
15555         } else if((p = strstr(opt->name, " -file "))) {
15556             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15557             opt->textValue = p+7;
15558             opt->type = FileName; // FileName;
15559         } else if((p = strstr(opt->name, " -path "))) {
15560             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15561             opt->textValue = p+7;
15562             opt->type = PathName; // PathName;
15563         } else if(p = strstr(opt->name, " -check ")) {
15564             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15565             opt->value = (def != 0);
15566             opt->type = CheckBox;
15567         } else if(p = strstr(opt->name, " -combo ")) {
15568             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15569             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15570             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15571             opt->value = n = 0;
15572             while(q = StrStr(q, " /// ")) {
15573                 n++; *q = 0;    // count choices, and null-terminate each of them
15574                 q += 5;
15575                 if(*q == '*') { // remember default, which is marked with * prefix
15576                     q++;
15577                     opt->value = n;
15578                 }
15579                 cps->comboList[cps->comboCnt++] = q;
15580             }
15581             cps->comboList[cps->comboCnt++] = NULL;
15582             opt->max = n + 1;
15583             opt->type = ComboBox;
15584         } else if(p = strstr(opt->name, " -button")) {
15585             opt->type = Button;
15586         } else if(p = strstr(opt->name, " -save")) {
15587             opt->type = SaveButton;
15588         } else return FALSE;
15589         *p = 0; // terminate option name
15590         // now look if the command-line options define a setting for this engine option.
15591         if(cps->optionSettings && cps->optionSettings[0])
15592             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15593         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15594           snprintf(buf, MSG_SIZ, "option %s", p);
15595                 if(p = strstr(buf, ",")) *p = 0;
15596                 if(q = strchr(buf, '=')) switch(opt->type) {
15597                     case ComboBox:
15598                         for(n=0; n<opt->max; n++)
15599                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15600                         break;
15601                     case TextBox:
15602                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15603                         break;
15604                     case Spin:
15605                     case CheckBox:
15606                         opt->value = atoi(q+1);
15607                     default:
15608                         break;
15609                 }
15610                 strcat(buf, "\n");
15611                 SendToProgram(buf, cps);
15612         }
15613         return TRUE;
15614 }
15615
15616 void
15617 FeatureDone (ChessProgramState *cps, int val)
15618 {
15619   DelayedEventCallback cb = GetDelayedEvent();
15620   if ((cb == InitBackEnd3 && cps == &first) ||
15621       (cb == SettingsMenuIfReady && cps == &second) ||
15622       (cb == LoadEngine) ||
15623       (cb == TwoMachinesEventIfReady)) {
15624     CancelDelayedEvent();
15625     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15626   }
15627   cps->initDone = val;
15628 }
15629
15630 /* Parse feature command from engine */
15631 void
15632 ParseFeatures (char *args, ChessProgramState *cps)
15633 {
15634   char *p = args;
15635   char *q;
15636   int val;
15637   char buf[MSG_SIZ];
15638
15639   for (;;) {
15640     while (*p == ' ') p++;
15641     if (*p == NULLCHAR) return;
15642
15643     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15644     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15645     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15646     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15647     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15648     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15649     if (BoolFeature(&p, "reuse", &val, cps)) {
15650       /* Engine can disable reuse, but can't enable it if user said no */
15651       if (!val) cps->reuse = FALSE;
15652       continue;
15653     }
15654     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15655     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15656       if (gameMode == TwoMachinesPlay) {
15657         DisplayTwoMachinesTitle();
15658       } else {
15659         DisplayTitle("");
15660       }
15661       continue;
15662     }
15663     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15664     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15665     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15666     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15667     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15668     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15669     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15670     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15671     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15672     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15673     if (IntFeature(&p, "done", &val, cps)) {
15674       FeatureDone(cps, val);
15675       continue;
15676     }
15677     /* Added by Tord: */
15678     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15679     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15680     /* End of additions by Tord */
15681
15682     /* [HGM] added features: */
15683     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15684     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15685     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15686     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15687     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15688     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15689     if (StringFeature(&p, "option", buf, cps)) {
15690         FREE(cps->option[cps->nrOptions].name);
15691         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15692         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15693         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15694           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15695             SendToProgram(buf, cps);
15696             continue;
15697         }
15698         if(cps->nrOptions >= MAX_OPTIONS) {
15699             cps->nrOptions--;
15700             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15701             DisplayError(buf, 0);
15702         }
15703         continue;
15704     }
15705     /* End of additions by HGM */
15706
15707     /* unknown feature: complain and skip */
15708     q = p;
15709     while (*q && *q != '=') q++;
15710     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15711     SendToProgram(buf, cps);
15712     p = q;
15713     if (*p == '=') {
15714       p++;
15715       if (*p == '\"') {
15716         p++;
15717         while (*p && *p != '\"') p++;
15718         if (*p == '\"') p++;
15719       } else {
15720         while (*p && *p != ' ') p++;
15721       }
15722     }
15723   }
15724
15725 }
15726
15727 void
15728 PeriodicUpdatesEvent (int newState)
15729 {
15730     if (newState == appData.periodicUpdates)
15731       return;
15732
15733     appData.periodicUpdates=newState;
15734
15735     /* Display type changes, so update it now */
15736 //    DisplayAnalysis();
15737
15738     /* Get the ball rolling again... */
15739     if (newState) {
15740         AnalysisPeriodicEvent(1);
15741         StartAnalysisClock();
15742     }
15743 }
15744
15745 void
15746 PonderNextMoveEvent (int newState)
15747 {
15748     if (newState == appData.ponderNextMove) return;
15749     if (gameMode == EditPosition) EditPositionDone(TRUE);
15750     if (newState) {
15751         SendToProgram("hard\n", &first);
15752         if (gameMode == TwoMachinesPlay) {
15753             SendToProgram("hard\n", &second);
15754         }
15755     } else {
15756         SendToProgram("easy\n", &first);
15757         thinkOutput[0] = NULLCHAR;
15758         if (gameMode == TwoMachinesPlay) {
15759             SendToProgram("easy\n", &second);
15760         }
15761     }
15762     appData.ponderNextMove = newState;
15763 }
15764
15765 void
15766 NewSettingEvent (int option, int *feature, char *command, int value)
15767 {
15768     char buf[MSG_SIZ];
15769
15770     if (gameMode == EditPosition) EditPositionDone(TRUE);
15771     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15772     if(feature == NULL || *feature) SendToProgram(buf, &first);
15773     if (gameMode == TwoMachinesPlay) {
15774         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15775     }
15776 }
15777
15778 void
15779 ShowThinkingEvent ()
15780 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15781 {
15782     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15783     int newState = appData.showThinking
15784         // [HGM] thinking: other features now need thinking output as well
15785         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15786
15787     if (oldState == newState) return;
15788     oldState = newState;
15789     if (gameMode == EditPosition) EditPositionDone(TRUE);
15790     if (oldState) {
15791         SendToProgram("post\n", &first);
15792         if (gameMode == TwoMachinesPlay) {
15793             SendToProgram("post\n", &second);
15794         }
15795     } else {
15796         SendToProgram("nopost\n", &first);
15797         thinkOutput[0] = NULLCHAR;
15798         if (gameMode == TwoMachinesPlay) {
15799             SendToProgram("nopost\n", &second);
15800         }
15801     }
15802 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15803 }
15804
15805 void
15806 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15807 {
15808   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15809   if (pr == NoProc) return;
15810   AskQuestion(title, question, replyPrefix, pr);
15811 }
15812
15813 void
15814 TypeInEvent (char firstChar)
15815 {
15816     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15817         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15818         gameMode == AnalyzeMode || gameMode == EditGame || 
15819         gameMode == EditPosition || gameMode == IcsExamining ||
15820         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15821         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15822                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15823                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15824         gameMode == Training) PopUpMoveDialog(firstChar);
15825 }
15826
15827 void
15828 TypeInDoneEvent (char *move)
15829 {
15830         Board board;
15831         int n, fromX, fromY, toX, toY;
15832         char promoChar;
15833         ChessMove moveType;
15834
15835         // [HGM] FENedit
15836         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15837                 EditPositionPasteFEN(move);
15838                 return;
15839         }
15840         // [HGM] movenum: allow move number to be typed in any mode
15841         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15842           ToNrEvent(2*n-1);
15843           return;
15844         }
15845         // undocumented kludge: allow command-line option to be typed in!
15846         // (potentially fatal, and does not implement the effect of the option.)
15847         // should only be used for options that are values on which future decisions will be made,
15848         // and definitely not on options that would be used during initialization.
15849         if(strstr(move, "!!! -") == move) {
15850             ParseArgsFromString(move+4);
15851             return;
15852         }
15853
15854       if (gameMode != EditGame && currentMove != forwardMostMove && 
15855         gameMode != Training) {
15856         DisplayMoveError(_("Displayed move is not current"));
15857       } else {
15858         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15859           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15860         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15861         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15862           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15863           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15864         } else {
15865           DisplayMoveError(_("Could not parse move"));
15866         }
15867       }
15868 }
15869
15870 void
15871 DisplayMove (int moveNumber)
15872 {
15873     char message[MSG_SIZ];
15874     char res[MSG_SIZ];
15875     char cpThinkOutput[MSG_SIZ];
15876
15877     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15878
15879     if (moveNumber == forwardMostMove - 1 ||
15880         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15881
15882         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15883
15884         if (strchr(cpThinkOutput, '\n')) {
15885             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15886         }
15887     } else {
15888         *cpThinkOutput = NULLCHAR;
15889     }
15890
15891     /* [AS] Hide thinking from human user */
15892     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15893         *cpThinkOutput = NULLCHAR;
15894         if( thinkOutput[0] != NULLCHAR ) {
15895             int i;
15896
15897             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15898                 cpThinkOutput[i] = '.';
15899             }
15900             cpThinkOutput[i] = NULLCHAR;
15901             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15902         }
15903     }
15904
15905     if (moveNumber == forwardMostMove - 1 &&
15906         gameInfo.resultDetails != NULL) {
15907         if (gameInfo.resultDetails[0] == NULLCHAR) {
15908           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15909         } else {
15910           snprintf(res, MSG_SIZ, " {%s} %s",
15911                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15912         }
15913     } else {
15914         res[0] = NULLCHAR;
15915     }
15916
15917     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15918         DisplayMessage(res, cpThinkOutput);
15919     } else {
15920       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15921                 WhiteOnMove(moveNumber) ? " " : ".. ",
15922                 parseList[moveNumber], res);
15923         DisplayMessage(message, cpThinkOutput);
15924     }
15925 }
15926
15927 void
15928 DisplayComment (int moveNumber, char *text)
15929 {
15930     char title[MSG_SIZ];
15931
15932     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15933       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15934     } else {
15935       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15936               WhiteOnMove(moveNumber) ? " " : ".. ",
15937               parseList[moveNumber]);
15938     }
15939     if (text != NULL && (appData.autoDisplayComment || commentUp))
15940         CommentPopUp(title, text);
15941 }
15942
15943 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15944  * might be busy thinking or pondering.  It can be omitted if your
15945  * gnuchess is configured to stop thinking immediately on any user
15946  * input.  However, that gnuchess feature depends on the FIONREAD
15947  * ioctl, which does not work properly on some flavors of Unix.
15948  */
15949 void
15950 Attention (ChessProgramState *cps)
15951 {
15952 #if ATTENTION
15953     if (!cps->useSigint) return;
15954     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15955     switch (gameMode) {
15956       case MachinePlaysWhite:
15957       case MachinePlaysBlack:
15958       case TwoMachinesPlay:
15959       case IcsPlayingWhite:
15960       case IcsPlayingBlack:
15961       case AnalyzeMode:
15962       case AnalyzeFile:
15963         /* Skip if we know it isn't thinking */
15964         if (!cps->maybeThinking) return;
15965         if (appData.debugMode)
15966           fprintf(debugFP, "Interrupting %s\n", cps->which);
15967         InterruptChildProcess(cps->pr);
15968         cps->maybeThinking = FALSE;
15969         break;
15970       default:
15971         break;
15972     }
15973 #endif /*ATTENTION*/
15974 }
15975
15976 int
15977 CheckFlags ()
15978 {
15979     if (whiteTimeRemaining <= 0) {
15980         if (!whiteFlag) {
15981             whiteFlag = TRUE;
15982             if (appData.icsActive) {
15983                 if (appData.autoCallFlag &&
15984                     gameMode == IcsPlayingBlack && !blackFlag) {
15985                   SendToICS(ics_prefix);
15986                   SendToICS("flag\n");
15987                 }
15988             } else {
15989                 if (blackFlag) {
15990                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15991                 } else {
15992                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15993                     if (appData.autoCallFlag) {
15994                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15995                         return TRUE;
15996                     }
15997                 }
15998             }
15999         }
16000     }
16001     if (blackTimeRemaining <= 0) {
16002         if (!blackFlag) {
16003             blackFlag = TRUE;
16004             if (appData.icsActive) {
16005                 if (appData.autoCallFlag &&
16006                     gameMode == IcsPlayingWhite && !whiteFlag) {
16007                   SendToICS(ics_prefix);
16008                   SendToICS("flag\n");
16009                 }
16010             } else {
16011                 if (whiteFlag) {
16012                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16013                 } else {
16014                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16015                     if (appData.autoCallFlag) {
16016                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16017                         return TRUE;
16018                     }
16019                 }
16020             }
16021         }
16022     }
16023     return FALSE;
16024 }
16025
16026 void
16027 CheckTimeControl ()
16028 {
16029     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16030         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16031
16032     /*
16033      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16034      */
16035     if ( !WhiteOnMove(forwardMostMove) ) {
16036         /* White made time control */
16037         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16038         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16039         /* [HGM] time odds: correct new time quota for time odds! */
16040                                             / WhitePlayer()->timeOdds;
16041         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16042     } else {
16043         lastBlack -= blackTimeRemaining;
16044         /* Black made time control */
16045         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16046                                             / WhitePlayer()->other->timeOdds;
16047         lastWhite = whiteTimeRemaining;
16048     }
16049 }
16050
16051 void
16052 DisplayBothClocks ()
16053 {
16054     int wom = gameMode == EditPosition ?
16055       !blackPlaysFirst : WhiteOnMove(currentMove);
16056     DisplayWhiteClock(whiteTimeRemaining, wom);
16057     DisplayBlackClock(blackTimeRemaining, !wom);
16058 }
16059
16060
16061 /* Timekeeping seems to be a portability nightmare.  I think everyone
16062    has ftime(), but I'm really not sure, so I'm including some ifdefs
16063    to use other calls if you don't.  Clocks will be less accurate if
16064    you have neither ftime nor gettimeofday.
16065 */
16066
16067 /* VS 2008 requires the #include outside of the function */
16068 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16069 #include <sys/timeb.h>
16070 #endif
16071
16072 /* Get the current time as a TimeMark */
16073 void
16074 GetTimeMark (TimeMark *tm)
16075 {
16076 #if HAVE_GETTIMEOFDAY
16077
16078     struct timeval timeVal;
16079     struct timezone timeZone;
16080
16081     gettimeofday(&timeVal, &timeZone);
16082     tm->sec = (long) timeVal.tv_sec;
16083     tm->ms = (int) (timeVal.tv_usec / 1000L);
16084
16085 #else /*!HAVE_GETTIMEOFDAY*/
16086 #if HAVE_FTIME
16087
16088 // include <sys/timeb.h> / moved to just above start of function
16089     struct timeb timeB;
16090
16091     ftime(&timeB);
16092     tm->sec = (long) timeB.time;
16093     tm->ms = (int) timeB.millitm;
16094
16095 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16096     tm->sec = (long) time(NULL);
16097     tm->ms = 0;
16098 #endif
16099 #endif
16100 }
16101
16102 /* Return the difference in milliseconds between two
16103    time marks.  We assume the difference will fit in a long!
16104 */
16105 long
16106 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16107 {
16108     return 1000L*(tm2->sec - tm1->sec) +
16109            (long) (tm2->ms - tm1->ms);
16110 }
16111
16112
16113 /*
16114  * Code to manage the game clocks.
16115  *
16116  * In tournament play, black starts the clock and then white makes a move.
16117  * We give the human user a slight advantage if he is playing white---the
16118  * clocks don't run until he makes his first move, so it takes zero time.
16119  * Also, we don't account for network lag, so we could get out of sync
16120  * with GNU Chess's clock -- but then, referees are always right.
16121  */
16122
16123 static TimeMark tickStartTM;
16124 static long intendedTickLength;
16125
16126 long
16127 NextTickLength (long timeRemaining)
16128 {
16129     long nominalTickLength, nextTickLength;
16130
16131     if (timeRemaining > 0L && timeRemaining <= 10000L)
16132       nominalTickLength = 100L;
16133     else
16134       nominalTickLength = 1000L;
16135     nextTickLength = timeRemaining % nominalTickLength;
16136     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16137
16138     return nextTickLength;
16139 }
16140
16141 /* Adjust clock one minute up or down */
16142 void
16143 AdjustClock (Boolean which, int dir)
16144 {
16145     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16146     if(which) blackTimeRemaining += 60000*dir;
16147     else      whiteTimeRemaining += 60000*dir;
16148     DisplayBothClocks();
16149     adjustedClock = TRUE;
16150 }
16151
16152 /* Stop clocks and reset to a fresh time control */
16153 void
16154 ResetClocks ()
16155 {
16156     (void) StopClockTimer();
16157     if (appData.icsActive) {
16158         whiteTimeRemaining = blackTimeRemaining = 0;
16159     } else if (searchTime) {
16160         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16161         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16162     } else { /* [HGM] correct new time quote for time odds */
16163         whiteTC = blackTC = fullTimeControlString;
16164         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16165         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16166     }
16167     if (whiteFlag || blackFlag) {
16168         DisplayTitle("");
16169         whiteFlag = blackFlag = FALSE;
16170     }
16171     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16172     DisplayBothClocks();
16173     adjustedClock = FALSE;
16174 }
16175
16176 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16177
16178 /* Decrement running clock by amount of time that has passed */
16179 void
16180 DecrementClocks ()
16181 {
16182     long timeRemaining;
16183     long lastTickLength, fudge;
16184     TimeMark now;
16185
16186     if (!appData.clockMode) return;
16187     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16188
16189     GetTimeMark(&now);
16190
16191     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16192
16193     /* Fudge if we woke up a little too soon */
16194     fudge = intendedTickLength - lastTickLength;
16195     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16196
16197     if (WhiteOnMove(forwardMostMove)) {
16198         if(whiteNPS >= 0) lastTickLength = 0;
16199         timeRemaining = whiteTimeRemaining -= lastTickLength;
16200         if(timeRemaining < 0 && !appData.icsActive) {
16201             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16202             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16203                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16204                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16205             }
16206         }
16207         DisplayWhiteClock(whiteTimeRemaining - fudge,
16208                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16209     } else {
16210         if(blackNPS >= 0) lastTickLength = 0;
16211         timeRemaining = blackTimeRemaining -= lastTickLength;
16212         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16213             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16214             if(suddenDeath) {
16215                 blackStartMove = forwardMostMove;
16216                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16217             }
16218         }
16219         DisplayBlackClock(blackTimeRemaining - fudge,
16220                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16221     }
16222     if (CheckFlags()) return;
16223
16224     if(twoBoards) { // count down secondary board's clocks as well
16225         activePartnerTime -= lastTickLength;
16226         partnerUp = 1;
16227         if(activePartner == 'W')
16228             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16229         else
16230             DisplayBlackClock(activePartnerTime, TRUE);
16231         partnerUp = 0;
16232     }
16233
16234     tickStartTM = now;
16235     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16236     StartClockTimer(intendedTickLength);
16237
16238     /* if the time remaining has fallen below the alarm threshold, sound the
16239      * alarm. if the alarm has sounded and (due to a takeback or time control
16240      * with increment) the time remaining has increased to a level above the
16241      * threshold, reset the alarm so it can sound again.
16242      */
16243
16244     if (appData.icsActive && appData.icsAlarm) {
16245
16246         /* make sure we are dealing with the user's clock */
16247         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16248                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16249            )) return;
16250
16251         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16252             alarmSounded = FALSE;
16253         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16254             PlayAlarmSound();
16255             alarmSounded = TRUE;
16256         }
16257     }
16258 }
16259
16260
16261 /* A player has just moved, so stop the previously running
16262    clock and (if in clock mode) start the other one.
16263    We redisplay both clocks in case we're in ICS mode, because
16264    ICS gives us an update to both clocks after every move.
16265    Note that this routine is called *after* forwardMostMove
16266    is updated, so the last fractional tick must be subtracted
16267    from the color that is *not* on move now.
16268 */
16269 void
16270 SwitchClocks (int newMoveNr)
16271 {
16272     long lastTickLength;
16273     TimeMark now;
16274     int flagged = FALSE;
16275
16276     GetTimeMark(&now);
16277
16278     if (StopClockTimer() && appData.clockMode) {
16279         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16280         if (!WhiteOnMove(forwardMostMove)) {
16281             if(blackNPS >= 0) lastTickLength = 0;
16282             blackTimeRemaining -= lastTickLength;
16283            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16284 //         if(pvInfoList[forwardMostMove].time == -1)
16285                  pvInfoList[forwardMostMove].time =               // use GUI time
16286                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16287         } else {
16288            if(whiteNPS >= 0) lastTickLength = 0;
16289            whiteTimeRemaining -= lastTickLength;
16290            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16291 //         if(pvInfoList[forwardMostMove].time == -1)
16292                  pvInfoList[forwardMostMove].time =
16293                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16294         }
16295         flagged = CheckFlags();
16296     }
16297     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16298     CheckTimeControl();
16299
16300     if (flagged || !appData.clockMode) return;
16301
16302     switch (gameMode) {
16303       case MachinePlaysBlack:
16304       case MachinePlaysWhite:
16305       case BeginningOfGame:
16306         if (pausing) return;
16307         break;
16308
16309       case EditGame:
16310       case PlayFromGameFile:
16311       case IcsExamining:
16312         return;
16313
16314       default:
16315         break;
16316     }
16317
16318     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16319         if(WhiteOnMove(forwardMostMove))
16320              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16321         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16322     }
16323
16324     tickStartTM = now;
16325     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16326       whiteTimeRemaining : blackTimeRemaining);
16327     StartClockTimer(intendedTickLength);
16328 }
16329
16330
16331 /* Stop both clocks */
16332 void
16333 StopClocks ()
16334 {
16335     long lastTickLength;
16336     TimeMark now;
16337
16338     if (!StopClockTimer()) return;
16339     if (!appData.clockMode) return;
16340
16341     GetTimeMark(&now);
16342
16343     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16344     if (WhiteOnMove(forwardMostMove)) {
16345         if(whiteNPS >= 0) lastTickLength = 0;
16346         whiteTimeRemaining -= lastTickLength;
16347         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16348     } else {
16349         if(blackNPS >= 0) lastTickLength = 0;
16350         blackTimeRemaining -= lastTickLength;
16351         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16352     }
16353     CheckFlags();
16354 }
16355
16356 /* Start clock of player on move.  Time may have been reset, so
16357    if clock is already running, stop and restart it. */
16358 void
16359 StartClocks ()
16360 {
16361     (void) StopClockTimer(); /* in case it was running already */
16362     DisplayBothClocks();
16363     if (CheckFlags()) return;
16364
16365     if (!appData.clockMode) return;
16366     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16367
16368     GetTimeMark(&tickStartTM);
16369     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16370       whiteTimeRemaining : blackTimeRemaining);
16371
16372    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16373     whiteNPS = blackNPS = -1;
16374     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16375        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16376         whiteNPS = first.nps;
16377     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16378        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16379         blackNPS = first.nps;
16380     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16381         whiteNPS = second.nps;
16382     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16383         blackNPS = second.nps;
16384     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16385
16386     StartClockTimer(intendedTickLength);
16387 }
16388
16389 char *
16390 TimeString (long ms)
16391 {
16392     long second, minute, hour, day;
16393     char *sign = "";
16394     static char buf[32];
16395
16396     if (ms > 0 && ms <= 9900) {
16397       /* convert milliseconds to tenths, rounding up */
16398       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16399
16400       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16401       return buf;
16402     }
16403
16404     /* convert milliseconds to seconds, rounding up */
16405     /* use floating point to avoid strangeness of integer division
16406        with negative dividends on many machines */
16407     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16408
16409     if (second < 0) {
16410         sign = "-";
16411         second = -second;
16412     }
16413
16414     day = second / (60 * 60 * 24);
16415     second = second % (60 * 60 * 24);
16416     hour = second / (60 * 60);
16417     second = second % (60 * 60);
16418     minute = second / 60;
16419     second = second % 60;
16420
16421     if (day > 0)
16422       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16423               sign, day, hour, minute, second);
16424     else if (hour > 0)
16425       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16426     else
16427       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16428
16429     return buf;
16430 }
16431
16432
16433 /*
16434  * This is necessary because some C libraries aren't ANSI C compliant yet.
16435  */
16436 char *
16437 StrStr (char *string, char *match)
16438 {
16439     int i, length;
16440
16441     length = strlen(match);
16442
16443     for (i = strlen(string) - length; i >= 0; i--, string++)
16444       if (!strncmp(match, string, length))
16445         return string;
16446
16447     return NULL;
16448 }
16449
16450 char *
16451 StrCaseStr (char *string, char *match)
16452 {
16453     int i, j, length;
16454
16455     length = strlen(match);
16456
16457     for (i = strlen(string) - length; i >= 0; i--, string++) {
16458         for (j = 0; j < length; j++) {
16459             if (ToLower(match[j]) != ToLower(string[j]))
16460               break;
16461         }
16462         if (j == length) return string;
16463     }
16464
16465     return NULL;
16466 }
16467
16468 #ifndef _amigados
16469 int
16470 StrCaseCmp (char *s1, char *s2)
16471 {
16472     char c1, c2;
16473
16474     for (;;) {
16475         c1 = ToLower(*s1++);
16476         c2 = ToLower(*s2++);
16477         if (c1 > c2) return 1;
16478         if (c1 < c2) return -1;
16479         if (c1 == NULLCHAR) return 0;
16480     }
16481 }
16482
16483
16484 int
16485 ToLower (int c)
16486 {
16487     return isupper(c) ? tolower(c) : c;
16488 }
16489
16490
16491 int
16492 ToUpper (int c)
16493 {
16494     return islower(c) ? toupper(c) : c;
16495 }
16496 #endif /* !_amigados    */
16497
16498 char *
16499 StrSave (char *s)
16500 {
16501   char *ret;
16502
16503   if ((ret = (char *) malloc(strlen(s) + 1)))
16504     {
16505       safeStrCpy(ret, s, strlen(s)+1);
16506     }
16507   return ret;
16508 }
16509
16510 char *
16511 StrSavePtr (char *s, char **savePtr)
16512 {
16513     if (*savePtr) {
16514         free(*savePtr);
16515     }
16516     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16517       safeStrCpy(*savePtr, s, strlen(s)+1);
16518     }
16519     return(*savePtr);
16520 }
16521
16522 char *
16523 PGNDate ()
16524 {
16525     time_t clock;
16526     struct tm *tm;
16527     char buf[MSG_SIZ];
16528
16529     clock = time((time_t *)NULL);
16530     tm = localtime(&clock);
16531     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16532             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16533     return StrSave(buf);
16534 }
16535
16536
16537 char *
16538 PositionToFEN (int move, char *overrideCastling)
16539 {
16540     int i, j, fromX, fromY, toX, toY;
16541     int whiteToPlay;
16542     char buf[MSG_SIZ];
16543     char *p, *q;
16544     int emptycount;
16545     ChessSquare piece;
16546
16547     whiteToPlay = (gameMode == EditPosition) ?
16548       !blackPlaysFirst : (move % 2 == 0);
16549     p = buf;
16550
16551     /* Piece placement data */
16552     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16553         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16554         emptycount = 0;
16555         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16556             if (boards[move][i][j] == EmptySquare) {
16557                 emptycount++;
16558             } else { ChessSquare piece = boards[move][i][j];
16559                 if (emptycount > 0) {
16560                     if(emptycount<10) /* [HGM] can be >= 10 */
16561                         *p++ = '0' + emptycount;
16562                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16563                     emptycount = 0;
16564                 }
16565                 if(PieceToChar(piece) == '+') {
16566                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16567                     *p++ = '+';
16568                     piece = (ChessSquare)(DEMOTED piece);
16569                 }
16570                 *p++ = PieceToChar(piece);
16571                 if(p[-1] == '~') {
16572                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16573                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16574                     *p++ = '~';
16575                 }
16576             }
16577         }
16578         if (emptycount > 0) {
16579             if(emptycount<10) /* [HGM] can be >= 10 */
16580                 *p++ = '0' + emptycount;
16581             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16582             emptycount = 0;
16583         }
16584         *p++ = '/';
16585     }
16586     *(p - 1) = ' ';
16587
16588     /* [HGM] print Crazyhouse or Shogi holdings */
16589     if( gameInfo.holdingsWidth ) {
16590         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16591         q = p;
16592         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16593             piece = boards[move][i][BOARD_WIDTH-1];
16594             if( piece != EmptySquare )
16595               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16596                   *p++ = PieceToChar(piece);
16597         }
16598         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16599             piece = boards[move][BOARD_HEIGHT-i-1][0];
16600             if( piece != EmptySquare )
16601               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16602                   *p++ = PieceToChar(piece);
16603         }
16604
16605         if( q == p ) *p++ = '-';
16606         *p++ = ']';
16607         *p++ = ' ';
16608     }
16609
16610     /* Active color */
16611     *p++ = whiteToPlay ? 'w' : 'b';
16612     *p++ = ' ';
16613
16614   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16615     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16616   } else {
16617   if(nrCastlingRights) {
16618      q = p;
16619      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16620        /* [HGM] write directly from rights */
16621            if(boards[move][CASTLING][2] != NoRights &&
16622               boards[move][CASTLING][0] != NoRights   )
16623                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16624            if(boards[move][CASTLING][2] != NoRights &&
16625               boards[move][CASTLING][1] != NoRights   )
16626                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16627            if(boards[move][CASTLING][5] != NoRights &&
16628               boards[move][CASTLING][3] != NoRights   )
16629                 *p++ = boards[move][CASTLING][3] + AAA;
16630            if(boards[move][CASTLING][5] != NoRights &&
16631               boards[move][CASTLING][4] != NoRights   )
16632                 *p++ = boards[move][CASTLING][4] + AAA;
16633      } else {
16634
16635         /* [HGM] write true castling rights */
16636         if( nrCastlingRights == 6 ) {
16637             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16638                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16639             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16640                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16641             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16642                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16643             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16644                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16645         }
16646      }
16647      if (q == p) *p++ = '-'; /* No castling rights */
16648      *p++ = ' ';
16649   }
16650
16651   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16652      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16653     /* En passant target square */
16654     if (move > backwardMostMove) {
16655         fromX = moveList[move - 1][0] - AAA;
16656         fromY = moveList[move - 1][1] - ONE;
16657         toX = moveList[move - 1][2] - AAA;
16658         toY = moveList[move - 1][3] - ONE;
16659         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16660             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16661             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16662             fromX == toX) {
16663             /* 2-square pawn move just happened */
16664             *p++ = toX + AAA;
16665             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16666         } else {
16667             *p++ = '-';
16668         }
16669     } else if(move == backwardMostMove) {
16670         // [HGM] perhaps we should always do it like this, and forget the above?
16671         if((signed char)boards[move][EP_STATUS] >= 0) {
16672             *p++ = boards[move][EP_STATUS] + AAA;
16673             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16674         } else {
16675             *p++ = '-';
16676         }
16677     } else {
16678         *p++ = '-';
16679     }
16680     *p++ = ' ';
16681   }
16682   }
16683
16684     /* [HGM] find reversible plies */
16685     {   int i = 0, j=move;
16686
16687         if (appData.debugMode) { int k;
16688             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16689             for(k=backwardMostMove; k<=forwardMostMove; k++)
16690                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16691
16692         }
16693
16694         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16695         if( j == backwardMostMove ) i += initialRulePlies;
16696         sprintf(p, "%d ", i);
16697         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16698     }
16699     /* Fullmove number */
16700     sprintf(p, "%d", (move / 2) + 1);
16701
16702     return StrSave(buf);
16703 }
16704
16705 Boolean
16706 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16707 {
16708     int i, j;
16709     char *p, c;
16710     int emptycount;
16711     ChessSquare piece;
16712
16713     p = fen;
16714
16715     /* [HGM] by default clear Crazyhouse holdings, if present */
16716     if(gameInfo.holdingsWidth) {
16717        for(i=0; i<BOARD_HEIGHT; i++) {
16718            board[i][0]             = EmptySquare; /* black holdings */
16719            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16720            board[i][1]             = (ChessSquare) 0; /* black counts */
16721            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16722        }
16723     }
16724
16725     /* Piece placement data */
16726     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16727         j = 0;
16728         for (;;) {
16729             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16730                 if (*p == '/') p++;
16731                 emptycount = gameInfo.boardWidth - j;
16732                 while (emptycount--)
16733                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16734                 break;
16735 #if(BOARD_FILES >= 10)
16736             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16737                 p++; emptycount=10;
16738                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16739                 while (emptycount--)
16740                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16741 #endif
16742             } else if (isdigit(*p)) {
16743                 emptycount = *p++ - '0';
16744                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16745                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16746                 while (emptycount--)
16747                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16748             } else if (*p == '+' || isalpha(*p)) {
16749                 if (j >= gameInfo.boardWidth) return FALSE;
16750                 if(*p=='+') {
16751                     piece = CharToPiece(*++p);
16752                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16753                     piece = (ChessSquare) (PROMOTED piece ); p++;
16754                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16755                 } else piece = CharToPiece(*p++);
16756
16757                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16758                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16759                     piece = (ChessSquare) (PROMOTED piece);
16760                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16761                     p++;
16762                 }
16763                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16764             } else {
16765                 return FALSE;
16766             }
16767         }
16768     }
16769     while (*p == '/' || *p == ' ') p++;
16770
16771     /* [HGM] look for Crazyhouse holdings here */
16772     while(*p==' ') p++;
16773     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16774         if(*p == '[') p++;
16775         if(*p == '-' ) p++; /* empty holdings */ else {
16776             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16777             /* if we would allow FEN reading to set board size, we would   */
16778             /* have to add holdings and shift the board read so far here   */
16779             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16780                 p++;
16781                 if((int) piece >= (int) BlackPawn ) {
16782                     i = (int)piece - (int)BlackPawn;
16783                     i = PieceToNumber((ChessSquare)i);
16784                     if( i >= gameInfo.holdingsSize ) return FALSE;
16785                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16786                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16787                 } else {
16788                     i = (int)piece - (int)WhitePawn;
16789                     i = PieceToNumber((ChessSquare)i);
16790                     if( i >= gameInfo.holdingsSize ) return FALSE;
16791                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16792                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16793                 }
16794             }
16795         }
16796         if(*p == ']') p++;
16797     }
16798
16799     while(*p == ' ') p++;
16800
16801     /* Active color */
16802     c = *p++;
16803     if(appData.colorNickNames) {
16804       if( c == appData.colorNickNames[0] ) c = 'w'; else
16805       if( c == appData.colorNickNames[1] ) c = 'b';
16806     }
16807     switch (c) {
16808       case 'w':
16809         *blackPlaysFirst = FALSE;
16810         break;
16811       case 'b':
16812         *blackPlaysFirst = TRUE;
16813         break;
16814       default:
16815         return FALSE;
16816     }
16817
16818     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16819     /* return the extra info in global variiables             */
16820
16821     /* set defaults in case FEN is incomplete */
16822     board[EP_STATUS] = EP_UNKNOWN;
16823     for(i=0; i<nrCastlingRights; i++ ) {
16824         board[CASTLING][i] =
16825             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16826     }   /* assume possible unless obviously impossible */
16827     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16828     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16829     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16830                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16831     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16832     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16833     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16834                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16835     FENrulePlies = 0;
16836
16837     while(*p==' ') p++;
16838     if(nrCastlingRights) {
16839       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16840           /* castling indicator present, so default becomes no castlings */
16841           for(i=0; i<nrCastlingRights; i++ ) {
16842                  board[CASTLING][i] = NoRights;
16843           }
16844       }
16845       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16846              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16847              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16848              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16849         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16850
16851         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16852             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16853             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16854         }
16855         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16856             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16857         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16858                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16859         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16860                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16861         switch(c) {
16862           case'K':
16863               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16864               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16865               board[CASTLING][2] = whiteKingFile;
16866               break;
16867           case'Q':
16868               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16869               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16870               board[CASTLING][2] = whiteKingFile;
16871               break;
16872           case'k':
16873               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16874               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16875               board[CASTLING][5] = blackKingFile;
16876               break;
16877           case'q':
16878               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16879               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16880               board[CASTLING][5] = blackKingFile;
16881           case '-':
16882               break;
16883           default: /* FRC castlings */
16884               if(c >= 'a') { /* black rights */
16885                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16886                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16887                   if(i == BOARD_RGHT) break;
16888                   board[CASTLING][5] = i;
16889                   c -= AAA;
16890                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16891                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16892                   if(c > i)
16893                       board[CASTLING][3] = c;
16894                   else
16895                       board[CASTLING][4] = c;
16896               } else { /* white rights */
16897                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16898                     if(board[0][i] == WhiteKing) break;
16899                   if(i == BOARD_RGHT) break;
16900                   board[CASTLING][2] = i;
16901                   c -= AAA - 'a' + 'A';
16902                   if(board[0][c] >= WhiteKing) break;
16903                   if(c > i)
16904                       board[CASTLING][0] = c;
16905                   else
16906                       board[CASTLING][1] = c;
16907               }
16908         }
16909       }
16910       for(i=0; i<nrCastlingRights; i++)
16911         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16912     if (appData.debugMode) {
16913         fprintf(debugFP, "FEN castling rights:");
16914         for(i=0; i<nrCastlingRights; i++)
16915         fprintf(debugFP, " %d", board[CASTLING][i]);
16916         fprintf(debugFP, "\n");
16917     }
16918
16919       while(*p==' ') p++;
16920     }
16921
16922     /* read e.p. field in games that know e.p. capture */
16923     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16924        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16925       if(*p=='-') {
16926         p++; board[EP_STATUS] = EP_NONE;
16927       } else {
16928          char c = *p++ - AAA;
16929
16930          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16931          if(*p >= '0' && *p <='9') p++;
16932          board[EP_STATUS] = c;
16933       }
16934     }
16935
16936
16937     if(sscanf(p, "%d", &i) == 1) {
16938         FENrulePlies = i; /* 50-move ply counter */
16939         /* (The move number is still ignored)    */
16940     }
16941
16942     return TRUE;
16943 }
16944
16945 void
16946 EditPositionPasteFEN (char *fen)
16947 {
16948   if (fen != NULL) {
16949     Board initial_position;
16950
16951     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16952       DisplayError(_("Bad FEN position in clipboard"), 0);
16953       return ;
16954     } else {
16955       int savedBlackPlaysFirst = blackPlaysFirst;
16956       EditPositionEvent();
16957       blackPlaysFirst = savedBlackPlaysFirst;
16958       CopyBoard(boards[0], initial_position);
16959       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16960       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16961       DisplayBothClocks();
16962       DrawPosition(FALSE, boards[currentMove]);
16963     }
16964   }
16965 }
16966
16967 static char cseq[12] = "\\   ";
16968
16969 Boolean
16970 set_cont_sequence (char *new_seq)
16971 {
16972     int len;
16973     Boolean ret;
16974
16975     // handle bad attempts to set the sequence
16976         if (!new_seq)
16977                 return 0; // acceptable error - no debug
16978
16979     len = strlen(new_seq);
16980     ret = (len > 0) && (len < sizeof(cseq));
16981     if (ret)
16982       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16983     else if (appData.debugMode)
16984       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16985     return ret;
16986 }
16987
16988 /*
16989     reformat a source message so words don't cross the width boundary.  internal
16990     newlines are not removed.  returns the wrapped size (no null character unless
16991     included in source message).  If dest is NULL, only calculate the size required
16992     for the dest buffer.  lp argument indicats line position upon entry, and it's
16993     passed back upon exit.
16994 */
16995 int
16996 wrap (char *dest, char *src, int count, int width, int *lp)
16997 {
16998     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16999
17000     cseq_len = strlen(cseq);
17001     old_line = line = *lp;
17002     ansi = len = clen = 0;
17003
17004     for (i=0; i < count; i++)
17005     {
17006         if (src[i] == '\033')
17007             ansi = 1;
17008
17009         // if we hit the width, back up
17010         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17011         {
17012             // store i & len in case the word is too long
17013             old_i = i, old_len = len;
17014
17015             // find the end of the last word
17016             while (i && src[i] != ' ' && src[i] != '\n')
17017             {
17018                 i--;
17019                 len--;
17020             }
17021
17022             // word too long?  restore i & len before splitting it
17023             if ((old_i-i+clen) >= width)
17024             {
17025                 i = old_i;
17026                 len = old_len;
17027             }
17028
17029             // extra space?
17030             if (i && src[i-1] == ' ')
17031                 len--;
17032
17033             if (src[i] != ' ' && src[i] != '\n')
17034             {
17035                 i--;
17036                 if (len)
17037                     len--;
17038             }
17039
17040             // now append the newline and continuation sequence
17041             if (dest)
17042                 dest[len] = '\n';
17043             len++;
17044             if (dest)
17045                 strncpy(dest+len, cseq, cseq_len);
17046             len += cseq_len;
17047             line = cseq_len;
17048             clen = cseq_len;
17049             continue;
17050         }
17051
17052         if (dest)
17053             dest[len] = src[i];
17054         len++;
17055         if (!ansi)
17056             line++;
17057         if (src[i] == '\n')
17058             line = 0;
17059         if (src[i] == 'm')
17060             ansi = 0;
17061     }
17062     if (dest && appData.debugMode)
17063     {
17064         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17065             count, width, line, len, *lp);
17066         show_bytes(debugFP, src, count);
17067         fprintf(debugFP, "\ndest: ");
17068         show_bytes(debugFP, dest, len);
17069         fprintf(debugFP, "\n");
17070     }
17071     *lp = dest ? line : old_line;
17072
17073     return len;
17074 }
17075
17076 // [HGM] vari: routines for shelving variations
17077 Boolean modeRestore = FALSE;
17078
17079 void
17080 PushInner (int firstMove, int lastMove)
17081 {
17082         int i, j, nrMoves = lastMove - firstMove;
17083
17084         // push current tail of game on stack
17085         savedResult[storedGames] = gameInfo.result;
17086         savedDetails[storedGames] = gameInfo.resultDetails;
17087         gameInfo.resultDetails = NULL;
17088         savedFirst[storedGames] = firstMove;
17089         savedLast [storedGames] = lastMove;
17090         savedFramePtr[storedGames] = framePtr;
17091         framePtr -= nrMoves; // reserve space for the boards
17092         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17093             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17094             for(j=0; j<MOVE_LEN; j++)
17095                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17096             for(j=0; j<2*MOVE_LEN; j++)
17097                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17098             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17099             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17100             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17101             pvInfoList[firstMove+i-1].depth = 0;
17102             commentList[framePtr+i] = commentList[firstMove+i];
17103             commentList[firstMove+i] = NULL;
17104         }
17105
17106         storedGames++;
17107         forwardMostMove = firstMove; // truncate game so we can start variation
17108 }
17109
17110 void
17111 PushTail (int firstMove, int lastMove)
17112 {
17113         if(appData.icsActive) { // only in local mode
17114                 forwardMostMove = currentMove; // mimic old ICS behavior
17115                 return;
17116         }
17117         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17118
17119         PushInner(firstMove, lastMove);
17120         if(storedGames == 1) GreyRevert(FALSE);
17121         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17122 }
17123
17124 void
17125 PopInner (Boolean annotate)
17126 {
17127         int i, j, nrMoves;
17128         char buf[8000], moveBuf[20];
17129
17130         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17131         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17132         nrMoves = savedLast[storedGames] - currentMove;
17133         if(annotate) {
17134                 int cnt = 10;
17135                 if(!WhiteOnMove(currentMove))
17136                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17137                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17138                 for(i=currentMove; i<forwardMostMove; i++) {
17139                         if(WhiteOnMove(i))
17140                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17141                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17142                         strcat(buf, moveBuf);
17143                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17144                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17145                 }
17146                 strcat(buf, ")");
17147         }
17148         for(i=1; i<=nrMoves; i++) { // copy last variation back
17149             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17150             for(j=0; j<MOVE_LEN; j++)
17151                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17152             for(j=0; j<2*MOVE_LEN; j++)
17153                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17154             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17155             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17156             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17157             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17158             commentList[currentMove+i] = commentList[framePtr+i];
17159             commentList[framePtr+i] = NULL;
17160         }
17161         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17162         framePtr = savedFramePtr[storedGames];
17163         gameInfo.result = savedResult[storedGames];
17164         if(gameInfo.resultDetails != NULL) {
17165             free(gameInfo.resultDetails);
17166       }
17167         gameInfo.resultDetails = savedDetails[storedGames];
17168         forwardMostMove = currentMove + nrMoves;
17169 }
17170
17171 Boolean
17172 PopTail (Boolean annotate)
17173 {
17174         if(appData.icsActive) return FALSE; // only in local mode
17175         if(!storedGames) return FALSE; // sanity
17176         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17177
17178         PopInner(annotate);
17179         if(currentMove < forwardMostMove) ForwardEvent(); else
17180         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17181
17182         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17183         return TRUE;
17184 }
17185
17186 void
17187 CleanupTail ()
17188 {       // remove all shelved variations
17189         int i;
17190         for(i=0; i<storedGames; i++) {
17191             if(savedDetails[i])
17192                 free(savedDetails[i]);
17193             savedDetails[i] = NULL;
17194         }
17195         for(i=framePtr; i<MAX_MOVES; i++) {
17196                 if(commentList[i]) free(commentList[i]);
17197                 commentList[i] = NULL;
17198         }
17199         framePtr = MAX_MOVES-1;
17200         storedGames = 0;
17201 }
17202
17203 void
17204 LoadVariation (int index, char *text)
17205 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17206         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17207         int level = 0, move;
17208
17209         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17210         // first find outermost bracketing variation
17211         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17212             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17213                 if(*p == '{') wait = '}'; else
17214                 if(*p == '[') wait = ']'; else
17215                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17216                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17217             }
17218             if(*p == wait) wait = NULLCHAR; // closing ]} found
17219             p++;
17220         }
17221         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17222         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17223         end[1] = NULLCHAR; // clip off comment beyond variation
17224         ToNrEvent(currentMove-1);
17225         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17226         // kludge: use ParsePV() to append variation to game
17227         move = currentMove;
17228         ParsePV(start, TRUE, TRUE);
17229         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17230         ClearPremoveHighlights();
17231         CommentPopDown();
17232         ToNrEvent(currentMove+1);
17233 }
17234