265a55c249d6d43b29cfe88bba0cd274e4bdce54
[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     if(toX >= 0) {
5177         int victim = boards[currentMove][toY][toX];
5178         boards[currentMove][toY][toX] = promoSweep;
5179         DrawPosition(FALSE, boards[currentMove]);
5180         boards[currentMove][toY][toX] = victim;
5181     } else
5182     ChangeDragPiece(promoSweep);
5183 }
5184
5185 int
5186 PromoScroll (int x, int y)
5187 {
5188   int step = 0;
5189
5190   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5191   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5192   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5193   if(!step) return FALSE;
5194   lastX = x; lastY = y;
5195   if((promoSweep < BlackPawn) == flipView) step = -step;
5196   if(step > 0) selectFlag = 1;
5197   if(!selectFlag) Sweep(step);
5198   return FALSE;
5199 }
5200
5201 void
5202 NextPiece (int step)
5203 {
5204     ChessSquare piece = boards[currentMove][toY][toX];
5205     do {
5206         pieceSweep -= step;
5207         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5208         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5209         if(!step) step = -1;
5210     } while(PieceToChar(pieceSweep) == '.');
5211     boards[currentMove][toY][toX] = pieceSweep;
5212     DrawPosition(FALSE, boards[currentMove]);
5213     boards[currentMove][toY][toX] = piece;
5214 }
5215 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5216 void
5217 AlphaRank (char *move, int n)
5218 {
5219 //    char *p = move, c; int x, y;
5220
5221     if (appData.debugMode) {
5222         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5223     }
5224
5225     if(move[1]=='*' &&
5226        move[2]>='0' && move[2]<='9' &&
5227        move[3]>='a' && move[3]<='x'    ) {
5228         move[1] = '@';
5229         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5230         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5231     } else
5232     if(move[0]>='0' && move[0]<='9' &&
5233        move[1]>='a' && move[1]<='x' &&
5234        move[2]>='0' && move[2]<='9' &&
5235        move[3]>='a' && move[3]<='x'    ) {
5236         /* input move, Shogi -> normal */
5237         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5238         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5239         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5240         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5241     } else
5242     if(move[1]=='@' &&
5243        move[3]>='0' && move[3]<='9' &&
5244        move[2]>='a' && move[2]<='x'    ) {
5245         move[1] = '*';
5246         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5247         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5248     } else
5249     if(
5250        move[0]>='a' && move[0]<='x' &&
5251        move[3]>='0' && move[3]<='9' &&
5252        move[2]>='a' && move[2]<='x'    ) {
5253          /* output move, normal -> Shogi */
5254         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5255         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5256         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5257         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5258         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5259     }
5260     if (appData.debugMode) {
5261         fprintf(debugFP, "   out = '%s'\n", move);
5262     }
5263 }
5264
5265 char yy_textstr[8000];
5266
5267 /* Parser for moves from gnuchess, ICS, or user typein box */
5268 Boolean
5269 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5270 {
5271     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5272
5273     switch (*moveType) {
5274       case WhitePromotion:
5275       case BlackPromotion:
5276       case WhiteNonPromotion:
5277       case BlackNonPromotion:
5278       case NormalMove:
5279       case WhiteCapturesEnPassant:
5280       case BlackCapturesEnPassant:
5281       case WhiteKingSideCastle:
5282       case WhiteQueenSideCastle:
5283       case BlackKingSideCastle:
5284       case BlackQueenSideCastle:
5285       case WhiteKingSideCastleWild:
5286       case WhiteQueenSideCastleWild:
5287       case BlackKingSideCastleWild:
5288       case BlackQueenSideCastleWild:
5289       /* Code added by Tord: */
5290       case WhiteHSideCastleFR:
5291       case WhiteASideCastleFR:
5292       case BlackHSideCastleFR:
5293       case BlackASideCastleFR:
5294       /* End of code added by Tord */
5295       case IllegalMove:         /* bug or odd chess variant */
5296         *fromX = currentMoveString[0] - AAA;
5297         *fromY = currentMoveString[1] - ONE;
5298         *toX = currentMoveString[2] - AAA;
5299         *toY = currentMoveString[3] - ONE;
5300         *promoChar = currentMoveString[4];
5301         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5302             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5303     if (appData.debugMode) {
5304         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5305     }
5306             *fromX = *fromY = *toX = *toY = 0;
5307             return FALSE;
5308         }
5309         if (appData.testLegality) {
5310           return (*moveType != IllegalMove);
5311         } else {
5312           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5313                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5314         }
5315
5316       case WhiteDrop:
5317       case BlackDrop:
5318         *fromX = *moveType == WhiteDrop ?
5319           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5320           (int) CharToPiece(ToLower(currentMoveString[0]));
5321         *fromY = DROP_RANK;
5322         *toX = currentMoveString[2] - AAA;
5323         *toY = currentMoveString[3] - ONE;
5324         *promoChar = NULLCHAR;
5325         return TRUE;
5326
5327       case AmbiguousMove:
5328       case ImpossibleMove:
5329       case EndOfFile:
5330       case ElapsedTime:
5331       case Comment:
5332       case PGNTag:
5333       case NAG:
5334       case WhiteWins:
5335       case BlackWins:
5336       case GameIsDrawn:
5337       default:
5338     if (appData.debugMode) {
5339         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5340     }
5341         /* bug? */
5342         *fromX = *fromY = *toX = *toY = 0;
5343         *promoChar = NULLCHAR;
5344         return FALSE;
5345     }
5346 }
5347
5348 Boolean pushed = FALSE;
5349 char *lastParseAttempt;
5350
5351 void
5352 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5353 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5354   int fromX, fromY, toX, toY; char promoChar;
5355   ChessMove moveType;
5356   Boolean valid;
5357   int nr = 0;
5358
5359   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5360     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5361     pushed = TRUE;
5362   }
5363   endPV = forwardMostMove;
5364   do {
5365     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5366     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5367     lastParseAttempt = pv;
5368     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5369     if(!valid && nr == 0 &&
5370        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5371         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5372         // Hande case where played move is different from leading PV move
5373         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5374         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5375         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5376         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5377           endPV += 2; // if position different, keep this
5378           moveList[endPV-1][0] = fromX + AAA;
5379           moveList[endPV-1][1] = fromY + ONE;
5380           moveList[endPV-1][2] = toX + AAA;
5381           moveList[endPV-1][3] = toY + ONE;
5382           parseList[endPV-1][0] = NULLCHAR;
5383           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5384         }
5385       }
5386     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5387     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5388     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5389     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5390         valid++; // allow comments in PV
5391         continue;
5392     }
5393     nr++;
5394     if(endPV+1 > framePtr) break; // no space, truncate
5395     if(!valid) break;
5396     endPV++;
5397     CopyBoard(boards[endPV], boards[endPV-1]);
5398     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5399     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5400     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5401     CoordsToAlgebraic(boards[endPV - 1],
5402                              PosFlags(endPV - 1),
5403                              fromY, fromX, toY, toX, promoChar,
5404                              parseList[endPV - 1]);
5405   } while(valid);
5406   if(atEnd == 2) return; // used hidden, for PV conversion
5407   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5408   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5409   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5410                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5411   DrawPosition(TRUE, boards[currentMove]);
5412 }
5413
5414 int
5415 MultiPV (ChessProgramState *cps)
5416 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5417         int i;
5418         for(i=0; i<cps->nrOptions; i++)
5419             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5420                 return i;
5421         return -1;
5422 }
5423
5424 Boolean
5425 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5426 {
5427         int startPV, multi, lineStart, origIndex = index;
5428         char *p, buf2[MSG_SIZ];
5429
5430         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5431         lastX = x; lastY = y;
5432         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5433         lineStart = startPV = index;
5434         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5435         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5436         index = startPV;
5437         do{ while(buf[index] && buf[index] != '\n') index++;
5438         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5439         buf[index] = 0;
5440         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5441                 int n = first.option[multi].value;
5442                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5443                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5444                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5445                 first.option[multi].value = n;
5446                 *start = *end = 0;
5447                 return FALSE;
5448         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5449                 ExcludeClick(origIndex - lineStart);
5450                 return FALSE;
5451         }
5452         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5453         *start = startPV; *end = index-1;
5454         return TRUE;
5455 }
5456
5457 char *
5458 PvToSAN (char *pv)
5459 {
5460         static char buf[10*MSG_SIZ];
5461         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5462         *buf = NULLCHAR;
5463         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5464         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5465         for(i = forwardMostMove; i<endPV; i++){
5466             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5467             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5468             k += strlen(buf+k);
5469         }
5470         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5471         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5472         endPV = savedEnd;
5473         return buf;
5474 }
5475
5476 Boolean
5477 LoadPV (int x, int y)
5478 { // called on right mouse click to load PV
5479   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5480   lastX = x; lastY = y;
5481   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5482   return TRUE;
5483 }
5484
5485 void
5486 UnLoadPV ()
5487 {
5488   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5489   if(endPV < 0) return;
5490   if(appData.autoCopyPV) CopyFENToClipboard();
5491   endPV = -1;
5492   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5493         Boolean saveAnimate = appData.animate;
5494         if(pushed) {
5495             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5496                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5497             } else storedGames--; // abandon shelved tail of original game
5498         }
5499         pushed = FALSE;
5500         forwardMostMove = currentMove;
5501         currentMove = oldFMM;
5502         appData.animate = FALSE;
5503         ToNrEvent(forwardMostMove);
5504         appData.animate = saveAnimate;
5505   }
5506   currentMove = forwardMostMove;
5507   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5508   ClearPremoveHighlights();
5509   DrawPosition(TRUE, boards[currentMove]);
5510 }
5511
5512 void
5513 MovePV (int x, int y, int h)
5514 { // step through PV based on mouse coordinates (called on mouse move)
5515   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5516
5517   // we must somehow check if right button is still down (might be released off board!)
5518   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5519   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5520   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5521   if(!step) return;
5522   lastX = x; lastY = y;
5523
5524   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5525   if(endPV < 0) return;
5526   if(y < margin) step = 1; else
5527   if(y > h - margin) step = -1;
5528   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5529   currentMove += step;
5530   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5531   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5532                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5533   DrawPosition(FALSE, boards[currentMove]);
5534 }
5535
5536
5537 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5538 // All positions will have equal probability, but the current method will not provide a unique
5539 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5540 #define DARK 1
5541 #define LITE 2
5542 #define ANY 3
5543
5544 int squaresLeft[4];
5545 int piecesLeft[(int)BlackPawn];
5546 int seed, nrOfShuffles;
5547
5548 void
5549 GetPositionNumber ()
5550 {       // sets global variable seed
5551         int i;
5552
5553         seed = appData.defaultFrcPosition;
5554         if(seed < 0) { // randomize based on time for negative FRC position numbers
5555                 for(i=0; i<50; i++) seed += random();
5556                 seed = random() ^ random() >> 8 ^ random() << 8;
5557                 if(seed<0) seed = -seed;
5558         }
5559 }
5560
5561 int
5562 put (Board board, int pieceType, int rank, int n, int shade)
5563 // put the piece on the (n-1)-th empty squares of the given shade
5564 {
5565         int i;
5566
5567         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5568                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5569                         board[rank][i] = (ChessSquare) pieceType;
5570                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5571                         squaresLeft[ANY]--;
5572                         piecesLeft[pieceType]--;
5573                         return i;
5574                 }
5575         }
5576         return -1;
5577 }
5578
5579
5580 void
5581 AddOnePiece (Board board, int pieceType, int rank, int shade)
5582 // calculate where the next piece goes, (any empty square), and put it there
5583 {
5584         int i;
5585
5586         i = seed % squaresLeft[shade];
5587         nrOfShuffles *= squaresLeft[shade];
5588         seed /= squaresLeft[shade];
5589         put(board, pieceType, rank, i, shade);
5590 }
5591
5592 void
5593 AddTwoPieces (Board board, int pieceType, int rank)
5594 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5595 {
5596         int i, n=squaresLeft[ANY], j=n-1, k;
5597
5598         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5599         i = seed % k;  // pick one
5600         nrOfShuffles *= k;
5601         seed /= k;
5602         while(i >= j) i -= j--;
5603         j = n - 1 - j; i += j;
5604         put(board, pieceType, rank, j, ANY);
5605         put(board, pieceType, rank, i, ANY);
5606 }
5607
5608 void
5609 SetUpShuffle (Board board, int number)
5610 {
5611         int i, p, first=1;
5612
5613         GetPositionNumber(); nrOfShuffles = 1;
5614
5615         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5616         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5617         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5618
5619         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5620
5621         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5622             p = (int) board[0][i];
5623             if(p < (int) BlackPawn) piecesLeft[p] ++;
5624             board[0][i] = EmptySquare;
5625         }
5626
5627         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5628             // shuffles restricted to allow normal castling put KRR first
5629             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5630                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5631             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5632                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5633             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5634                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5635             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5636                 put(board, WhiteRook, 0, 0, ANY);
5637             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5638         }
5639
5640         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5641             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5642             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5643                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5644                 while(piecesLeft[p] >= 2) {
5645                     AddOnePiece(board, p, 0, LITE);
5646                     AddOnePiece(board, p, 0, DARK);
5647                 }
5648                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5649             }
5650
5651         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5652             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5653             // but we leave King and Rooks for last, to possibly obey FRC restriction
5654             if(p == (int)WhiteRook) continue;
5655             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5656             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5657         }
5658
5659         // now everything is placed, except perhaps King (Unicorn) and Rooks
5660
5661         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5662             // Last King gets castling rights
5663             while(piecesLeft[(int)WhiteUnicorn]) {
5664                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5665                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5666             }
5667
5668             while(piecesLeft[(int)WhiteKing]) {
5669                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5670                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5671             }
5672
5673
5674         } else {
5675             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5676             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5677         }
5678
5679         // Only Rooks can be left; simply place them all
5680         while(piecesLeft[(int)WhiteRook]) {
5681                 i = put(board, WhiteRook, 0, 0, ANY);
5682                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5683                         if(first) {
5684                                 first=0;
5685                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5686                         }
5687                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5688                 }
5689         }
5690         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5691             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5692         }
5693
5694         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5695 }
5696
5697 int
5698 SetCharTable (char *table, const char * map)
5699 /* [HGM] moved here from winboard.c because of its general usefulness */
5700 /*       Basically a safe strcpy that uses the last character as King */
5701 {
5702     int result = FALSE; int NrPieces;
5703
5704     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5705                     && NrPieces >= 12 && !(NrPieces&1)) {
5706         int i; /* [HGM] Accept even length from 12 to 34 */
5707
5708         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5709         for( i=0; i<NrPieces/2-1; i++ ) {
5710             table[i] = map[i];
5711             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5712         }
5713         table[(int) WhiteKing]  = map[NrPieces/2-1];
5714         table[(int) BlackKing]  = map[NrPieces-1];
5715
5716         result = TRUE;
5717     }
5718
5719     return result;
5720 }
5721
5722 void
5723 Prelude (Board board)
5724 {       // [HGM] superchess: random selection of exo-pieces
5725         int i, j, k; ChessSquare p;
5726         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5727
5728         GetPositionNumber(); // use FRC position number
5729
5730         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5731             SetCharTable(pieceToChar, appData.pieceToCharTable);
5732             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5733                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5734         }
5735
5736         j = seed%4;                 seed /= 4;
5737         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5738         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5739         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5740         j = seed%3 + (seed%3 >= j); seed /= 3;
5741         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5742         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5743         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5744         j = seed%3;                 seed /= 3;
5745         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5746         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5747         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5748         j = seed%2 + (seed%2 >= j); seed /= 2;
5749         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5750         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5751         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5752         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5753         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5754         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5755         put(board, exoPieces[0],    0, 0, ANY);
5756         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5757 }
5758
5759 void
5760 InitPosition (int redraw)
5761 {
5762     ChessSquare (* pieces)[BOARD_FILES];
5763     int i, j, pawnRow, overrule,
5764     oldx = gameInfo.boardWidth,
5765     oldy = gameInfo.boardHeight,
5766     oldh = gameInfo.holdingsWidth;
5767     static int oldv;
5768
5769     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5770
5771     /* [AS] Initialize pv info list [HGM] and game status */
5772     {
5773         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5774             pvInfoList[i].depth = 0;
5775             boards[i][EP_STATUS] = EP_NONE;
5776             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5777         }
5778
5779         initialRulePlies = 0; /* 50-move counter start */
5780
5781         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5782         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5783     }
5784
5785
5786     /* [HGM] logic here is completely changed. In stead of full positions */
5787     /* the initialized data only consist of the two backranks. The switch */
5788     /* selects which one we will use, which is than copied to the Board   */
5789     /* initialPosition, which for the rest is initialized by Pawns and    */
5790     /* empty squares. This initial position is then copied to boards[0],  */
5791     /* possibly after shuffling, so that it remains available.            */
5792
5793     gameInfo.holdingsWidth = 0; /* default board sizes */
5794     gameInfo.boardWidth    = 8;
5795     gameInfo.boardHeight   = 8;
5796     gameInfo.holdingsSize  = 0;
5797     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5798     for(i=0; i<BOARD_FILES-2; i++)
5799       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5800     initialPosition[EP_STATUS] = EP_NONE;
5801     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5802     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5803          SetCharTable(pieceNickName, appData.pieceNickNames);
5804     else SetCharTable(pieceNickName, "............");
5805     pieces = FIDEArray;
5806
5807     switch (gameInfo.variant) {
5808     case VariantFischeRandom:
5809       shuffleOpenings = TRUE;
5810     default:
5811       break;
5812     case VariantShatranj:
5813       pieces = ShatranjArray;
5814       nrCastlingRights = 0;
5815       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5816       break;
5817     case VariantMakruk:
5818       pieces = makrukArray;
5819       nrCastlingRights = 0;
5820       startedFromSetupPosition = TRUE;
5821       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5822       break;
5823     case VariantTwoKings:
5824       pieces = twoKingsArray;
5825       break;
5826     case VariantGrand:
5827       pieces = GrandArray;
5828       nrCastlingRights = 0;
5829       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5830       gameInfo.boardWidth = 10;
5831       gameInfo.boardHeight = 10;
5832       gameInfo.holdingsSize = 7;
5833       break;
5834     case VariantCapaRandom:
5835       shuffleOpenings = TRUE;
5836     case VariantCapablanca:
5837       pieces = CapablancaArray;
5838       gameInfo.boardWidth = 10;
5839       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5840       break;
5841     case VariantGothic:
5842       pieces = GothicArray;
5843       gameInfo.boardWidth = 10;
5844       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5845       break;
5846     case VariantSChess:
5847       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5848       gameInfo.holdingsSize = 7;
5849       break;
5850     case VariantJanus:
5851       pieces = JanusArray;
5852       gameInfo.boardWidth = 10;
5853       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5854       nrCastlingRights = 6;
5855         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5856         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5857         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5858         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5859         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5860         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5861       break;
5862     case VariantFalcon:
5863       pieces = FalconArray;
5864       gameInfo.boardWidth = 10;
5865       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5866       break;
5867     case VariantXiangqi:
5868       pieces = XiangqiArray;
5869       gameInfo.boardWidth  = 9;
5870       gameInfo.boardHeight = 10;
5871       nrCastlingRights = 0;
5872       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5873       break;
5874     case VariantShogi:
5875       pieces = ShogiArray;
5876       gameInfo.boardWidth  = 9;
5877       gameInfo.boardHeight = 9;
5878       gameInfo.holdingsSize = 7;
5879       nrCastlingRights = 0;
5880       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5881       break;
5882     case VariantCourier:
5883       pieces = CourierArray;
5884       gameInfo.boardWidth  = 12;
5885       nrCastlingRights = 0;
5886       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5887       break;
5888     case VariantKnightmate:
5889       pieces = KnightmateArray;
5890       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5891       break;
5892     case VariantSpartan:
5893       pieces = SpartanArray;
5894       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5895       break;
5896     case VariantFairy:
5897       pieces = fairyArray;
5898       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5899       break;
5900     case VariantGreat:
5901       pieces = GreatArray;
5902       gameInfo.boardWidth = 10;
5903       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5904       gameInfo.holdingsSize = 8;
5905       break;
5906     case VariantSuper:
5907       pieces = FIDEArray;
5908       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5909       gameInfo.holdingsSize = 8;
5910       startedFromSetupPosition = TRUE;
5911       break;
5912     case VariantCrazyhouse:
5913     case VariantBughouse:
5914       pieces = FIDEArray;
5915       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5916       gameInfo.holdingsSize = 5;
5917       break;
5918     case VariantWildCastle:
5919       pieces = FIDEArray;
5920       /* !!?shuffle with kings guaranteed to be on d or e file */
5921       shuffleOpenings = 1;
5922       break;
5923     case VariantNoCastle:
5924       pieces = FIDEArray;
5925       nrCastlingRights = 0;
5926       /* !!?unconstrained back-rank shuffle */
5927       shuffleOpenings = 1;
5928       break;
5929     }
5930
5931     overrule = 0;
5932     if(appData.NrFiles >= 0) {
5933         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5934         gameInfo.boardWidth = appData.NrFiles;
5935     }
5936     if(appData.NrRanks >= 0) {
5937         gameInfo.boardHeight = appData.NrRanks;
5938     }
5939     if(appData.holdingsSize >= 0) {
5940         i = appData.holdingsSize;
5941         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5942         gameInfo.holdingsSize = i;
5943     }
5944     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5945     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5946         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5947
5948     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5949     if(pawnRow < 1) pawnRow = 1;
5950     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5951
5952     /* User pieceToChar list overrules defaults */
5953     if(appData.pieceToCharTable != NULL)
5954         SetCharTable(pieceToChar, appData.pieceToCharTable);
5955
5956     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5957
5958         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5959             s = (ChessSquare) 0; /* account holding counts in guard band */
5960         for( i=0; i<BOARD_HEIGHT; i++ )
5961             initialPosition[i][j] = s;
5962
5963         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5964         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5965         initialPosition[pawnRow][j] = WhitePawn;
5966         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5967         if(gameInfo.variant == VariantXiangqi) {
5968             if(j&1) {
5969                 initialPosition[pawnRow][j] =
5970                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5971                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5972                    initialPosition[2][j] = WhiteCannon;
5973                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5974                 }
5975             }
5976         }
5977         if(gameInfo.variant == VariantGrand) {
5978             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5979                initialPosition[0][j] = WhiteRook;
5980                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5981             }
5982         }
5983         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5984     }
5985     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5986
5987             j=BOARD_LEFT+1;
5988             initialPosition[1][j] = WhiteBishop;
5989             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5990             j=BOARD_RGHT-2;
5991             initialPosition[1][j] = WhiteRook;
5992             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5993     }
5994
5995     if( nrCastlingRights == -1) {
5996         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5997         /*       This sets default castling rights from none to normal corners   */
5998         /* Variants with other castling rights must set them themselves above    */
5999         nrCastlingRights = 6;
6000
6001         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6002         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6003         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6004         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6005         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6006         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6007      }
6008
6009      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6010      if(gameInfo.variant == VariantGreat) { // promotion commoners
6011         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6012         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6013         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6014         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6015      }
6016      if( gameInfo.variant == VariantSChess ) {
6017       initialPosition[1][0] = BlackMarshall;
6018       initialPosition[2][0] = BlackAngel;
6019       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6020       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6021       initialPosition[1][1] = initialPosition[2][1] = 
6022       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6023      }
6024   if (appData.debugMode) {
6025     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6026   }
6027     if(shuffleOpenings) {
6028         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6029         startedFromSetupPosition = TRUE;
6030     }
6031     if(startedFromPositionFile) {
6032       /* [HGM] loadPos: use PositionFile for every new game */
6033       CopyBoard(initialPosition, filePosition);
6034       for(i=0; i<nrCastlingRights; i++)
6035           initialRights[i] = filePosition[CASTLING][i];
6036       startedFromSetupPosition = TRUE;
6037     }
6038
6039     CopyBoard(boards[0], initialPosition);
6040
6041     if(oldx != gameInfo.boardWidth ||
6042        oldy != gameInfo.boardHeight ||
6043        oldv != gameInfo.variant ||
6044        oldh != gameInfo.holdingsWidth
6045                                          )
6046             InitDrawingSizes(-2 ,0);
6047
6048     oldv = gameInfo.variant;
6049     if (redraw)
6050       DrawPosition(TRUE, boards[currentMove]);
6051 }
6052
6053 void
6054 SendBoard (ChessProgramState *cps, int moveNum)
6055 {
6056     char message[MSG_SIZ];
6057
6058     if (cps->useSetboard) {
6059       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6060       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6061       SendToProgram(message, cps);
6062       free(fen);
6063
6064     } else {
6065       ChessSquare *bp;
6066       int i, j, left=0, right=BOARD_WIDTH;
6067       /* Kludge to set black to move, avoiding the troublesome and now
6068        * deprecated "black" command.
6069        */
6070       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6071         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6072
6073       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6074
6075       SendToProgram("edit\n", cps);
6076       SendToProgram("#\n", cps);
6077       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6078         bp = &boards[moveNum][i][left];
6079         for (j = left; j < right; j++, bp++) {
6080           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6081           if ((int) *bp < (int) BlackPawn) {
6082             if(j == BOARD_RGHT+1)
6083                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6084             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6085             if(message[0] == '+' || message[0] == '~') {
6086               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6087                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6088                         AAA + j, ONE + i);
6089             }
6090             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6091                 message[1] = BOARD_RGHT   - 1 - j + '1';
6092                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6093             }
6094             SendToProgram(message, cps);
6095           }
6096         }
6097       }
6098
6099       SendToProgram("c\n", cps);
6100       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6101         bp = &boards[moveNum][i][left];
6102         for (j = left; j < right; j++, bp++) {
6103           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6104           if (((int) *bp != (int) EmptySquare)
6105               && ((int) *bp >= (int) BlackPawn)) {
6106             if(j == BOARD_LEFT-2)
6107                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6108             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6109                     AAA + j, ONE + i);
6110             if(message[0] == '+' || message[0] == '~') {
6111               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6112                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6113                         AAA + j, ONE + i);
6114             }
6115             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6116                 message[1] = BOARD_RGHT   - 1 - j + '1';
6117                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6118             }
6119             SendToProgram(message, cps);
6120           }
6121         }
6122       }
6123
6124       SendToProgram(".\n", cps);
6125     }
6126     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6127 }
6128
6129 char exclusionHeader[MSG_SIZ];
6130 int exCnt, excludePtr;
6131 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6132 static Exclusion excluTab[200];
6133 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6134
6135 static void
6136 WriteMap (int s)
6137 {
6138     int j;
6139     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6140     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6141 }
6142
6143 static void
6144 ClearMap ()
6145 {
6146     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6147     excludePtr = 24; exCnt = 0;
6148     WriteMap(0);
6149 }
6150
6151 static void
6152 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6153 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6154     char buf[2*MOVE_LEN], *p;
6155     Exclusion *e = excluTab;
6156     int i;
6157     for(i=0; i<exCnt; i++)
6158         if(e[i].ff == fromX && e[i].fr == fromY &&
6159            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6160     if(i == exCnt) { // was not in exclude list; add it
6161         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6162         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6163             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6164             return; // abort
6165         }
6166         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6167         excludePtr++; e[i].mark = excludePtr++;
6168         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6169         exCnt++;
6170     }
6171     exclusionHeader[e[i].mark] = state;
6172 }
6173
6174 static int
6175 ExcludeOneMove (int fromY, int fromX, int toY, int toX, signed char promoChar, char state)
6176 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6177     char buf[MSG_SIZ];
6178     int j, k;
6179     ChessMove moveType;
6180     if(promoChar == -1) { // kludge to indicate best move
6181         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6182             return 1; // if unparsable, abort
6183     }
6184     // update exclusion map (resolving toggle by consulting existing state)
6185     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6186     j = k%8; k >>= 3;
6187     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6188     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6189          excludeMap[k] |=   1<<j;
6190     else excludeMap[k] &= ~(1<<j);
6191     // update header
6192     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6193     // inform engine
6194     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6195     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6196     SendToProgram(buf, &first);
6197     return (state == '+');
6198 }
6199
6200 static void
6201 ExcludeClick (int index)
6202 {
6203     int i, j;
6204     Exclusion *e = excluTab;
6205     if(index < 25) { // none, best or tail clicked
6206         if(index < 13) { // none: include all
6207             WriteMap(0); // clear map
6208             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6209             SendToProgram("include all\n", &first); // and inform engine
6210         } else if(index > 18) { // tail
6211             if(exclusionHeader[19] == '-') { // tail was excluded
6212                 SendToProgram("include all\n", &first);
6213                 WriteMap(0); // clear map completely
6214                 // now re-exclude selected moves
6215                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6216                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6217             } else { // tail was included or in mixed state
6218                 SendToProgram("exclude all\n", &first);
6219                 WriteMap(0xFF); // fill map completely
6220                 // now re-include selected moves
6221                 j = 0; // count them
6222                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6223                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6224                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6225             }
6226         } else { // best
6227             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6228         }
6229     } else {
6230         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6231             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6232             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6233             break;
6234         }
6235     }
6236 }
6237
6238 ChessSquare
6239 DefaultPromoChoice (int white)
6240 {
6241     ChessSquare result;
6242     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6243         result = WhiteFerz; // no choice
6244     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6245         result= WhiteKing; // in Suicide Q is the last thing we want
6246     else if(gameInfo.variant == VariantSpartan)
6247         result = white ? WhiteQueen : WhiteAngel;
6248     else result = WhiteQueen;
6249     if(!white) result = WHITE_TO_BLACK result;
6250     return result;
6251 }
6252
6253 static int autoQueen; // [HGM] oneclick
6254
6255 int
6256 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6257 {
6258     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6259     /* [HGM] add Shogi promotions */
6260     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6261     ChessSquare piece;
6262     ChessMove moveType;
6263     Boolean premove;
6264
6265     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6266     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6267
6268     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6269       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6270         return FALSE;
6271
6272     piece = boards[currentMove][fromY][fromX];
6273     if(gameInfo.variant == VariantShogi) {
6274         promotionZoneSize = BOARD_HEIGHT/3;
6275         highestPromotingPiece = (int)WhiteFerz;
6276     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6277         promotionZoneSize = 3;
6278     }
6279
6280     // Treat Lance as Pawn when it is not representing Amazon
6281     if(gameInfo.variant != VariantSuper) {
6282         if(piece == WhiteLance) piece = WhitePawn; else
6283         if(piece == BlackLance) piece = BlackPawn;
6284     }
6285
6286     // next weed out all moves that do not touch the promotion zone at all
6287     if((int)piece >= BlackPawn) {
6288         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6289              return FALSE;
6290         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6291     } else {
6292         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6293            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6294     }
6295
6296     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6297
6298     // weed out mandatory Shogi promotions
6299     if(gameInfo.variant == VariantShogi) {
6300         if(piece >= BlackPawn) {
6301             if(toY == 0 && piece == BlackPawn ||
6302                toY == 0 && piece == BlackQueen ||
6303                toY <= 1 && piece == BlackKnight) {
6304                 *promoChoice = '+';
6305                 return FALSE;
6306             }
6307         } else {
6308             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6309                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6310                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6311                 *promoChoice = '+';
6312                 return FALSE;
6313             }
6314         }
6315     }
6316
6317     // weed out obviously illegal Pawn moves
6318     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6319         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6320         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6321         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6322         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6323         // note we are not allowed to test for valid (non-)capture, due to premove
6324     }
6325
6326     // we either have a choice what to promote to, or (in Shogi) whether to promote
6327     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6328         *promoChoice = PieceToChar(BlackFerz);  // no choice
6329         return FALSE;
6330     }
6331     // no sense asking what we must promote to if it is going to explode...
6332     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6333         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6334         return FALSE;
6335     }
6336     // give caller the default choice even if we will not make it
6337     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6338     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6339     if(        sweepSelect && gameInfo.variant != VariantGreat
6340                            && gameInfo.variant != VariantGrand
6341                            && gameInfo.variant != VariantSuper) return FALSE;
6342     if(autoQueen) return FALSE; // predetermined
6343
6344     // suppress promotion popup on illegal moves that are not premoves
6345     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6346               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6347     if(appData.testLegality && !premove) {
6348         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6349                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6350         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6351             return FALSE;
6352     }
6353
6354     return TRUE;
6355 }
6356
6357 int
6358 InPalace (int row, int column)
6359 {   /* [HGM] for Xiangqi */
6360     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6361          column < (BOARD_WIDTH + 4)/2 &&
6362          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6363     return FALSE;
6364 }
6365
6366 int
6367 PieceForSquare (int x, int y)
6368 {
6369   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6370      return -1;
6371   else
6372      return boards[currentMove][y][x];
6373 }
6374
6375 int
6376 OKToStartUserMove (int x, int y)
6377 {
6378     ChessSquare from_piece;
6379     int white_piece;
6380
6381     if (matchMode) return FALSE;
6382     if (gameMode == EditPosition) return TRUE;
6383
6384     if (x >= 0 && y >= 0)
6385       from_piece = boards[currentMove][y][x];
6386     else
6387       from_piece = EmptySquare;
6388
6389     if (from_piece == EmptySquare) return FALSE;
6390
6391     white_piece = (int)from_piece >= (int)WhitePawn &&
6392       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6393
6394     switch (gameMode) {
6395       case AnalyzeFile:
6396       case TwoMachinesPlay:
6397       case EndOfGame:
6398         return FALSE;
6399
6400       case IcsObserving:
6401       case IcsIdle:
6402         return FALSE;
6403
6404       case MachinePlaysWhite:
6405       case IcsPlayingBlack:
6406         if (appData.zippyPlay) return FALSE;
6407         if (white_piece) {
6408             DisplayMoveError(_("You are playing Black"));
6409             return FALSE;
6410         }
6411         break;
6412
6413       case MachinePlaysBlack:
6414       case IcsPlayingWhite:
6415         if (appData.zippyPlay) return FALSE;
6416         if (!white_piece) {
6417             DisplayMoveError(_("You are playing White"));
6418             return FALSE;
6419         }
6420         break;
6421
6422       case PlayFromGameFile:
6423             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6424       case EditGame:
6425         if (!white_piece && WhiteOnMove(currentMove)) {
6426             DisplayMoveError(_("It is White's turn"));
6427             return FALSE;
6428         }
6429         if (white_piece && !WhiteOnMove(currentMove)) {
6430             DisplayMoveError(_("It is Black's turn"));
6431             return FALSE;
6432         }
6433         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6434             /* Editing correspondence game history */
6435             /* Could disallow this or prompt for confirmation */
6436             cmailOldMove = -1;
6437         }
6438         break;
6439
6440       case BeginningOfGame:
6441         if (appData.icsActive) return FALSE;
6442         if (!appData.noChessProgram) {
6443             if (!white_piece) {
6444                 DisplayMoveError(_("You are playing White"));
6445                 return FALSE;
6446             }
6447         }
6448         break;
6449
6450       case Training:
6451         if (!white_piece && WhiteOnMove(currentMove)) {
6452             DisplayMoveError(_("It is White's turn"));
6453             return FALSE;
6454         }
6455         if (white_piece && !WhiteOnMove(currentMove)) {
6456             DisplayMoveError(_("It is Black's turn"));
6457             return FALSE;
6458         }
6459         break;
6460
6461       default:
6462       case IcsExamining:
6463         break;
6464     }
6465     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6466         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6467         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6468         && gameMode != AnalyzeFile && gameMode != Training) {
6469         DisplayMoveError(_("Displayed position is not current"));
6470         return FALSE;
6471     }
6472     return TRUE;
6473 }
6474
6475 Boolean
6476 OnlyMove (int *x, int *y, Boolean captures) 
6477 {
6478     DisambiguateClosure cl;
6479     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6480     switch(gameMode) {
6481       case MachinePlaysBlack:
6482       case IcsPlayingWhite:
6483       case BeginningOfGame:
6484         if(!WhiteOnMove(currentMove)) return FALSE;
6485         break;
6486       case MachinePlaysWhite:
6487       case IcsPlayingBlack:
6488         if(WhiteOnMove(currentMove)) return FALSE;
6489         break;
6490       case EditGame:
6491         break;
6492       default:
6493         return FALSE;
6494     }
6495     cl.pieceIn = EmptySquare;
6496     cl.rfIn = *y;
6497     cl.ffIn = *x;
6498     cl.rtIn = -1;
6499     cl.ftIn = -1;
6500     cl.promoCharIn = NULLCHAR;
6501     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6502     if( cl.kind == NormalMove ||
6503         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6504         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6505         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6506       fromX = cl.ff;
6507       fromY = cl.rf;
6508       *x = cl.ft;
6509       *y = cl.rt;
6510       return TRUE;
6511     }
6512     if(cl.kind != ImpossibleMove) return FALSE;
6513     cl.pieceIn = EmptySquare;
6514     cl.rfIn = -1;
6515     cl.ffIn = -1;
6516     cl.rtIn = *y;
6517     cl.ftIn = *x;
6518     cl.promoCharIn = NULLCHAR;
6519     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6520     if( cl.kind == NormalMove ||
6521         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6522         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6523         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6524       fromX = cl.ff;
6525       fromY = cl.rf;
6526       *x = cl.ft;
6527       *y = cl.rt;
6528       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6529       return TRUE;
6530     }
6531     return FALSE;
6532 }
6533
6534 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6535 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6536 int lastLoadGameUseList = FALSE;
6537 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6538 ChessMove lastLoadGameStart = EndOfFile;
6539 int doubleClick;
6540
6541 void
6542 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6543 {
6544     ChessMove moveType;
6545     ChessSquare pdown, pup;
6546     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6547
6548
6549     /* Check if the user is playing in turn.  This is complicated because we
6550        let the user "pick up" a piece before it is his turn.  So the piece he
6551        tried to pick up may have been captured by the time he puts it down!
6552        Therefore we use the color the user is supposed to be playing in this
6553        test, not the color of the piece that is currently on the starting
6554        square---except in EditGame mode, where the user is playing both
6555        sides; fortunately there the capture race can't happen.  (It can
6556        now happen in IcsExamining mode, but that's just too bad.  The user
6557        will get a somewhat confusing message in that case.)
6558        */
6559
6560     switch (gameMode) {
6561       case AnalyzeFile:
6562       case TwoMachinesPlay:
6563       case EndOfGame:
6564       case IcsObserving:
6565       case IcsIdle:
6566         /* We switched into a game mode where moves are not accepted,
6567            perhaps while the mouse button was down. */
6568         return;
6569
6570       case MachinePlaysWhite:
6571         /* User is moving for Black */
6572         if (WhiteOnMove(currentMove)) {
6573             DisplayMoveError(_("It is White's turn"));
6574             return;
6575         }
6576         break;
6577
6578       case MachinePlaysBlack:
6579         /* User is moving for White */
6580         if (!WhiteOnMove(currentMove)) {
6581             DisplayMoveError(_("It is Black's turn"));
6582             return;
6583         }
6584         break;
6585
6586       case PlayFromGameFile:
6587             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6588       case EditGame:
6589       case IcsExamining:
6590       case BeginningOfGame:
6591       case AnalyzeMode:
6592       case Training:
6593         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6594         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6595             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6596             /* User is moving for Black */
6597             if (WhiteOnMove(currentMove)) {
6598                 DisplayMoveError(_("It is White's turn"));
6599                 return;
6600             }
6601         } else {
6602             /* User is moving for White */
6603             if (!WhiteOnMove(currentMove)) {
6604                 DisplayMoveError(_("It is Black's turn"));
6605                 return;
6606             }
6607         }
6608         break;
6609
6610       case IcsPlayingBlack:
6611         /* User is moving for Black */
6612         if (WhiteOnMove(currentMove)) {
6613             if (!appData.premove) {
6614                 DisplayMoveError(_("It is White's turn"));
6615             } else if (toX >= 0 && toY >= 0) {
6616                 premoveToX = toX;
6617                 premoveToY = toY;
6618                 premoveFromX = fromX;
6619                 premoveFromY = fromY;
6620                 premovePromoChar = promoChar;
6621                 gotPremove = 1;
6622                 if (appData.debugMode)
6623                     fprintf(debugFP, "Got premove: fromX %d,"
6624                             "fromY %d, toX %d, toY %d\n",
6625                             fromX, fromY, toX, toY);
6626             }
6627             return;
6628         }
6629         break;
6630
6631       case IcsPlayingWhite:
6632         /* User is moving for White */
6633         if (!WhiteOnMove(currentMove)) {
6634             if (!appData.premove) {
6635                 DisplayMoveError(_("It is Black's turn"));
6636             } else if (toX >= 0 && toY >= 0) {
6637                 premoveToX = toX;
6638                 premoveToY = toY;
6639                 premoveFromX = fromX;
6640                 premoveFromY = fromY;
6641                 premovePromoChar = promoChar;
6642                 gotPremove = 1;
6643                 if (appData.debugMode)
6644                     fprintf(debugFP, "Got premove: fromX %d,"
6645                             "fromY %d, toX %d, toY %d\n",
6646                             fromX, fromY, toX, toY);
6647             }
6648             return;
6649         }
6650         break;
6651
6652       default:
6653         break;
6654
6655       case EditPosition:
6656         /* EditPosition, empty square, or different color piece;
6657            click-click move is possible */
6658         if (toX == -2 || toY == -2) {
6659             boards[0][fromY][fromX] = EmptySquare;
6660             DrawPosition(FALSE, boards[currentMove]);
6661             return;
6662         } else if (toX >= 0 && toY >= 0) {
6663             boards[0][toY][toX] = boards[0][fromY][fromX];
6664             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6665                 if(boards[0][fromY][0] != EmptySquare) {
6666                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6667                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6668                 }
6669             } else
6670             if(fromX == BOARD_RGHT+1) {
6671                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6672                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6673                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6674                 }
6675             } else
6676             boards[0][fromY][fromX] = gatingPiece;
6677             DrawPosition(FALSE, boards[currentMove]);
6678             return;
6679         }
6680         return;
6681     }
6682
6683     if(toX < 0 || toY < 0) return;
6684     pdown = boards[currentMove][fromY][fromX];
6685     pup = boards[currentMove][toY][toX];
6686
6687     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6688     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6689          if( pup != EmptySquare ) return;
6690          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6691            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6692                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6693            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6694            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6695            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6696            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6697          fromY = DROP_RANK;
6698     }
6699
6700     /* [HGM] always test for legality, to get promotion info */
6701     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6702                                          fromY, fromX, toY, toX, promoChar);
6703
6704     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6705
6706     /* [HGM] but possibly ignore an IllegalMove result */
6707     if (appData.testLegality) {
6708         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6709             DisplayMoveError(_("Illegal move"));
6710             return;
6711         }
6712     }
6713
6714     if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6715         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6716              ClearPremoveHighlights(); // was included
6717         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6718         return;
6719     }
6720
6721     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6722 }
6723
6724 /* Common tail of UserMoveEvent and DropMenuEvent */
6725 int
6726 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6727 {
6728     char *bookHit = 0;
6729
6730     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6731         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6732         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6733         if(WhiteOnMove(currentMove)) {
6734             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6735         } else {
6736             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6737         }
6738     }
6739
6740     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6741        move type in caller when we know the move is a legal promotion */
6742     if(moveType == NormalMove && promoChar)
6743         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6744
6745     /* [HGM] <popupFix> The following if has been moved here from
6746        UserMoveEvent(). Because it seemed to belong here (why not allow
6747        piece drops in training games?), and because it can only be
6748        performed after it is known to what we promote. */
6749     if (gameMode == Training) {
6750       /* compare the move played on the board to the next move in the
6751        * game. If they match, display the move and the opponent's response.
6752        * If they don't match, display an error message.
6753        */
6754       int saveAnimate;
6755       Board testBoard;
6756       CopyBoard(testBoard, boards[currentMove]);
6757       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6758
6759       if (CompareBoards(testBoard, boards[currentMove+1])) {
6760         ForwardInner(currentMove+1);
6761
6762         /* Autoplay the opponent's response.
6763          * if appData.animate was TRUE when Training mode was entered,
6764          * the response will be animated.
6765          */
6766         saveAnimate = appData.animate;
6767         appData.animate = animateTraining;
6768         ForwardInner(currentMove+1);
6769         appData.animate = saveAnimate;
6770
6771         /* check for the end of the game */
6772         if (currentMove >= forwardMostMove) {
6773           gameMode = PlayFromGameFile;
6774           ModeHighlight();
6775           SetTrainingModeOff();
6776           DisplayInformation(_("End of game"));
6777         }
6778       } else {
6779         DisplayError(_("Incorrect move"), 0);
6780       }
6781       return 1;
6782     }
6783
6784   /* Ok, now we know that the move is good, so we can kill
6785      the previous line in Analysis Mode */
6786   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6787                                 && currentMove < forwardMostMove) {
6788     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6789     else forwardMostMove = currentMove;
6790   }
6791
6792   ClearMap();
6793
6794   /* If we need the chess program but it's dead, restart it */
6795   ResurrectChessProgram();
6796
6797   /* A user move restarts a paused game*/
6798   if (pausing)
6799     PauseEvent();
6800
6801   thinkOutput[0] = NULLCHAR;
6802
6803   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6804
6805   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6806     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6807     return 1;
6808   }
6809
6810   if (gameMode == BeginningOfGame) {
6811     if (appData.noChessProgram) {
6812       gameMode = EditGame;
6813       SetGameInfo();
6814     } else {
6815       char buf[MSG_SIZ];
6816       gameMode = MachinePlaysBlack;
6817       StartClocks();
6818       SetGameInfo();
6819       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6820       DisplayTitle(buf);
6821       if (first.sendName) {
6822         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6823         SendToProgram(buf, &first);
6824       }
6825       StartClocks();
6826     }
6827     ModeHighlight();
6828   }
6829
6830   /* Relay move to ICS or chess engine */
6831   if (appData.icsActive) {
6832     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6833         gameMode == IcsExamining) {
6834       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6835         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6836         SendToICS("draw ");
6837         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6838       }
6839       // also send plain move, in case ICS does not understand atomic claims
6840       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6841       ics_user_moved = 1;
6842     }
6843   } else {
6844     if (first.sendTime && (gameMode == BeginningOfGame ||
6845                            gameMode == MachinePlaysWhite ||
6846                            gameMode == MachinePlaysBlack)) {
6847       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6848     }
6849     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6850          // [HGM] book: if program might be playing, let it use book
6851         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6852         first.maybeThinking = TRUE;
6853     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6854         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6855         SendBoard(&first, currentMove+1);
6856     } else SendMoveToProgram(forwardMostMove-1, &first);
6857     if (currentMove == cmailOldMove + 1) {
6858       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6859     }
6860   }
6861
6862   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6863
6864   switch (gameMode) {
6865   case EditGame:
6866     if(appData.testLegality)
6867     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6868     case MT_NONE:
6869     case MT_CHECK:
6870       break;
6871     case MT_CHECKMATE:
6872     case MT_STAINMATE:
6873       if (WhiteOnMove(currentMove)) {
6874         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6875       } else {
6876         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6877       }
6878       break;
6879     case MT_STALEMATE:
6880       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6881       break;
6882     }
6883     break;
6884
6885   case MachinePlaysBlack:
6886   case MachinePlaysWhite:
6887     /* disable certain menu options while machine is thinking */
6888     SetMachineThinkingEnables();
6889     break;
6890
6891   default:
6892     break;
6893   }
6894
6895   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6896   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6897
6898   if(bookHit) { // [HGM] book: simulate book reply
6899         static char bookMove[MSG_SIZ]; // a bit generous?
6900
6901         programStats.nodes = programStats.depth = programStats.time =
6902         programStats.score = programStats.got_only_move = 0;
6903         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6904
6905         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6906         strcat(bookMove, bookHit);
6907         HandleMachineMove(bookMove, &first);
6908   }
6909   return 1;
6910 }
6911
6912 void
6913 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6914 {
6915     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6916     Markers *m = (Markers *) closure;
6917     if(rf == fromY && ff == fromX)
6918         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6919                          || kind == WhiteCapturesEnPassant
6920                          || kind == BlackCapturesEnPassant);
6921     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6922 }
6923
6924 void
6925 MarkTargetSquares (int clear)
6926 {
6927   int x, y;
6928   if(clear) // no reason to ever suppress clearing
6929     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6930   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6931      !appData.testLegality || gameMode == EditPosition) return;
6932   if(!clear) {
6933     int capt = 0;
6934     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6935     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6936       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6937       if(capt)
6938       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6939     }
6940   }
6941   DrawPosition(FALSE, NULL);
6942 }
6943
6944 int
6945 Explode (Board board, int fromX, int fromY, int toX, int toY)
6946 {
6947     if(gameInfo.variant == VariantAtomic &&
6948        (board[toY][toX] != EmptySquare ||                     // capture?
6949         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6950                          board[fromY][fromX] == BlackPawn   )
6951       )) {
6952         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6953         return TRUE;
6954     }
6955     return FALSE;
6956 }
6957
6958 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6959
6960 int
6961 CanPromote (ChessSquare piece, int y)
6962 {
6963         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6964         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6965         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6966            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6967            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6968                                                   gameInfo.variant == VariantMakruk) return FALSE;
6969         return (piece == BlackPawn && y == 1 ||
6970                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6971                 piece == BlackLance && y == 1 ||
6972                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6973 }
6974
6975 void
6976 LeftClick (ClickType clickType, int xPix, int yPix)
6977 {
6978     int x, y;
6979     Boolean saveAnimate;
6980     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
6981     char promoChoice = NULLCHAR;
6982     ChessSquare piece;
6983     static TimeMark lastClickTime, prevClickTime;
6984
6985     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
6986
6987     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
6988
6989     if (clickType == Press) ErrorPopDown();
6990
6991     x = EventToSquare(xPix, BOARD_WIDTH);
6992     y = EventToSquare(yPix, BOARD_HEIGHT);
6993     if (!flipView && y >= 0) {
6994         y = BOARD_HEIGHT - 1 - y;
6995     }
6996     if (flipView && x >= 0) {
6997         x = BOARD_WIDTH - 1 - x;
6998     }
6999
7000     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7001         defaultPromoChoice = promoSweep;
7002         promoSweep = EmptySquare;   // terminate sweep
7003         promoDefaultAltered = TRUE;
7004         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7005     }
7006
7007     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7008         if(clickType == Release) return; // ignore upclick of click-click destination
7009         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7010         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7011         if(gameInfo.holdingsWidth &&
7012                 (WhiteOnMove(currentMove)
7013                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7014                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7015             // click in right holdings, for determining promotion piece
7016             ChessSquare p = boards[currentMove][y][x];
7017             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7018             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7019             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7020                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7021                 fromX = fromY = -1;
7022                 return;
7023             }
7024         }
7025         DrawPosition(FALSE, boards[currentMove]);
7026         return;
7027     }
7028
7029     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7030     if(clickType == Press
7031             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7032               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7033               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7034         return;
7035
7036     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7037         // could be static click on premove from-square: abort premove
7038         gotPremove = 0;
7039         ClearPremoveHighlights();
7040     }
7041
7042     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7043         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7044
7045     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7046         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7047                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7048         defaultPromoChoice = DefaultPromoChoice(side);
7049     }
7050
7051     autoQueen = appData.alwaysPromoteToQueen;
7052
7053     if (fromX == -1) {
7054       int originalY = y;
7055       gatingPiece = EmptySquare;
7056       if (clickType != Press) {
7057         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7058             DragPieceEnd(xPix, yPix); dragging = 0;
7059             DrawPosition(FALSE, NULL);
7060         }
7061         return;
7062       }
7063       doubleClick = FALSE;
7064       fromX = x; fromY = y; toX = toY = -1;
7065       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7066          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7067          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7068             /* First square */
7069             if (OKToStartUserMove(fromX, fromY)) {
7070                 second = 0;
7071                 MarkTargetSquares(0);
7072                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7073                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7074                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7075                     promoSweep = defaultPromoChoice;
7076                     selectFlag = 0; lastX = xPix; lastY = yPix;
7077                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7078                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7079                 }
7080                 if (appData.highlightDragging) {
7081                     SetHighlights(fromX, fromY, -1, -1);
7082                 } else {
7083                     ClearHighlights();
7084                 }
7085             } else fromX = fromY = -1;
7086             return;
7087         }
7088     }
7089
7090     /* fromX != -1 */
7091     if (clickType == Press && gameMode != EditPosition) {
7092         ChessSquare fromP;
7093         ChessSquare toP;
7094         int frc;
7095
7096         // ignore off-board to clicks
7097         if(y < 0 || x < 0) return;
7098
7099         /* Check if clicking again on the same color piece */
7100         fromP = boards[currentMove][fromY][fromX];
7101         toP = boards[currentMove][y][x];
7102         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7103         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7104              WhitePawn <= toP && toP <= WhiteKing &&
7105              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7106              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7107             (BlackPawn <= fromP && fromP <= BlackKing &&
7108              BlackPawn <= toP && toP <= BlackKing &&
7109              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7110              !(fromP == BlackKing && toP == BlackRook && frc))) {
7111             /* Clicked again on same color piece -- changed his mind */
7112             second = (x == fromX && y == fromY);
7113             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7114                 second = FALSE; // first double-click rather than scond click
7115                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7116             }
7117             promoDefaultAltered = FALSE;
7118             MarkTargetSquares(1);
7119            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7120             if (appData.highlightDragging) {
7121                 SetHighlights(x, y, -1, -1);
7122             } else {
7123                 ClearHighlights();
7124             }
7125             if (OKToStartUserMove(x, y)) {
7126                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7127                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7128                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7129                  gatingPiece = boards[currentMove][fromY][fromX];
7130                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7131                 fromX = x;
7132                 fromY = y; dragging = 1;
7133                 MarkTargetSquares(0);
7134                 DragPieceBegin(xPix, yPix, FALSE);
7135                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7136                     promoSweep = defaultPromoChoice;
7137                     selectFlag = 0; lastX = xPix; lastY = yPix;
7138                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7139                 }
7140             }
7141            }
7142            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7143            second = FALSE; 
7144         }
7145         // ignore clicks on holdings
7146         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7147     }
7148
7149     if (clickType == Release && x == fromX && y == fromY) {
7150         DragPieceEnd(xPix, yPix); dragging = 0;
7151         if(clearFlag) {
7152             // a deferred attempt to click-click move an empty square on top of a piece
7153             boards[currentMove][y][x] = EmptySquare;
7154             ClearHighlights();
7155             DrawPosition(FALSE, boards[currentMove]);
7156             fromX = fromY = -1; clearFlag = 0;
7157             return;
7158         }
7159         if (appData.animateDragging) {
7160             /* Undo animation damage if any */
7161             DrawPosition(FALSE, NULL);
7162         }
7163         if (second || sweepSelecting) {
7164             /* Second up/down in same square; just abort move */
7165             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7166             second = sweepSelecting = 0;
7167             fromX = fromY = -1;
7168             gatingPiece = EmptySquare;
7169             ClearHighlights();
7170             gotPremove = 0;
7171             ClearPremoveHighlights();
7172         } else {
7173             /* First upclick in same square; start click-click mode */
7174             SetHighlights(x, y, -1, -1);
7175         }
7176         return;
7177     }
7178
7179     clearFlag = 0;
7180
7181     /* we now have a different from- and (possibly off-board) to-square */
7182     /* Completed move */
7183     if(!sweepSelecting) {
7184         toX = x;
7185         toY = y;
7186         saveAnimate = appData.animate;
7187     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7188
7189     if (clickType == Press) {
7190         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7191             // must be Edit Position mode with empty-square selected
7192             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7193             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7194             return;
7195         }
7196         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7197           if(appData.sweepSelect) {
7198             ChessSquare piece = boards[currentMove][fromY][fromX];
7199             promoSweep = defaultPromoChoice;
7200             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7201             selectFlag = 0; lastX = xPix; lastY = yPix;
7202             Sweep(0); // Pawn that is going to promote: preview promotion piece
7203             sweepSelecting = 1;
7204             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7205             MarkTargetSquares(1);
7206           }
7207           return; // promo popup appears on up-click
7208         }
7209         /* Finish clickclick move */
7210         if (appData.animate || appData.highlightLastMove) {
7211             SetHighlights(fromX, fromY, toX, toY);
7212         } else {
7213             ClearHighlights();
7214         }
7215     } else {
7216         /* Finish drag move */
7217         if (appData.highlightLastMove) {
7218             SetHighlights(fromX, fromY, toX, toY);
7219         } else {
7220             ClearHighlights();
7221         }
7222         DragPieceEnd(xPix, yPix); dragging = 0;
7223         /* Don't animate move and drag both */
7224         appData.animate = FALSE;
7225     }
7226     MarkTargetSquares(1);
7227
7228     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7229     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7230         ChessSquare piece = boards[currentMove][fromY][fromX];
7231         if(gameMode == EditPosition && piece != EmptySquare &&
7232            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7233             int n;
7234
7235             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7236                 n = PieceToNumber(piece - (int)BlackPawn);
7237                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7238                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7239                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7240             } else
7241             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7242                 n = PieceToNumber(piece);
7243                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7244                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7245                 boards[currentMove][n][BOARD_WIDTH-2]++;
7246             }
7247             boards[currentMove][fromY][fromX] = EmptySquare;
7248         }
7249         ClearHighlights();
7250         fromX = fromY = -1;
7251         DrawPosition(TRUE, boards[currentMove]);
7252         return;
7253     }
7254
7255     // off-board moves should not be highlighted
7256     if(x < 0 || y < 0) ClearHighlights();
7257
7258     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7259
7260     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7261         SetHighlights(fromX, fromY, toX, toY);
7262         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7263             // [HGM] super: promotion to captured piece selected from holdings
7264             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7265             promotionChoice = TRUE;
7266             // kludge follows to temporarily execute move on display, without promoting yet
7267             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7268             boards[currentMove][toY][toX] = p;
7269             DrawPosition(FALSE, boards[currentMove]);
7270             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7271             boards[currentMove][toY][toX] = q;
7272             DisplayMessage("Click in holdings to choose piece", "");
7273             return;
7274         }
7275         PromotionPopUp();
7276     } else {
7277         int oldMove = currentMove;
7278         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7279         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7280         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7281         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7282            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7283             DrawPosition(TRUE, boards[currentMove]);
7284         fromX = fromY = -1;
7285     }
7286     appData.animate = saveAnimate;
7287     if (appData.animate || appData.animateDragging) {
7288         /* Undo animation damage if needed */
7289         DrawPosition(FALSE, NULL);
7290     }
7291 }
7292
7293 int
7294 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7295 {   // front-end-free part taken out of PieceMenuPopup
7296     int whichMenu; int xSqr, ySqr;
7297
7298     if(seekGraphUp) { // [HGM] seekgraph
7299         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7300         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7301         return -2;
7302     }
7303
7304     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7305          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7306         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7307         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7308         if(action == Press)   {
7309             originalFlip = flipView;
7310             flipView = !flipView; // temporarily flip board to see game from partners perspective
7311             DrawPosition(TRUE, partnerBoard);
7312             DisplayMessage(partnerStatus, "");
7313             partnerUp = TRUE;
7314         } else if(action == Release) {
7315             flipView = originalFlip;
7316             DrawPosition(TRUE, boards[currentMove]);
7317             partnerUp = FALSE;
7318         }
7319         return -2;
7320     }
7321
7322     xSqr = EventToSquare(x, BOARD_WIDTH);
7323     ySqr = EventToSquare(y, BOARD_HEIGHT);
7324     if (action == Release) {
7325         if(pieceSweep != EmptySquare) {
7326             EditPositionMenuEvent(pieceSweep, toX, toY);
7327             pieceSweep = EmptySquare;
7328         } else UnLoadPV(); // [HGM] pv
7329     }
7330     if (action != Press) return -2; // return code to be ignored
7331     switch (gameMode) {
7332       case IcsExamining:
7333         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7334       case EditPosition:
7335         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7336         if (xSqr < 0 || ySqr < 0) return -1;
7337         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7338         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7339         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7340         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7341         NextPiece(0);
7342         return 2; // grab
7343       case IcsObserving:
7344         if(!appData.icsEngineAnalyze) return -1;
7345       case IcsPlayingWhite:
7346       case IcsPlayingBlack:
7347         if(!appData.zippyPlay) goto noZip;
7348       case AnalyzeMode:
7349       case AnalyzeFile:
7350       case MachinePlaysWhite:
7351       case MachinePlaysBlack:
7352       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7353         if (!appData.dropMenu) {
7354           LoadPV(x, y);
7355           return 2; // flag front-end to grab mouse events
7356         }
7357         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7358            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7359       case EditGame:
7360       noZip:
7361         if (xSqr < 0 || ySqr < 0) return -1;
7362         if (!appData.dropMenu || appData.testLegality &&
7363             gameInfo.variant != VariantBughouse &&
7364             gameInfo.variant != VariantCrazyhouse) return -1;
7365         whichMenu = 1; // drop menu
7366         break;
7367       default:
7368         return -1;
7369     }
7370
7371     if (((*fromX = xSqr) < 0) ||
7372         ((*fromY = ySqr) < 0)) {
7373         *fromX = *fromY = -1;
7374         return -1;
7375     }
7376     if (flipView)
7377       *fromX = BOARD_WIDTH - 1 - *fromX;
7378     else
7379       *fromY = BOARD_HEIGHT - 1 - *fromY;
7380
7381     return whichMenu;
7382 }
7383
7384 void
7385 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7386 {
7387 //    char * hint = lastHint;
7388     FrontEndProgramStats stats;
7389
7390     stats.which = cps == &first ? 0 : 1;
7391     stats.depth = cpstats->depth;
7392     stats.nodes = cpstats->nodes;
7393     stats.score = cpstats->score;
7394     stats.time = cpstats->time;
7395     stats.pv = cpstats->movelist;
7396     stats.hint = lastHint;
7397     stats.an_move_index = 0;
7398     stats.an_move_count = 0;
7399
7400     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7401         stats.hint = cpstats->move_name;
7402         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7403         stats.an_move_count = cpstats->nr_moves;
7404     }
7405
7406     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
7407
7408     SetProgramStats( &stats );
7409 }
7410
7411 void
7412 ClearEngineOutputPane (int which)
7413 {
7414     static FrontEndProgramStats dummyStats;
7415     dummyStats.which = which;
7416     dummyStats.pv = "#";
7417     SetProgramStats( &dummyStats );
7418 }
7419
7420 #define MAXPLAYERS 500
7421
7422 char *
7423 TourneyStandings (int display)
7424 {
7425     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7426     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7427     char result, *p, *names[MAXPLAYERS];
7428
7429     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7430         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7431     names[0] = p = strdup(appData.participants);
7432     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7433
7434     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7435
7436     while(result = appData.results[nr]) {
7437         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7438         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7439         wScore = bScore = 0;
7440         switch(result) {
7441           case '+': wScore = 2; break;
7442           case '-': bScore = 2; break;
7443           case '=': wScore = bScore = 1; break;
7444           case ' ':
7445           case '*': return strdup("busy"); // tourney not finished
7446         }
7447         score[w] += wScore;
7448         score[b] += bScore;
7449         games[w]++;
7450         games[b]++;
7451         nr++;
7452     }
7453     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7454     for(w=0; w<nPlayers; w++) {
7455         bScore = -1;
7456         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7457         ranking[w] = b; points[w] = bScore; score[b] = -2;
7458     }
7459     p = malloc(nPlayers*34+1);
7460     for(w=0; w<nPlayers && w<display; w++)
7461         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7462     free(names[0]);
7463     return p;
7464 }
7465
7466 void
7467 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7468 {       // count all piece types
7469         int p, f, r;
7470         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7471         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7472         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7473                 p = board[r][f];
7474                 pCnt[p]++;
7475                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7476                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7477                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7478                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7479                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7480                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7481         }
7482 }
7483
7484 int
7485 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7486 {
7487         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7488         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7489
7490         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7491         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7492         if(myPawns == 2 && nMine == 3) // KPP
7493             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7494         if(myPawns == 1 && nMine == 2) // KP
7495             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7496         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7497             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7498         if(myPawns) return FALSE;
7499         if(pCnt[WhiteRook+side])
7500             return pCnt[BlackRook-side] ||
7501                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7502                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7503                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7504         if(pCnt[WhiteCannon+side]) {
7505             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7506             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7507         }
7508         if(pCnt[WhiteKnight+side])
7509             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7510         return FALSE;
7511 }
7512
7513 int
7514 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7515 {
7516         VariantClass v = gameInfo.variant;
7517
7518         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7519         if(v == VariantShatranj) return TRUE; // always winnable through baring
7520         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7521         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7522
7523         if(v == VariantXiangqi) {
7524                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7525
7526                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7527                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7528                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7529                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7530                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7531                 if(stale) // we have at least one last-rank P plus perhaps C
7532                     return majors // KPKX
7533                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7534                 else // KCA*E*
7535                     return pCnt[WhiteFerz+side] // KCAK
7536                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7537                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7538                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7539
7540         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7541                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7542
7543                 if(nMine == 1) return FALSE; // bare King
7544                 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
7545                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7546                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7547                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7548                 if(pCnt[WhiteKnight+side])
7549                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7550                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7551                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7552                 if(nBishops)
7553                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7554                 if(pCnt[WhiteAlfil+side])
7555                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7556                 if(pCnt[WhiteWazir+side])
7557                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7558         }
7559
7560         return TRUE;
7561 }
7562
7563 int
7564 CompareWithRights (Board b1, Board b2)
7565 {
7566     int rights = 0;
7567     if(!CompareBoards(b1, b2)) return FALSE;
7568     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7569     /* compare castling rights */
7570     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7571            rights++; /* King lost rights, while rook still had them */
7572     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7573         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7574            rights++; /* but at least one rook lost them */
7575     }
7576     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7577            rights++;
7578     if( b1[CASTLING][5] != NoRights ) {
7579         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7580            rights++;
7581     }
7582     return rights == 0;
7583 }
7584
7585 int
7586 Adjudicate (ChessProgramState *cps)
7587 {       // [HGM] some adjudications useful with buggy engines
7588         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7589         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7590         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7591         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7592         int k, count = 0; static int bare = 1;
7593         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7594         Boolean canAdjudicate = !appData.icsActive;
7595
7596         // most tests only when we understand the game, i.e. legality-checking on
7597             if( appData.testLegality )
7598             {   /* [HGM] Some more adjudications for obstinate engines */
7599                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7600                 static int moveCount = 6;
7601                 ChessMove result;
7602                 char *reason = NULL;
7603
7604                 /* Count what is on board. */
7605                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7606
7607                 /* Some material-based adjudications that have to be made before stalemate test */
7608                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7609                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7610                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7611                      if(canAdjudicate && appData.checkMates) {
7612                          if(engineOpponent)
7613                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7614                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7615                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7616                          return 1;
7617                      }
7618                 }
7619
7620                 /* Bare King in Shatranj (loses) or Losers (wins) */
7621                 if( nrW == 1 || nrB == 1) {
7622                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7623                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7624                      if(canAdjudicate && appData.checkMates) {
7625                          if(engineOpponent)
7626                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7627                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7628                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7629                          return 1;
7630                      }
7631                   } else
7632                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7633                   {    /* bare King */
7634                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7635                         if(canAdjudicate && appData.checkMates) {
7636                             /* but only adjudicate if adjudication enabled */
7637                             if(engineOpponent)
7638                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7639                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7640                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7641                             return 1;
7642                         }
7643                   }
7644                 } else bare = 1;
7645
7646
7647             // don't wait for engine to announce game end if we can judge ourselves
7648             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7649               case MT_CHECK:
7650                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7651                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7652                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7653                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7654                             checkCnt++;
7655                         if(checkCnt >= 2) {
7656                             reason = "Xboard adjudication: 3rd check";
7657                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7658                             break;
7659                         }
7660                     }
7661                 }
7662               case MT_NONE:
7663               default:
7664                 break;
7665               case MT_STALEMATE:
7666               case MT_STAINMATE:
7667                 reason = "Xboard adjudication: Stalemate";
7668                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7669                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7670                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7671                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7672                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7673                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7674                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7675                                                                         EP_CHECKMATE : EP_WINS);
7676                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7677                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7678                 }
7679                 break;
7680               case MT_CHECKMATE:
7681                 reason = "Xboard adjudication: Checkmate";
7682                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7683                 break;
7684             }
7685
7686                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7687                     case EP_STALEMATE:
7688                         result = GameIsDrawn; break;
7689                     case EP_CHECKMATE:
7690                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7691                     case EP_WINS:
7692                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7693                     default:
7694                         result = EndOfFile;
7695                 }
7696                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7697                     if(engineOpponent)
7698                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7699                     GameEnds( result, reason, GE_XBOARD );
7700                     return 1;
7701                 }
7702
7703                 /* Next absolutely insufficient mating material. */
7704                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7705                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7706                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7707
7708                      /* always flag draws, for judging claims */
7709                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7710
7711                      if(canAdjudicate && appData.materialDraws) {
7712                          /* but only adjudicate them if adjudication enabled */
7713                          if(engineOpponent) {
7714                            SendToProgram("force\n", engineOpponent); // suppress reply
7715                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7716                          }
7717                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7718                          return 1;
7719                      }
7720                 }
7721
7722                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7723                 if(gameInfo.variant == VariantXiangqi ?
7724                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7725                  : nrW + nrB == 4 &&
7726                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7727                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7728                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7729                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7730                    ) ) {
7731                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7732                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7733                           if(engineOpponent) {
7734                             SendToProgram("force\n", engineOpponent); // suppress reply
7735                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7736                           }
7737                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7738                           return 1;
7739                      }
7740                 } else moveCount = 6;
7741             }
7742
7743         // Repetition draws and 50-move rule can be applied independently of legality testing
7744
7745                 /* Check for rep-draws */
7746                 count = 0;
7747                 for(k = forwardMostMove-2;
7748                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7749                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7750                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7751                     k-=2)
7752                 {   int rights=0;
7753                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7754                         /* compare castling rights */
7755                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7756                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7757                                 rights++; /* King lost rights, while rook still had them */
7758                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7759                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7760                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7761                                    rights++; /* but at least one rook lost them */
7762                         }
7763                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7764                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7765                                 rights++;
7766                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7767                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7768                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7769                                    rights++;
7770                         }
7771                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7772                             && appData.drawRepeats > 1) {
7773                              /* adjudicate after user-specified nr of repeats */
7774                              int result = GameIsDrawn;
7775                              char *details = "XBoard adjudication: repetition draw";
7776                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7777                                 // [HGM] xiangqi: check for forbidden perpetuals
7778                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7779                                 for(m=forwardMostMove; m>k; m-=2) {
7780                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7781                                         ourPerpetual = 0; // the current mover did not always check
7782                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7783                                         hisPerpetual = 0; // the opponent did not always check
7784                                 }
7785                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7786                                                                         ourPerpetual, hisPerpetual);
7787                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7788                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7789                                     details = "Xboard adjudication: perpetual checking";
7790                                 } else
7791                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7792                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7793                                 } else
7794                                 // Now check for perpetual chases
7795                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7796                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7797                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7798                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7799                                         static char resdet[MSG_SIZ];
7800                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7801                                         details = resdet;
7802                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7803                                     } else
7804                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7805                                         break; // Abort repetition-checking loop.
7806                                 }
7807                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7808                              }
7809                              if(engineOpponent) {
7810                                SendToProgram("force\n", engineOpponent); // suppress reply
7811                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7812                              }
7813                              GameEnds( result, details, GE_XBOARD );
7814                              return 1;
7815                         }
7816                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7817                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7818                     }
7819                 }
7820
7821                 /* Now we test for 50-move draws. Determine ply count */
7822                 count = forwardMostMove;
7823                 /* look for last irreversble move */
7824                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7825                     count--;
7826                 /* if we hit starting position, add initial plies */
7827                 if( count == backwardMostMove )
7828                     count -= initialRulePlies;
7829                 count = forwardMostMove - count;
7830                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7831                         // adjust reversible move counter for checks in Xiangqi
7832                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7833                         if(i < backwardMostMove) i = backwardMostMove;
7834                         while(i <= forwardMostMove) {
7835                                 lastCheck = inCheck; // check evasion does not count
7836                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7837                                 if(inCheck || lastCheck) count--; // check does not count
7838                                 i++;
7839                         }
7840                 }
7841                 if( count >= 100)
7842                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7843                          /* this is used to judge if draw claims are legal */
7844                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7845                          if(engineOpponent) {
7846                            SendToProgram("force\n", engineOpponent); // suppress reply
7847                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7848                          }
7849                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7850                          return 1;
7851                 }
7852
7853                 /* if draw offer is pending, treat it as a draw claim
7854                  * when draw condition present, to allow engines a way to
7855                  * claim draws before making their move to avoid a race
7856                  * condition occurring after their move
7857                  */
7858                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7859                          char *p = NULL;
7860                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7861                              p = "Draw claim: 50-move rule";
7862                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7863                              p = "Draw claim: 3-fold repetition";
7864                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7865                              p = "Draw claim: insufficient mating material";
7866                          if( p != NULL && canAdjudicate) {
7867                              if(engineOpponent) {
7868                                SendToProgram("force\n", engineOpponent); // suppress reply
7869                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7870                              }
7871                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7872                              return 1;
7873                          }
7874                 }
7875
7876                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7877                     if(engineOpponent) {
7878                       SendToProgram("force\n", engineOpponent); // suppress reply
7879                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7880                     }
7881                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7882                     return 1;
7883                 }
7884         return 0;
7885 }
7886
7887 char *
7888 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7889 {   // [HGM] book: this routine intercepts moves to simulate book replies
7890     char *bookHit = NULL;
7891
7892     //first determine if the incoming move brings opponent into his book
7893     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7894         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7895     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7896     if(bookHit != NULL && !cps->bookSuspend) {
7897         // make sure opponent is not going to reply after receiving move to book position
7898         SendToProgram("force\n", cps);
7899         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7900     }
7901     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7902     // now arrange restart after book miss
7903     if(bookHit) {
7904         // after a book hit we never send 'go', and the code after the call to this routine
7905         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7906         char buf[MSG_SIZ], *move = bookHit;
7907         if(cps->useSAN) {
7908             int fromX, fromY, toX, toY;
7909             char promoChar;
7910             ChessMove moveType;
7911             move = buf + 30;
7912             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7913                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7914                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7915                                     PosFlags(forwardMostMove),
7916                                     fromY, fromX, toY, toX, promoChar, move);
7917             } else {
7918                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7919                 bookHit = NULL;
7920             }
7921         }
7922         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7923         SendToProgram(buf, cps);
7924         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7925     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7926         SendToProgram("go\n", cps);
7927         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7928     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7929         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7930             SendToProgram("go\n", cps);
7931         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7932     }
7933     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7934 }
7935
7936 int
7937 LoadError (char *errmess, ChessProgramState *cps)
7938 {   // unloads engine and switches back to -ncp mode if it was first
7939     if(cps->initDone) return FALSE;
7940     cps->isr = NULL; // this should suppress further error popups from breaking pipes
7941     DestroyChildProcess(cps->pr, 9 ); // just to be sure
7942     cps->pr = NoProc; 
7943     if(cps == &first) {
7944         appData.noChessProgram = TRUE;
7945         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
7946         gameMode = BeginningOfGame; ModeHighlight();
7947         SetNCPMode();
7948     }
7949     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
7950     DisplayMessage("", ""); // erase waiting message
7951     if(errmess) DisplayError(errmess, 0); // announce reason, if given
7952     return TRUE;
7953 }
7954
7955 char *savedMessage;
7956 ChessProgramState *savedState;
7957 void
7958 DeferredBookMove (void)
7959 {
7960         if(savedState->lastPing != savedState->lastPong)
7961                     ScheduleDelayedEvent(DeferredBookMove, 10);
7962         else
7963         HandleMachineMove(savedMessage, savedState);
7964 }
7965
7966 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7967
7968 void
7969 HandleMachineMove (char *message, ChessProgramState *cps)
7970 {
7971     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7972     char realname[MSG_SIZ];
7973     int fromX, fromY, toX, toY;
7974     ChessMove moveType;
7975     char promoChar;
7976     char *p, *pv=buf1;
7977     int machineWhite, oldError;
7978     char *bookHit;
7979
7980     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7981         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7982         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7983             DisplayError(_("Invalid pairing from pairing engine"), 0);
7984             return;
7985         }
7986         pairingReceived = 1;
7987         NextMatchGame();
7988         return; // Skim the pairing messages here.
7989     }
7990
7991     oldError = cps->userError; cps->userError = 0;
7992
7993 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7994     /*
7995      * Kludge to ignore BEL characters
7996      */
7997     while (*message == '\007') message++;
7998
7999     /*
8000      * [HGM] engine debug message: ignore lines starting with '#' character
8001      */
8002     if(cps->debug && *message == '#') return;
8003
8004     /*
8005      * Look for book output
8006      */
8007     if (cps == &first && bookRequested) {
8008         if (message[0] == '\t' || message[0] == ' ') {
8009             /* Part of the book output is here; append it */
8010             strcat(bookOutput, message);
8011             strcat(bookOutput, "  \n");
8012             return;
8013         } else if (bookOutput[0] != NULLCHAR) {
8014             /* All of book output has arrived; display it */
8015             char *p = bookOutput;
8016             while (*p != NULLCHAR) {
8017                 if (*p == '\t') *p = ' ';
8018                 p++;
8019             }
8020             DisplayInformation(bookOutput);
8021             bookRequested = FALSE;
8022             /* Fall through to parse the current output */
8023         }
8024     }
8025
8026     /*
8027      * Look for machine move.
8028      */
8029     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8030         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8031     {
8032         /* This method is only useful on engines that support ping */
8033         if (cps->lastPing != cps->lastPong) {
8034           if (gameMode == BeginningOfGame) {
8035             /* Extra move from before last new; ignore */
8036             if (appData.debugMode) {
8037                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8038             }
8039           } else {
8040             if (appData.debugMode) {
8041                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8042                         cps->which, gameMode);
8043             }
8044
8045             SendToProgram("undo\n", cps);
8046           }
8047           return;
8048         }
8049
8050         switch (gameMode) {
8051           case BeginningOfGame:
8052             /* Extra move from before last reset; ignore */
8053             if (appData.debugMode) {
8054                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8055             }
8056             return;
8057
8058           case EndOfGame:
8059           case IcsIdle:
8060           default:
8061             /* Extra move after we tried to stop.  The mode test is
8062                not a reliable way of detecting this problem, but it's
8063                the best we can do on engines that don't support ping.
8064             */
8065             if (appData.debugMode) {
8066                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8067                         cps->which, gameMode);
8068             }
8069             SendToProgram("undo\n", cps);
8070             return;
8071
8072           case MachinePlaysWhite:
8073           case IcsPlayingWhite:
8074             machineWhite = TRUE;
8075             break;
8076
8077           case MachinePlaysBlack:
8078           case IcsPlayingBlack:
8079             machineWhite = FALSE;
8080             break;
8081
8082           case TwoMachinesPlay:
8083             machineWhite = (cps->twoMachinesColor[0] == 'w');
8084             break;
8085         }
8086         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8087             if (appData.debugMode) {
8088                 fprintf(debugFP,
8089                         "Ignoring move out of turn by %s, gameMode %d"
8090                         ", forwardMost %d\n",
8091                         cps->which, gameMode, forwardMostMove);
8092             }
8093             return;
8094         }
8095
8096         if(cps->alphaRank) AlphaRank(machineMove, 4);
8097         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8098                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8099             /* Machine move could not be parsed; ignore it. */
8100           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8101                     machineMove, _(cps->which));
8102             DisplayError(buf1, 0);
8103             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8104                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8105             if (gameMode == TwoMachinesPlay) {
8106               GameEnds(machineWhite ? BlackWins : WhiteWins,
8107                        buf1, GE_XBOARD);
8108             }
8109             return;
8110         }
8111
8112         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8113         /* So we have to redo legality test with true e.p. status here,  */
8114         /* to make sure an illegal e.p. capture does not slip through,   */
8115         /* to cause a forfeit on a justified illegal-move complaint      */
8116         /* of the opponent.                                              */
8117         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8118            ChessMove moveType;
8119            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8120                              fromY, fromX, toY, toX, promoChar);
8121             if(moveType == IllegalMove) {
8122               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8123                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8124                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8125                            buf1, GE_XBOARD);
8126                 return;
8127            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8128            /* [HGM] Kludge to handle engines that send FRC-style castling
8129               when they shouldn't (like TSCP-Gothic) */
8130            switch(moveType) {
8131              case WhiteASideCastleFR:
8132              case BlackASideCastleFR:
8133                toX+=2;
8134                currentMoveString[2]++;
8135                break;
8136              case WhiteHSideCastleFR:
8137              case BlackHSideCastleFR:
8138                toX--;
8139                currentMoveString[2]--;
8140                break;
8141              default: ; // nothing to do, but suppresses warning of pedantic compilers
8142            }
8143         }
8144         hintRequested = FALSE;
8145         lastHint[0] = NULLCHAR;
8146         bookRequested = FALSE;
8147         /* Program may be pondering now */
8148         cps->maybeThinking = TRUE;
8149         if (cps->sendTime == 2) cps->sendTime = 1;
8150         if (cps->offeredDraw) cps->offeredDraw--;
8151
8152         /* [AS] Save move info*/
8153         pvInfoList[ forwardMostMove ].score = programStats.score;
8154         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8155         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8156
8157         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8158
8159         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8160         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8161             int count = 0;
8162
8163             while( count < adjudicateLossPlies ) {
8164                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8165
8166                 if( count & 1 ) {
8167                     score = -score; /* Flip score for winning side */
8168                 }
8169
8170                 if( score > adjudicateLossThreshold ) {
8171                     break;
8172                 }
8173
8174                 count++;
8175             }
8176
8177             if( count >= adjudicateLossPlies ) {
8178                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8179
8180                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8181                     "Xboard adjudication",
8182                     GE_XBOARD );
8183
8184                 return;
8185             }
8186         }
8187
8188         if(Adjudicate(cps)) {
8189             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8190             return; // [HGM] adjudicate: for all automatic game ends
8191         }
8192
8193 #if ZIPPY
8194         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8195             first.initDone) {
8196           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8197                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8198                 SendToICS("draw ");
8199                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8200           }
8201           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8202           ics_user_moved = 1;
8203           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8204                 char buf[3*MSG_SIZ];
8205
8206                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8207                         programStats.score / 100.,
8208                         programStats.depth,
8209                         programStats.time / 100.,
8210                         (unsigned int)programStats.nodes,
8211                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8212                         programStats.movelist);
8213                 SendToICS(buf);
8214 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8215           }
8216         }
8217 #endif
8218
8219         /* [AS] Clear stats for next move */
8220         ClearProgramStats();
8221         thinkOutput[0] = NULLCHAR;
8222         hiddenThinkOutputState = 0;
8223
8224         bookHit = NULL;
8225         if (gameMode == TwoMachinesPlay) {
8226             /* [HGM] relaying draw offers moved to after reception of move */
8227             /* and interpreting offer as claim if it brings draw condition */
8228             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8229                 SendToProgram("draw\n", cps->other);
8230             }
8231             if (cps->other->sendTime) {
8232                 SendTimeRemaining(cps->other,
8233                                   cps->other->twoMachinesColor[0] == 'w');
8234             }
8235             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8236             if (firstMove && !bookHit) {
8237                 firstMove = FALSE;
8238                 if (cps->other->useColors) {
8239                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8240                 }
8241                 SendToProgram("go\n", cps->other);
8242             }
8243             cps->other->maybeThinking = TRUE;
8244         }
8245
8246         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8247
8248         if (!pausing && appData.ringBellAfterMoves) {
8249             RingBell();
8250         }
8251
8252         /*
8253          * Reenable menu items that were disabled while
8254          * machine was thinking
8255          */
8256         if (gameMode != TwoMachinesPlay)
8257             SetUserThinkingEnables();
8258
8259         // [HGM] book: after book hit opponent has received move and is now in force mode
8260         // force the book reply into it, and then fake that it outputted this move by jumping
8261         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8262         if(bookHit) {
8263                 static char bookMove[MSG_SIZ]; // a bit generous?
8264
8265                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8266                 strcat(bookMove, bookHit);
8267                 message = bookMove;
8268                 cps = cps->other;
8269                 programStats.nodes = programStats.depth = programStats.time =
8270                 programStats.score = programStats.got_only_move = 0;
8271                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8272
8273                 if(cps->lastPing != cps->lastPong) {
8274                     savedMessage = message; // args for deferred call
8275                     savedState = cps;
8276                     ScheduleDelayedEvent(DeferredBookMove, 10);
8277                     return;
8278                 }
8279                 goto FakeBookMove;
8280         }
8281
8282         return;
8283     }
8284
8285     /* Set special modes for chess engines.  Later something general
8286      *  could be added here; for now there is just one kludge feature,
8287      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8288      *  when "xboard" is given as an interactive command.
8289      */
8290     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8291         cps->useSigint = FALSE;
8292         cps->useSigterm = FALSE;
8293     }
8294     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8295       ParseFeatures(message+8, cps);
8296       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8297     }
8298
8299     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8300                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8301       int dummy, s=6; char buf[MSG_SIZ];
8302       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8303       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8304       if(startedFromSetupPosition) return;
8305       ParseFEN(boards[0], &dummy, message+s);
8306       DrawPosition(TRUE, boards[0]);
8307       startedFromSetupPosition = TRUE;
8308       return;
8309     }
8310     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8311      * want this, I was asked to put it in, and obliged.
8312      */
8313     if (!strncmp(message, "setboard ", 9)) {
8314         Board initial_position;
8315
8316         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8317
8318         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8319             DisplayError(_("Bad FEN received from engine"), 0);
8320             return ;
8321         } else {
8322            Reset(TRUE, FALSE);
8323            CopyBoard(boards[0], initial_position);
8324            initialRulePlies = FENrulePlies;
8325            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8326            else gameMode = MachinePlaysBlack;
8327            DrawPosition(FALSE, boards[currentMove]);
8328         }
8329         return;
8330     }
8331
8332     /*
8333      * Look for communication commands
8334      */
8335     if (!strncmp(message, "telluser ", 9)) {
8336         if(message[9] == '\\' && message[10] == '\\')
8337             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8338         PlayTellSound();
8339         DisplayNote(message + 9);
8340         return;
8341     }
8342     if (!strncmp(message, "tellusererror ", 14)) {
8343         cps->userError = 1;
8344         if(message[14] == '\\' && message[15] == '\\')
8345             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8346         PlayTellSound();
8347         DisplayError(message + 14, 0);
8348         return;
8349     }
8350     if (!strncmp(message, "tellopponent ", 13)) {
8351       if (appData.icsActive) {
8352         if (loggedOn) {
8353           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8354           SendToICS(buf1);
8355         }
8356       } else {
8357         DisplayNote(message + 13);
8358       }
8359       return;
8360     }
8361     if (!strncmp(message, "tellothers ", 11)) {
8362       if (appData.icsActive) {
8363         if (loggedOn) {
8364           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8365           SendToICS(buf1);
8366         }
8367       }
8368       return;
8369     }
8370     if (!strncmp(message, "tellall ", 8)) {
8371       if (appData.icsActive) {
8372         if (loggedOn) {
8373           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8374           SendToICS(buf1);
8375         }
8376       } else {
8377         DisplayNote(message + 8);
8378       }
8379       return;
8380     }
8381     if (strncmp(message, "warning", 7) == 0) {
8382         /* Undocumented feature, use tellusererror in new code */
8383         DisplayError(message, 0);
8384         return;
8385     }
8386     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8387         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8388         strcat(realname, " query");
8389         AskQuestion(realname, buf2, buf1, cps->pr);
8390         return;
8391     }
8392     /* Commands from the engine directly to ICS.  We don't allow these to be
8393      *  sent until we are logged on. Crafty kibitzes have been known to
8394      *  interfere with the login process.
8395      */
8396     if (loggedOn) {
8397         if (!strncmp(message, "tellics ", 8)) {
8398             SendToICS(message + 8);
8399             SendToICS("\n");
8400             return;
8401         }
8402         if (!strncmp(message, "tellicsnoalias ", 15)) {
8403             SendToICS(ics_prefix);
8404             SendToICS(message + 15);
8405             SendToICS("\n");
8406             return;
8407         }
8408         /* The following are for backward compatibility only */
8409         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8410             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8411             SendToICS(ics_prefix);
8412             SendToICS(message);
8413             SendToICS("\n");
8414             return;
8415         }
8416     }
8417     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8418         return;
8419     }
8420     /*
8421      * If the move is illegal, cancel it and redraw the board.
8422      * Also deal with other error cases.  Matching is rather loose
8423      * here to accommodate engines written before the spec.
8424      */
8425     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8426         strncmp(message, "Error", 5) == 0) {
8427         if (StrStr(message, "name") ||
8428             StrStr(message, "rating") || StrStr(message, "?") ||
8429             StrStr(message, "result") || StrStr(message, "board") ||
8430             StrStr(message, "bk") || StrStr(message, "computer") ||
8431             StrStr(message, "variant") || StrStr(message, "hint") ||
8432             StrStr(message, "random") || StrStr(message, "depth") ||
8433             StrStr(message, "accepted")) {
8434             return;
8435         }
8436         if (StrStr(message, "protover")) {
8437           /* Program is responding to input, so it's apparently done
8438              initializing, and this error message indicates it is
8439              protocol version 1.  So we don't need to wait any longer
8440              for it to initialize and send feature commands. */
8441           FeatureDone(cps, 1);
8442           cps->protocolVersion = 1;
8443           return;
8444         }
8445         cps->maybeThinking = FALSE;
8446
8447         if (StrStr(message, "draw")) {
8448             /* Program doesn't have "draw" command */
8449             cps->sendDrawOffers = 0;
8450             return;
8451         }
8452         if (cps->sendTime != 1 &&
8453             (StrStr(message, "time") || StrStr(message, "otim"))) {
8454           /* Program apparently doesn't have "time" or "otim" command */
8455           cps->sendTime = 0;
8456           return;
8457         }
8458         if (StrStr(message, "analyze")) {
8459             cps->analysisSupport = FALSE;
8460             cps->analyzing = FALSE;
8461 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8462             EditGameEvent(); // [HGM] try to preserve loaded game
8463             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8464             DisplayError(buf2, 0);
8465             return;
8466         }
8467         if (StrStr(message, "(no matching move)st")) {
8468           /* Special kludge for GNU Chess 4 only */
8469           cps->stKludge = TRUE;
8470           SendTimeControl(cps, movesPerSession, timeControl,
8471                           timeIncrement, appData.searchDepth,
8472                           searchTime);
8473           return;
8474         }
8475         if (StrStr(message, "(no matching move)sd")) {
8476           /* Special kludge for GNU Chess 4 only */
8477           cps->sdKludge = TRUE;
8478           SendTimeControl(cps, movesPerSession, timeControl,
8479                           timeIncrement, appData.searchDepth,
8480                           searchTime);
8481           return;
8482         }
8483         if (!StrStr(message, "llegal")) {
8484             return;
8485         }
8486         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8487             gameMode == IcsIdle) return;
8488         if (forwardMostMove <= backwardMostMove) return;
8489         if (pausing) PauseEvent();
8490       if(appData.forceIllegal) {
8491             // [HGM] illegal: machine refused move; force position after move into it
8492           SendToProgram("force\n", cps);
8493           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8494                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8495                 // when black is to move, while there might be nothing on a2 or black
8496                 // might already have the move. So send the board as if white has the move.
8497                 // But first we must change the stm of the engine, as it refused the last move
8498                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8499                 if(WhiteOnMove(forwardMostMove)) {
8500                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8501                     SendBoard(cps, forwardMostMove); // kludgeless board
8502                 } else {
8503                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8504                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8505                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8506                 }
8507           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8508             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8509                  gameMode == TwoMachinesPlay)
8510               SendToProgram("go\n", cps);
8511             return;
8512       } else
8513         if (gameMode == PlayFromGameFile) {
8514             /* Stop reading this game file */
8515             gameMode = EditGame;
8516             ModeHighlight();
8517         }
8518         /* [HGM] illegal-move claim should forfeit game when Xboard */
8519         /* only passes fully legal moves                            */
8520         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8521             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8522                                 "False illegal-move claim", GE_XBOARD );
8523             return; // do not take back move we tested as valid
8524         }
8525         currentMove = forwardMostMove-1;
8526         DisplayMove(currentMove-1); /* before DisplayMoveError */
8527         SwitchClocks(forwardMostMove-1); // [HGM] race
8528         DisplayBothClocks();
8529         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8530                 parseList[currentMove], _(cps->which));
8531         DisplayMoveError(buf1);
8532         DrawPosition(FALSE, boards[currentMove]);
8533
8534         SetUserThinkingEnables();
8535         return;
8536     }
8537     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8538         /* Program has a broken "time" command that
8539            outputs a string not ending in newline.
8540            Don't use it. */
8541         cps->sendTime = 0;
8542     }
8543
8544     /*
8545      * If chess program startup fails, exit with an error message.
8546      * Attempts to recover here are futile. [HGM] Well, we try anyway
8547      */
8548     if ((StrStr(message, "unknown host") != NULL)
8549         || (StrStr(message, "No remote directory") != NULL)
8550         || (StrStr(message, "not found") != NULL)
8551         || (StrStr(message, "No such file") != NULL)
8552         || (StrStr(message, "can't alloc") != NULL)
8553         || (StrStr(message, "Permission denied") != NULL)) {
8554
8555         cps->maybeThinking = FALSE;
8556         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8557                 _(cps->which), cps->program, cps->host, message);
8558         RemoveInputSource(cps->isr);
8559         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8560             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8561             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8562         }
8563         return;
8564     }
8565
8566     /*
8567      * Look for hint output
8568      */
8569     if (sscanf(message, "Hint: %s", buf1) == 1) {
8570         if (cps == &first && hintRequested) {
8571             hintRequested = FALSE;
8572             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8573                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8574                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8575                                     PosFlags(forwardMostMove),
8576                                     fromY, fromX, toY, toX, promoChar, buf1);
8577                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8578                 DisplayInformation(buf2);
8579             } else {
8580                 /* Hint move could not be parsed!? */
8581               snprintf(buf2, sizeof(buf2),
8582                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8583                         buf1, _(cps->which));
8584                 DisplayError(buf2, 0);
8585             }
8586         } else {
8587           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8588         }
8589         return;
8590     }
8591
8592     /*
8593      * Ignore other messages if game is not in progress
8594      */
8595     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8596         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8597
8598     /*
8599      * look for win, lose, draw, or draw offer
8600      */
8601     if (strncmp(message, "1-0", 3) == 0) {
8602         char *p, *q, *r = "";
8603         p = strchr(message, '{');
8604         if (p) {
8605             q = strchr(p, '}');
8606             if (q) {
8607                 *q = NULLCHAR;
8608                 r = p + 1;
8609             }
8610         }
8611         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8612         return;
8613     } else if (strncmp(message, "0-1", 3) == 0) {
8614         char *p, *q, *r = "";
8615         p = strchr(message, '{');
8616         if (p) {
8617             q = strchr(p, '}');
8618             if (q) {
8619                 *q = NULLCHAR;
8620                 r = p + 1;
8621             }
8622         }
8623         /* Kludge for Arasan 4.1 bug */
8624         if (strcmp(r, "Black resigns") == 0) {
8625             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8626             return;
8627         }
8628         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8629         return;
8630     } else if (strncmp(message, "1/2", 3) == 0) {
8631         char *p, *q, *r = "";
8632         p = strchr(message, '{');
8633         if (p) {
8634             q = strchr(p, '}');
8635             if (q) {
8636                 *q = NULLCHAR;
8637                 r = p + 1;
8638             }
8639         }
8640
8641         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8642         return;
8643
8644     } else if (strncmp(message, "White resign", 12) == 0) {
8645         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8646         return;
8647     } else if (strncmp(message, "Black resign", 12) == 0) {
8648         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8649         return;
8650     } else if (strncmp(message, "White matches", 13) == 0 ||
8651                strncmp(message, "Black matches", 13) == 0   ) {
8652         /* [HGM] ignore GNUShogi noises */
8653         return;
8654     } else if (strncmp(message, "White", 5) == 0 &&
8655                message[5] != '(' &&
8656                StrStr(message, "Black") == NULL) {
8657         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8658         return;
8659     } else if (strncmp(message, "Black", 5) == 0 &&
8660                message[5] != '(') {
8661         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8662         return;
8663     } else if (strcmp(message, "resign") == 0 ||
8664                strcmp(message, "computer resigns") == 0) {
8665         switch (gameMode) {
8666           case MachinePlaysBlack:
8667           case IcsPlayingBlack:
8668             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8669             break;
8670           case MachinePlaysWhite:
8671           case IcsPlayingWhite:
8672             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8673             break;
8674           case TwoMachinesPlay:
8675             if (cps->twoMachinesColor[0] == 'w')
8676               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8677             else
8678               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8679             break;
8680           default:
8681             /* can't happen */
8682             break;
8683         }
8684         return;
8685     } else if (strncmp(message, "opponent mates", 14) == 0) {
8686         switch (gameMode) {
8687           case MachinePlaysBlack:
8688           case IcsPlayingBlack:
8689             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8690             break;
8691           case MachinePlaysWhite:
8692           case IcsPlayingWhite:
8693             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8694             break;
8695           case TwoMachinesPlay:
8696             if (cps->twoMachinesColor[0] == 'w')
8697               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8698             else
8699               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8700             break;
8701           default:
8702             /* can't happen */
8703             break;
8704         }
8705         return;
8706     } else if (strncmp(message, "computer mates", 14) == 0) {
8707         switch (gameMode) {
8708           case MachinePlaysBlack:
8709           case IcsPlayingBlack:
8710             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8711             break;
8712           case MachinePlaysWhite:
8713           case IcsPlayingWhite:
8714             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8715             break;
8716           case TwoMachinesPlay:
8717             if (cps->twoMachinesColor[0] == 'w')
8718               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8719             else
8720               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8721             break;
8722           default:
8723             /* can't happen */
8724             break;
8725         }
8726         return;
8727     } else if (strncmp(message, "checkmate", 9) == 0) {
8728         if (WhiteOnMove(forwardMostMove)) {
8729             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8730         } else {
8731             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8732         }
8733         return;
8734     } else if (strstr(message, "Draw") != NULL ||
8735                strstr(message, "game is a draw") != NULL) {
8736         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8737         return;
8738     } else if (strstr(message, "offer") != NULL &&
8739                strstr(message, "draw") != NULL) {
8740 #if ZIPPY
8741         if (appData.zippyPlay && first.initDone) {
8742             /* Relay offer to ICS */
8743             SendToICS(ics_prefix);
8744             SendToICS("draw\n");
8745         }
8746 #endif
8747         cps->offeredDraw = 2; /* valid until this engine moves twice */
8748         if (gameMode == TwoMachinesPlay) {
8749             if (cps->other->offeredDraw) {
8750                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8751             /* [HGM] in two-machine mode we delay relaying draw offer      */
8752             /* until after we also have move, to see if it is really claim */
8753             }
8754         } else if (gameMode == MachinePlaysWhite ||
8755                    gameMode == MachinePlaysBlack) {
8756           if (userOfferedDraw) {
8757             DisplayInformation(_("Machine accepts your draw offer"));
8758             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8759           } else {
8760             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8761           }
8762         }
8763     }
8764
8765
8766     /*
8767      * Look for thinking output
8768      */
8769     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8770           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8771                                 ) {
8772         int plylev, mvleft, mvtot, curscore, time;
8773         char mvname[MOVE_LEN];
8774         u64 nodes; // [DM]
8775         char plyext;
8776         int ignore = FALSE;
8777         int prefixHint = FALSE;
8778         mvname[0] = NULLCHAR;
8779
8780         switch (gameMode) {
8781           case MachinePlaysBlack:
8782           case IcsPlayingBlack:
8783             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8784             break;
8785           case MachinePlaysWhite:
8786           case IcsPlayingWhite:
8787             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8788             break;
8789           case AnalyzeMode:
8790           case AnalyzeFile:
8791             break;
8792           case IcsObserving: /* [DM] icsEngineAnalyze */
8793             if (!appData.icsEngineAnalyze) ignore = TRUE;
8794             break;
8795           case TwoMachinesPlay:
8796             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8797                 ignore = TRUE;
8798             }
8799             break;
8800           default:
8801             ignore = TRUE;
8802             break;
8803         }
8804
8805         if (!ignore) {
8806             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8807             buf1[0] = NULLCHAR;
8808             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8809                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8810
8811                 if (plyext != ' ' && plyext != '\t') {
8812                     time *= 100;
8813                 }
8814
8815                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8816                 if( cps->scoreIsAbsolute &&
8817                     ( gameMode == MachinePlaysBlack ||
8818                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8819                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8820                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8821                      !WhiteOnMove(currentMove)
8822                     ) )
8823                 {
8824                     curscore = -curscore;
8825                 }
8826
8827                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8828
8829                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8830                         char buf[MSG_SIZ];
8831                         FILE *f;
8832                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8833                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8834                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8835                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8836                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8837                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8838                                 fclose(f);
8839                         } else DisplayError(_("failed writing PV"), 0);
8840                 }
8841
8842                 tempStats.depth = plylev;
8843                 tempStats.nodes = nodes;
8844                 tempStats.time = time;
8845                 tempStats.score = curscore;
8846                 tempStats.got_only_move = 0;
8847
8848                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8849                         int ticklen;
8850
8851                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8852                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8853                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8854                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8855                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8856                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8857                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8858                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8859                 }
8860
8861                 /* Buffer overflow protection */
8862                 if (pv[0] != NULLCHAR) {
8863                     if (strlen(pv) >= sizeof(tempStats.movelist)
8864                         && appData.debugMode) {
8865                         fprintf(debugFP,
8866                                 "PV is too long; using the first %u bytes.\n",
8867                                 (unsigned) sizeof(tempStats.movelist) - 1);
8868                     }
8869
8870                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8871                 } else {
8872                     sprintf(tempStats.movelist, " no PV\n");
8873                 }
8874
8875                 if (tempStats.seen_stat) {
8876                     tempStats.ok_to_send = 1;
8877                 }
8878
8879                 if (strchr(tempStats.movelist, '(') != NULL) {
8880                     tempStats.line_is_book = 1;
8881                     tempStats.nr_moves = 0;
8882                     tempStats.moves_left = 0;
8883                 } else {
8884                     tempStats.line_is_book = 0;
8885                 }
8886
8887                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8888                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8889
8890                 SendProgramStatsToFrontend( cps, &tempStats );
8891
8892                 /*
8893                     [AS] Protect the thinkOutput buffer from overflow... this
8894                     is only useful if buf1 hasn't overflowed first!
8895                 */
8896                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8897                          plylev,
8898                          (gameMode == TwoMachinesPlay ?
8899                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8900                          ((double) curscore) / 100.0,
8901                          prefixHint ? lastHint : "",
8902                          prefixHint ? " " : "" );
8903
8904                 if( buf1[0] != NULLCHAR ) {
8905                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8906
8907                     if( strlen(pv) > max_len ) {
8908                         if( appData.debugMode) {
8909                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8910                         }
8911                         pv[max_len+1] = '\0';
8912                     }
8913
8914                     strcat( thinkOutput, pv);
8915                 }
8916
8917                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8918                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8919                     DisplayMove(currentMove - 1);
8920                 }
8921                 return;
8922
8923             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8924                 /* crafty (9.25+) says "(only move) <move>"
8925                  * if there is only 1 legal move
8926                  */
8927                 sscanf(p, "(only move) %s", buf1);
8928                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8929                 sprintf(programStats.movelist, "%s (only move)", buf1);
8930                 programStats.depth = 1;
8931                 programStats.nr_moves = 1;
8932                 programStats.moves_left = 1;
8933                 programStats.nodes = 1;
8934                 programStats.time = 1;
8935                 programStats.got_only_move = 1;
8936
8937                 /* Not really, but we also use this member to
8938                    mean "line isn't going to change" (Crafty
8939                    isn't searching, so stats won't change) */
8940                 programStats.line_is_book = 1;
8941
8942                 SendProgramStatsToFrontend( cps, &programStats );
8943
8944                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8945                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8946                     DisplayMove(currentMove - 1);
8947                 }
8948                 return;
8949             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8950                               &time, &nodes, &plylev, &mvleft,
8951                               &mvtot, mvname) >= 5) {
8952                 /* The stat01: line is from Crafty (9.29+) in response
8953                    to the "." command */
8954                 programStats.seen_stat = 1;
8955                 cps->maybeThinking = TRUE;
8956
8957                 if (programStats.got_only_move || !appData.periodicUpdates)
8958                   return;
8959
8960                 programStats.depth = plylev;
8961                 programStats.time = time;
8962                 programStats.nodes = nodes;
8963                 programStats.moves_left = mvleft;
8964                 programStats.nr_moves = mvtot;
8965                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8966                 programStats.ok_to_send = 1;
8967                 programStats.movelist[0] = '\0';
8968
8969                 SendProgramStatsToFrontend( cps, &programStats );
8970
8971                 return;
8972
8973             } else if (strncmp(message,"++",2) == 0) {
8974                 /* Crafty 9.29+ outputs this */
8975                 programStats.got_fail = 2;
8976                 return;
8977
8978             } else if (strncmp(message,"--",2) == 0) {
8979                 /* Crafty 9.29+ outputs this */
8980                 programStats.got_fail = 1;
8981                 return;
8982
8983             } else if (thinkOutput[0] != NULLCHAR &&
8984                        strncmp(message, "    ", 4) == 0) {
8985                 unsigned message_len;
8986
8987                 p = message;
8988                 while (*p && *p == ' ') p++;
8989
8990                 message_len = strlen( p );
8991
8992                 /* [AS] Avoid buffer overflow */
8993                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8994                     strcat(thinkOutput, " ");
8995                     strcat(thinkOutput, p);
8996                 }
8997
8998                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8999                     strcat(programStats.movelist, " ");
9000                     strcat(programStats.movelist, p);
9001                 }
9002
9003                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9004                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9005                     DisplayMove(currentMove - 1);
9006                 }
9007                 return;
9008             }
9009         }
9010         else {
9011             buf1[0] = NULLCHAR;
9012
9013             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9014                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9015             {
9016                 ChessProgramStats cpstats;
9017
9018                 if (plyext != ' ' && plyext != '\t') {
9019                     time *= 100;
9020                 }
9021
9022                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9023                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9024                     curscore = -curscore;
9025                 }
9026
9027                 cpstats.depth = plylev;
9028                 cpstats.nodes = nodes;
9029                 cpstats.time = time;
9030                 cpstats.score = curscore;
9031                 cpstats.got_only_move = 0;
9032                 cpstats.movelist[0] = '\0';
9033
9034                 if (buf1[0] != NULLCHAR) {
9035                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9036                 }
9037
9038                 cpstats.ok_to_send = 0;
9039                 cpstats.line_is_book = 0;
9040                 cpstats.nr_moves = 0;
9041                 cpstats.moves_left = 0;
9042
9043                 SendProgramStatsToFrontend( cps, &cpstats );
9044             }
9045         }
9046     }
9047 }
9048
9049
9050 /* Parse a game score from the character string "game", and
9051    record it as the history of the current game.  The game
9052    score is NOT assumed to start from the standard position.
9053    The display is not updated in any way.
9054    */
9055 void
9056 ParseGameHistory (char *game)
9057 {
9058     ChessMove moveType;
9059     int fromX, fromY, toX, toY, boardIndex;
9060     char promoChar;
9061     char *p, *q;
9062     char buf[MSG_SIZ];
9063
9064     if (appData.debugMode)
9065       fprintf(debugFP, "Parsing game history: %s\n", game);
9066
9067     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9068     gameInfo.site = StrSave(appData.icsHost);
9069     gameInfo.date = PGNDate();
9070     gameInfo.round = StrSave("-");
9071
9072     /* Parse out names of players */
9073     while (*game == ' ') game++;
9074     p = buf;
9075     while (*game != ' ') *p++ = *game++;
9076     *p = NULLCHAR;
9077     gameInfo.white = StrSave(buf);
9078     while (*game == ' ') game++;
9079     p = buf;
9080     while (*game != ' ' && *game != '\n') *p++ = *game++;
9081     *p = NULLCHAR;
9082     gameInfo.black = StrSave(buf);
9083
9084     /* Parse moves */
9085     boardIndex = blackPlaysFirst ? 1 : 0;
9086     yynewstr(game);
9087     for (;;) {
9088         yyboardindex = boardIndex;
9089         moveType = (ChessMove) Myylex();
9090         switch (moveType) {
9091           case IllegalMove:             /* maybe suicide chess, etc. */
9092   if (appData.debugMode) {
9093     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9094     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9095     setbuf(debugFP, NULL);
9096   }
9097           case WhitePromotion:
9098           case BlackPromotion:
9099           case WhiteNonPromotion:
9100           case BlackNonPromotion:
9101           case NormalMove:
9102           case WhiteCapturesEnPassant:
9103           case BlackCapturesEnPassant:
9104           case WhiteKingSideCastle:
9105           case WhiteQueenSideCastle:
9106           case BlackKingSideCastle:
9107           case BlackQueenSideCastle:
9108           case WhiteKingSideCastleWild:
9109           case WhiteQueenSideCastleWild:
9110           case BlackKingSideCastleWild:
9111           case BlackQueenSideCastleWild:
9112           /* PUSH Fabien */
9113           case WhiteHSideCastleFR:
9114           case WhiteASideCastleFR:
9115           case BlackHSideCastleFR:
9116           case BlackASideCastleFR:
9117           /* POP Fabien */
9118             fromX = currentMoveString[0] - AAA;
9119             fromY = currentMoveString[1] - ONE;
9120             toX = currentMoveString[2] - AAA;
9121             toY = currentMoveString[3] - ONE;
9122             promoChar = currentMoveString[4];
9123             break;
9124           case WhiteDrop:
9125           case BlackDrop:
9126             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9127             fromX = moveType == WhiteDrop ?
9128               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9129             (int) CharToPiece(ToLower(currentMoveString[0]));
9130             fromY = DROP_RANK;
9131             toX = currentMoveString[2] - AAA;
9132             toY = currentMoveString[3] - ONE;
9133             promoChar = NULLCHAR;
9134             break;
9135           case AmbiguousMove:
9136             /* bug? */
9137             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9138   if (appData.debugMode) {
9139     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9140     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9141     setbuf(debugFP, NULL);
9142   }
9143             DisplayError(buf, 0);
9144             return;
9145           case ImpossibleMove:
9146             /* bug? */
9147             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9148   if (appData.debugMode) {
9149     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9150     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9151     setbuf(debugFP, NULL);
9152   }
9153             DisplayError(buf, 0);
9154             return;
9155           case EndOfFile:
9156             if (boardIndex < backwardMostMove) {
9157                 /* Oops, gap.  How did that happen? */
9158                 DisplayError(_("Gap in move list"), 0);
9159                 return;
9160             }
9161             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9162             if (boardIndex > forwardMostMove) {
9163                 forwardMostMove = boardIndex;
9164             }
9165             return;
9166           case ElapsedTime:
9167             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9168                 strcat(parseList[boardIndex-1], " ");
9169                 strcat(parseList[boardIndex-1], yy_text);
9170             }
9171             continue;
9172           case Comment:
9173           case PGNTag:
9174           case NAG:
9175           default:
9176             /* ignore */
9177             continue;
9178           case WhiteWins:
9179           case BlackWins:
9180           case GameIsDrawn:
9181           case GameUnfinished:
9182             if (gameMode == IcsExamining) {
9183                 if (boardIndex < backwardMostMove) {
9184                     /* Oops, gap.  How did that happen? */
9185                     return;
9186                 }
9187                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9188                 return;
9189             }
9190             gameInfo.result = moveType;
9191             p = strchr(yy_text, '{');
9192             if (p == NULL) p = strchr(yy_text, '(');
9193             if (p == NULL) {
9194                 p = yy_text;
9195                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9196             } else {
9197                 q = strchr(p, *p == '{' ? '}' : ')');
9198                 if (q != NULL) *q = NULLCHAR;
9199                 p++;
9200             }
9201             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9202             gameInfo.resultDetails = StrSave(p);
9203             continue;
9204         }
9205         if (boardIndex >= forwardMostMove &&
9206             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9207             backwardMostMove = blackPlaysFirst ? 1 : 0;
9208             return;
9209         }
9210         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9211                                  fromY, fromX, toY, toX, promoChar,
9212                                  parseList[boardIndex]);
9213         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9214         /* currentMoveString is set as a side-effect of yylex */
9215         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9216         strcat(moveList[boardIndex], "\n");
9217         boardIndex++;
9218         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9219         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9220           case MT_NONE:
9221           case MT_STALEMATE:
9222           default:
9223             break;
9224           case MT_CHECK:
9225             if(gameInfo.variant != VariantShogi)
9226                 strcat(parseList[boardIndex - 1], "+");
9227             break;
9228           case MT_CHECKMATE:
9229           case MT_STAINMATE:
9230             strcat(parseList[boardIndex - 1], "#");
9231             break;
9232         }
9233     }
9234 }
9235
9236
9237 /* Apply a move to the given board  */
9238 void
9239 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9240 {
9241   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9242   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9243
9244     /* [HGM] compute & store e.p. status and castling rights for new position */
9245     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9246
9247       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9248       oldEP = (signed char)board[EP_STATUS];
9249       board[EP_STATUS] = EP_NONE;
9250
9251   if (fromY == DROP_RANK) {
9252         /* must be first */
9253         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9254             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9255             return;
9256         }
9257         piece = board[toY][toX] = (ChessSquare) fromX;
9258   } else {
9259       int i;
9260
9261       if( board[toY][toX] != EmptySquare )
9262            board[EP_STATUS] = EP_CAPTURE;
9263
9264       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9265            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9266                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9267       } else
9268       if( board[fromY][fromX] == WhitePawn ) {
9269            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9270                board[EP_STATUS] = EP_PAWN_MOVE;
9271            if( toY-fromY==2) {
9272                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9273                         gameInfo.variant != VariantBerolina || toX < fromX)
9274                       board[EP_STATUS] = toX | berolina;
9275                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9276                         gameInfo.variant != VariantBerolina || toX > fromX)
9277                       board[EP_STATUS] = toX;
9278            }
9279       } else
9280       if( board[fromY][fromX] == BlackPawn ) {
9281            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9282                board[EP_STATUS] = EP_PAWN_MOVE;
9283            if( toY-fromY== -2) {
9284                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9285                         gameInfo.variant != VariantBerolina || toX < fromX)
9286                       board[EP_STATUS] = toX | berolina;
9287                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9288                         gameInfo.variant != VariantBerolina || toX > fromX)
9289                       board[EP_STATUS] = toX;
9290            }
9291        }
9292
9293        for(i=0; i<nrCastlingRights; i++) {
9294            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9295               board[CASTLING][i] == toX   && castlingRank[i] == toY
9296              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9297        }
9298
9299      if (fromX == toX && fromY == toY) return;
9300
9301      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9302      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9303      if(gameInfo.variant == VariantKnightmate)
9304          king += (int) WhiteUnicorn - (int) WhiteKing;
9305
9306     /* Code added by Tord: */
9307     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9308     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9309         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9310       board[fromY][fromX] = EmptySquare;
9311       board[toY][toX] = EmptySquare;
9312       if((toX > fromX) != (piece == WhiteRook)) {
9313         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9314       } else {
9315         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9316       }
9317     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9318                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9319       board[fromY][fromX] = EmptySquare;
9320       board[toY][toX] = EmptySquare;
9321       if((toX > fromX) != (piece == BlackRook)) {
9322         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9323       } else {
9324         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9325       }
9326     /* End of code added by Tord */
9327
9328     } else if (board[fromY][fromX] == king
9329         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9330         && toY == fromY && toX > fromX+1) {
9331         board[fromY][fromX] = EmptySquare;
9332         board[toY][toX] = king;
9333         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9334         board[fromY][BOARD_RGHT-1] = EmptySquare;
9335     } else if (board[fromY][fromX] == king
9336         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9337                && toY == fromY && toX < fromX-1) {
9338         board[fromY][fromX] = EmptySquare;
9339         board[toY][toX] = king;
9340         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9341         board[fromY][BOARD_LEFT] = EmptySquare;
9342     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9343                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9344                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9345                ) {
9346         /* white pawn promotion */
9347         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9348         if(gameInfo.variant==VariantBughouse ||
9349            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9350             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9351         board[fromY][fromX] = EmptySquare;
9352     } else if ((fromY >= BOARD_HEIGHT>>1)
9353                && (toX != fromX)
9354                && gameInfo.variant != VariantXiangqi
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         captured = board[toY - 1][toX];
9361         board[toY - 1][toX] = EmptySquare;
9362     } else if ((fromY == BOARD_HEIGHT-4)
9363                && (toX == fromX)
9364                && gameInfo.variant == VariantBerolina
9365                && (board[fromY][fromX] == WhitePawn)
9366                && (board[toY][toX] == EmptySquare)) {
9367         board[fromY][fromX] = EmptySquare;
9368         board[toY][toX] = WhitePawn;
9369         if(oldEP & EP_BEROLIN_A) {
9370                 captured = board[fromY][fromX-1];
9371                 board[fromY][fromX-1] = EmptySquare;
9372         }else{  captured = board[fromY][fromX+1];
9373                 board[fromY][fromX+1] = EmptySquare;
9374         }
9375     } else if (board[fromY][fromX] == king
9376         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9377                && toY == fromY && toX > fromX+1) {
9378         board[fromY][fromX] = EmptySquare;
9379         board[toY][toX] = king;
9380         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9381         board[fromY][BOARD_RGHT-1] = EmptySquare;
9382     } else if (board[fromY][fromX] == king
9383         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9384                && toY == fromY && toX < fromX-1) {
9385         board[fromY][fromX] = EmptySquare;
9386         board[toY][toX] = king;
9387         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9388         board[fromY][BOARD_LEFT] = EmptySquare;
9389     } else if (fromY == 7 && fromX == 3
9390                && board[fromY][fromX] == BlackKing
9391                && toY == 7 && toX == 5) {
9392         board[fromY][fromX] = EmptySquare;
9393         board[toY][toX] = BlackKing;
9394         board[fromY][7] = EmptySquare;
9395         board[toY][4] = BlackRook;
9396     } else if (fromY == 7 && fromX == 3
9397                && board[fromY][fromX] == BlackKing
9398                && toY == 7 && toX == 1) {
9399         board[fromY][fromX] = EmptySquare;
9400         board[toY][toX] = BlackKing;
9401         board[fromY][0] = EmptySquare;
9402         board[toY][2] = BlackRook;
9403     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9404                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9405                && toY < promoRank && promoChar
9406                ) {
9407         /* black pawn promotion */
9408         board[toY][toX] = CharToPiece(ToLower(promoChar));
9409         if(gameInfo.variant==VariantBughouse ||
9410            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9411             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9412         board[fromY][fromX] = EmptySquare;
9413     } else if ((fromY < BOARD_HEIGHT>>1)
9414                && (toX != fromX)
9415                && gameInfo.variant != VariantXiangqi
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         captured = board[toY + 1][toX];
9422         board[toY + 1][toX] = EmptySquare;
9423     } else if ((fromY == 3)
9424                && (toX == fromX)
9425                && gameInfo.variant == VariantBerolina
9426                && (board[fromY][fromX] == BlackPawn)
9427                && (board[toY][toX] == EmptySquare)) {
9428         board[fromY][fromX] = EmptySquare;
9429         board[toY][toX] = BlackPawn;
9430         if(oldEP & EP_BEROLIN_A) {
9431                 captured = board[fromY][fromX-1];
9432                 board[fromY][fromX-1] = EmptySquare;
9433         }else{  captured = board[fromY][fromX+1];
9434                 board[fromY][fromX+1] = EmptySquare;
9435         }
9436     } else {
9437         board[toY][toX] = board[fromY][fromX];
9438         board[fromY][fromX] = EmptySquare;
9439     }
9440   }
9441
9442     if (gameInfo.holdingsWidth != 0) {
9443
9444       /* !!A lot more code needs to be written to support holdings  */
9445       /* [HGM] OK, so I have written it. Holdings are stored in the */
9446       /* penultimate board files, so they are automaticlly stored   */
9447       /* in the game history.                                       */
9448       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9449                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9450         /* Delete from holdings, by decreasing count */
9451         /* and erasing image if necessary            */
9452         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9453         if(p < (int) BlackPawn) { /* white drop */
9454              p -= (int)WhitePawn;
9455                  p = PieceToNumber((ChessSquare)p);
9456              if(p >= gameInfo.holdingsSize) p = 0;
9457              if(--board[p][BOARD_WIDTH-2] <= 0)
9458                   board[p][BOARD_WIDTH-1] = EmptySquare;
9459              if((int)board[p][BOARD_WIDTH-2] < 0)
9460                         board[p][BOARD_WIDTH-2] = 0;
9461         } else {                  /* black drop */
9462              p -= (int)BlackPawn;
9463                  p = PieceToNumber((ChessSquare)p);
9464              if(p >= gameInfo.holdingsSize) p = 0;
9465              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9466                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9467              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9468                         board[BOARD_HEIGHT-1-p][1] = 0;
9469         }
9470       }
9471       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9472           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9473         /* [HGM] holdings: Add to holdings, if holdings exist */
9474         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9475                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9476                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9477         }
9478         p = (int) captured;
9479         if (p >= (int) BlackPawn) {
9480           p -= (int)BlackPawn;
9481           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9482                   /* in Shogi restore piece to its original  first */
9483                   captured = (ChessSquare) (DEMOTED captured);
9484                   p = DEMOTED p;
9485           }
9486           p = PieceToNumber((ChessSquare)p);
9487           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9488           board[p][BOARD_WIDTH-2]++;
9489           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9490         } else {
9491           p -= (int)WhitePawn;
9492           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9493                   captured = (ChessSquare) (DEMOTED captured);
9494                   p = DEMOTED p;
9495           }
9496           p = PieceToNumber((ChessSquare)p);
9497           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9498           board[BOARD_HEIGHT-1-p][1]++;
9499           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9500         }
9501       }
9502     } else if (gameInfo.variant == VariantAtomic) {
9503       if (captured != EmptySquare) {
9504         int y, x;
9505         for (y = toY-1; y <= toY+1; y++) {
9506           for (x = toX-1; x <= toX+1; x++) {
9507             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9508                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9509               board[y][x] = EmptySquare;
9510             }
9511           }
9512         }
9513         board[toY][toX] = EmptySquare;
9514       }
9515     }
9516     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9517         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9518     } else
9519     if(promoChar == '+') {
9520         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9521         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9522     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9523         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9524         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9525            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9526         board[toY][toX] = newPiece;
9527     }
9528     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9529                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9530         // [HGM] superchess: take promotion piece out of holdings
9531         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9532         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9533             if(!--board[k][BOARD_WIDTH-2])
9534                 board[k][BOARD_WIDTH-1] = EmptySquare;
9535         } else {
9536             if(!--board[BOARD_HEIGHT-1-k][1])
9537                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9538         }
9539     }
9540
9541 }
9542
9543 /* Updates forwardMostMove */
9544 void
9545 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9546 {
9547 //    forwardMostMove++; // [HGM] bare: moved downstream
9548
9549     (void) CoordsToAlgebraic(boards[forwardMostMove],
9550                              PosFlags(forwardMostMove),
9551                              fromY, fromX, toY, toX, promoChar,
9552                              parseList[forwardMostMove]);
9553
9554     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9555         int timeLeft; static int lastLoadFlag=0; int king, piece;
9556         piece = boards[forwardMostMove][fromY][fromX];
9557         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9558         if(gameInfo.variant == VariantKnightmate)
9559             king += (int) WhiteUnicorn - (int) WhiteKing;
9560         if(forwardMostMove == 0) {
9561             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9562                 fprintf(serverMoves, "%s;", UserName());
9563             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9564                 fprintf(serverMoves, "%s;", second.tidy);
9565             fprintf(serverMoves, "%s;", first.tidy);
9566             if(gameMode == MachinePlaysWhite)
9567                 fprintf(serverMoves, "%s;", UserName());
9568             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9569                 fprintf(serverMoves, "%s;", second.tidy);
9570         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9571         lastLoadFlag = loadFlag;
9572         // print base move
9573         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9574         // print castling suffix
9575         if( toY == fromY && piece == king ) {
9576             if(toX-fromX > 1)
9577                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9578             if(fromX-toX >1)
9579                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9580         }
9581         // e.p. suffix
9582         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9583              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9584              boards[forwardMostMove][toY][toX] == EmptySquare
9585              && fromX != toX && fromY != toY)
9586                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9587         // promotion suffix
9588         if(promoChar != NULLCHAR)
9589                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9590         if(!loadFlag) {
9591                 char buf[MOVE_LEN*2], *p; int len;
9592             fprintf(serverMoves, "/%d/%d",
9593                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9594             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9595             else                      timeLeft = blackTimeRemaining/1000;
9596             fprintf(serverMoves, "/%d", timeLeft);
9597                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9598                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9599                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9600             fprintf(serverMoves, "/%s", buf);
9601         }
9602         fflush(serverMoves);
9603     }
9604
9605     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9606         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9607       return;
9608     }
9609     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9610     if (commentList[forwardMostMove+1] != NULL) {
9611         free(commentList[forwardMostMove+1]);
9612         commentList[forwardMostMove+1] = NULL;
9613     }
9614     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9615     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9616     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9617     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9618     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9619     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9620     adjustedClock = FALSE;
9621     gameInfo.result = GameUnfinished;
9622     if (gameInfo.resultDetails != NULL) {
9623         free(gameInfo.resultDetails);
9624         gameInfo.resultDetails = NULL;
9625     }
9626     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9627                               moveList[forwardMostMove - 1]);
9628     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9629       case MT_NONE:
9630       case MT_STALEMATE:
9631       default:
9632         break;
9633       case MT_CHECK:
9634         if(gameInfo.variant != VariantShogi)
9635             strcat(parseList[forwardMostMove - 1], "+");
9636         break;
9637       case MT_CHECKMATE:
9638       case MT_STAINMATE:
9639         strcat(parseList[forwardMostMove - 1], "#");
9640         break;
9641     }
9642
9643 }
9644
9645 /* Updates currentMove if not pausing */
9646 void
9647 ShowMove (int fromX, int fromY, int toX, int toY)
9648 {
9649     int instant = (gameMode == PlayFromGameFile) ?
9650         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9651     if(appData.noGUI) return;
9652     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9653         if (!instant) {
9654             if (forwardMostMove == currentMove + 1) {
9655                 AnimateMove(boards[forwardMostMove - 1],
9656                             fromX, fromY, toX, toY);
9657             }
9658             if (appData.highlightLastMove) {
9659                 SetHighlights(fromX, fromY, toX, toY);
9660             }
9661         }
9662         currentMove = forwardMostMove;
9663     }
9664
9665     if (instant) return;
9666
9667     DisplayMove(currentMove - 1);
9668     DrawPosition(FALSE, boards[currentMove]);
9669     DisplayBothClocks();
9670     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9671 }
9672
9673 void
9674 SendEgtPath (ChessProgramState *cps)
9675 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9676         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9677
9678         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9679
9680         while(*p) {
9681             char c, *q = name+1, *r, *s;
9682
9683             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9684             while(*p && *p != ',') *q++ = *p++;
9685             *q++ = ':'; *q = 0;
9686             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9687                 strcmp(name, ",nalimov:") == 0 ) {
9688                 // take nalimov path from the menu-changeable option first, if it is defined
9689               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9690                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9691             } else
9692             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9693                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9694                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9695                 s = r = StrStr(s, ":") + 1; // beginning of path info
9696                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9697                 c = *r; *r = 0;             // temporarily null-terminate path info
9698                     *--q = 0;               // strip of trailig ':' from name
9699                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9700                 *r = c;
9701                 SendToProgram(buf,cps);     // send egtbpath command for this format
9702             }
9703             if(*p == ',') p++; // read away comma to position for next format name
9704         }
9705 }
9706
9707 void
9708 InitChessProgram (ChessProgramState *cps, int setup)
9709 /* setup needed to setup FRC opening position */
9710 {
9711     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9712     if (appData.noChessProgram) return;
9713     hintRequested = FALSE;
9714     bookRequested = FALSE;
9715
9716     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9717     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9718     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9719     if(cps->memSize) { /* [HGM] memory */
9720       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9721         SendToProgram(buf, cps);
9722     }
9723     SendEgtPath(cps); /* [HGM] EGT */
9724     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9725       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9726         SendToProgram(buf, cps);
9727     }
9728
9729     SendToProgram(cps->initString, cps);
9730     if (gameInfo.variant != VariantNormal &&
9731         gameInfo.variant != VariantLoadable
9732         /* [HGM] also send variant if board size non-standard */
9733         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9734                                             ) {
9735       char *v = VariantName(gameInfo.variant);
9736       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9737         /* [HGM] in protocol 1 we have to assume all variants valid */
9738         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9739         DisplayFatalError(buf, 0, 1);
9740         return;
9741       }
9742
9743       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9744       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9745       if( gameInfo.variant == VariantXiangqi )
9746            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9747       if( gameInfo.variant == VariantShogi )
9748            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9749       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9750            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9751       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9752           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9753            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9754       if( gameInfo.variant == VariantCourier )
9755            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9756       if( gameInfo.variant == VariantSuper )
9757            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9758       if( gameInfo.variant == VariantGreat )
9759            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9760       if( gameInfo.variant == VariantSChess )
9761            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9762       if( gameInfo.variant == VariantGrand )
9763            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9764
9765       if(overruled) {
9766         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9767                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9768            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9769            if(StrStr(cps->variants, b) == NULL) {
9770                // specific sized variant not known, check if general sizing allowed
9771                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9772                    if(StrStr(cps->variants, "boardsize") == NULL) {
9773                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9774                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9775                        DisplayFatalError(buf, 0, 1);
9776                        return;
9777                    }
9778                    /* [HGM] here we really should compare with the maximum supported board size */
9779                }
9780            }
9781       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9782       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9783       SendToProgram(buf, cps);
9784     }
9785     currentlyInitializedVariant = gameInfo.variant;
9786
9787     /* [HGM] send opening position in FRC to first engine */
9788     if(setup) {
9789           SendToProgram("force\n", cps);
9790           SendBoard(cps, 0);
9791           /* engine is now in force mode! Set flag to wake it up after first move. */
9792           setboardSpoiledMachineBlack = 1;
9793     }
9794
9795     if (cps->sendICS) {
9796       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9797       SendToProgram(buf, cps);
9798     }
9799     cps->maybeThinking = FALSE;
9800     cps->offeredDraw = 0;
9801     if (!appData.icsActive) {
9802         SendTimeControl(cps, movesPerSession, timeControl,
9803                         timeIncrement, appData.searchDepth,
9804                         searchTime);
9805     }
9806     if (appData.showThinking
9807         // [HGM] thinking: four options require thinking output to be sent
9808         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9809                                 ) {
9810         SendToProgram("post\n", cps);
9811     }
9812     SendToProgram("hard\n", cps);
9813     if (!appData.ponderNextMove) {
9814         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9815            it without being sure what state we are in first.  "hard"
9816            is not a toggle, so that one is OK.
9817          */
9818         SendToProgram("easy\n", cps);
9819     }
9820     if (cps->usePing) {
9821       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9822       SendToProgram(buf, cps);
9823     }
9824     cps->initDone = TRUE;
9825     ClearEngineOutputPane(cps == &second);
9826 }
9827
9828
9829 void
9830 StartChessProgram (ChessProgramState *cps)
9831 {
9832     char buf[MSG_SIZ];
9833     int err;
9834
9835     if (appData.noChessProgram) return;
9836     cps->initDone = FALSE;
9837
9838     if (strcmp(cps->host, "localhost") == 0) {
9839         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9840     } else if (*appData.remoteShell == NULLCHAR) {
9841         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9842     } else {
9843         if (*appData.remoteUser == NULLCHAR) {
9844           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9845                     cps->program);
9846         } else {
9847           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9848                     cps->host, appData.remoteUser, cps->program);
9849         }
9850         err = StartChildProcess(buf, "", &cps->pr);
9851     }
9852
9853     if (err != 0) {
9854       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9855         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9856         if(cps != &first) return;
9857         appData.noChessProgram = TRUE;
9858         ThawUI();
9859         SetNCPMode();
9860 //      DisplayFatalError(buf, err, 1);
9861 //      cps->pr = NoProc;
9862 //      cps->isr = NULL;
9863         return;
9864     }
9865
9866     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9867     if (cps->protocolVersion > 1) {
9868       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9869       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9870       cps->comboCnt = 0;  //                and values of combo boxes
9871       SendToProgram(buf, cps);
9872     } else {
9873       SendToProgram("xboard\n", cps);
9874     }
9875 }
9876
9877 void
9878 TwoMachinesEventIfReady P((void))
9879 {
9880   static int curMess = 0;
9881   if (first.lastPing != first.lastPong) {
9882     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9883     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9884     return;
9885   }
9886   if (second.lastPing != second.lastPong) {
9887     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9888     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9889     return;
9890   }
9891   DisplayMessage("", ""); curMess = 0;
9892   ThawUI();
9893   TwoMachinesEvent();
9894 }
9895
9896 char *
9897 MakeName (char *template)
9898 {
9899     time_t clock;
9900     struct tm *tm;
9901     static char buf[MSG_SIZ];
9902     char *p = buf;
9903     int i;
9904
9905     clock = time((time_t *)NULL);
9906     tm = localtime(&clock);
9907
9908     while(*p++ = *template++) if(p[-1] == '%') {
9909         switch(*template++) {
9910           case 0:   *p = 0; return buf;
9911           case 'Y': i = tm->tm_year+1900; break;
9912           case 'y': i = tm->tm_year-100; break;
9913           case 'M': i = tm->tm_mon+1; break;
9914           case 'd': i = tm->tm_mday; break;
9915           case 'h': i = tm->tm_hour; break;
9916           case 'm': i = tm->tm_min; break;
9917           case 's': i = tm->tm_sec; break;
9918           default:  i = 0;
9919         }
9920         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9921     }
9922     return buf;
9923 }
9924
9925 int
9926 CountPlayers (char *p)
9927 {
9928     int n = 0;
9929     while(p = strchr(p, '\n')) p++, n++; // count participants
9930     return n;
9931 }
9932
9933 FILE *
9934 WriteTourneyFile (char *results, FILE *f)
9935 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9936     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9937     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9938         // create a file with tournament description
9939         fprintf(f, "-participants {%s}\n", appData.participants);
9940         fprintf(f, "-seedBase %d\n", appData.seedBase);
9941         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9942         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9943         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9944         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9945         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9946         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9947         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9948         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9949         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9950         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9951         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9952         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9953         if(searchTime > 0)
9954                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9955         else {
9956                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9957                 fprintf(f, "-tc %s\n", appData.timeControl);
9958                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9959         }
9960         fprintf(f, "-results \"%s\"\n", results);
9961     }
9962     return f;
9963 }
9964
9965 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9966
9967 void
9968 Substitute (char *participants, int expunge)
9969 {
9970     int i, changed, changes=0, nPlayers=0;
9971     char *p, *q, *r, buf[MSG_SIZ];
9972     if(participants == NULL) return;
9973     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9974     r = p = participants; q = appData.participants;
9975     while(*p && *p == *q) {
9976         if(*p == '\n') r = p+1, nPlayers++;
9977         p++; q++;
9978     }
9979     if(*p) { // difference
9980         while(*p && *p++ != '\n');
9981         while(*q && *q++ != '\n');
9982       changed = nPlayers;
9983         changes = 1 + (strcmp(p, q) != 0);
9984     }
9985     if(changes == 1) { // a single engine mnemonic was changed
9986         q = r; while(*q) nPlayers += (*q++ == '\n');
9987         p = buf; while(*r && (*p = *r++) != '\n') p++;
9988         *p = NULLCHAR;
9989         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9990         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9991         if(mnemonic[i]) { // The substitute is valid
9992             FILE *f;
9993             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9994                 flock(fileno(f), LOCK_EX);
9995                 ParseArgsFromFile(f);
9996                 fseek(f, 0, SEEK_SET);
9997                 FREE(appData.participants); appData.participants = participants;
9998                 if(expunge) { // erase results of replaced engine
9999                     int len = strlen(appData.results), w, b, dummy;
10000                     for(i=0; i<len; i++) {
10001                         Pairing(i, nPlayers, &w, &b, &dummy);
10002                         if((w == changed || b == changed) && appData.results[i] == '*') {
10003                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10004                             fclose(f);
10005                             return;
10006                         }
10007                     }
10008                     for(i=0; i<len; i++) {
10009                         Pairing(i, nPlayers, &w, &b, &dummy);
10010                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10011                     }
10012                 }
10013                 WriteTourneyFile(appData.results, f);
10014                 fclose(f); // release lock
10015                 return;
10016             }
10017         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10018     }
10019     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10020     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10021     free(participants);
10022     return;
10023 }
10024
10025 int
10026 CreateTourney (char *name)
10027 {
10028         FILE *f;
10029         if(matchMode && strcmp(name, appData.tourneyFile)) {
10030              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10031         }
10032         if(name[0] == NULLCHAR) {
10033             if(appData.participants[0])
10034                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10035             return 0;
10036         }
10037         f = fopen(name, "r");
10038         if(f) { // file exists
10039             ASSIGN(appData.tourneyFile, name);
10040             ParseArgsFromFile(f); // parse it
10041         } else {
10042             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10043             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10044                 DisplayError(_("Not enough participants"), 0);
10045                 return 0;
10046             }
10047             ASSIGN(appData.tourneyFile, name);
10048             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10049             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10050         }
10051         fclose(f);
10052         appData.noChessProgram = FALSE;
10053         appData.clockMode = TRUE;
10054         SetGNUMode();
10055         return 1;
10056 }
10057
10058 int
10059 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10060 {
10061     char buf[MSG_SIZ], *p, *q;
10062     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10063     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10064     skip = !all && group[0]; // if group requested, we start in skip mode
10065     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10066         p = names; q = buf; header = 0;
10067         while(*p && *p != '\n') *q++ = *p++;
10068         *q = 0;
10069         if(*p == '\n') p++;
10070         if(buf[0] == '#') {
10071             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10072             depth++; // we must be entering a new group
10073             if(all) continue; // suppress printing group headers when complete list requested
10074             header = 1;
10075             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10076         }
10077         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10078         if(engineList[i]) free(engineList[i]);
10079         engineList[i] = strdup(buf);
10080         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10081         if(engineMnemonic[i]) free(engineMnemonic[i]);
10082         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10083             strcat(buf, " (");
10084             sscanf(q + 8, "%s", buf + strlen(buf));
10085             strcat(buf, ")");
10086         }
10087         engineMnemonic[i] = strdup(buf);
10088         i++;
10089     }
10090     engineList[i] = engineMnemonic[i] = NULL;
10091     return i;
10092 }
10093
10094 // following implemented as macro to avoid type limitations
10095 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10096
10097 void
10098 SwapEngines (int n)
10099 {   // swap settings for first engine and other engine (so far only some selected options)
10100     int h;
10101     char *p;
10102     if(n == 0) return;
10103     SWAP(directory, p)
10104     SWAP(chessProgram, p)
10105     SWAP(isUCI, h)
10106     SWAP(hasOwnBookUCI, h)
10107     SWAP(protocolVersion, h)
10108     SWAP(reuse, h)
10109     SWAP(scoreIsAbsolute, h)
10110     SWAP(timeOdds, h)
10111     SWAP(logo, p)
10112     SWAP(pgnName, p)
10113     SWAP(pvSAN, h)
10114     SWAP(engOptions, p)
10115     SWAP(engInitString, p)
10116     SWAP(computerString, p)
10117     SWAP(features, p)
10118     SWAP(fenOverride, p)
10119     SWAP(NPS, h)
10120     SWAP(accumulateTC, h)
10121     SWAP(host, p)
10122 }
10123
10124 int
10125 SetPlayer (int player, char *p)
10126 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10127     int i;
10128     char buf[MSG_SIZ], *engineName;
10129     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10130     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10131     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10132     if(mnemonic[i]) {
10133         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10134         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10135         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10136         ParseArgsFromString(buf);
10137     }
10138     free(engineName);
10139     return i;
10140 }
10141
10142 char *recentEngines;
10143
10144 void
10145 RecentEngineEvent (int nr)
10146 {
10147     int n;
10148 //    SwapEngines(1); // bump first to second
10149 //    ReplaceEngine(&second, 1); // and load it there
10150     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10151     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10152     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10153         ReplaceEngine(&first, 0);
10154         FloatToFront(&appData.recentEngineList, command[n]);
10155     }
10156 }
10157
10158 int
10159 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10160 {   // determine players from game number
10161     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10162
10163     if(appData.tourneyType == 0) {
10164         roundsPerCycle = (nPlayers - 1) | 1;
10165         pairingsPerRound = nPlayers / 2;
10166     } else if(appData.tourneyType > 0) {
10167         roundsPerCycle = nPlayers - appData.tourneyType;
10168         pairingsPerRound = appData.tourneyType;
10169     }
10170     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10171     gamesPerCycle = gamesPerRound * roundsPerCycle;
10172     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10173     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10174     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10175     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10176     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10177     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10178
10179     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10180     if(appData.roundSync) *syncInterval = gamesPerRound;
10181
10182     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10183
10184     if(appData.tourneyType == 0) {
10185         if(curPairing == (nPlayers-1)/2 ) {
10186             *whitePlayer = curRound;
10187             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10188         } else {
10189             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10190             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10191             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10192             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10193         }
10194     } else if(appData.tourneyType > 1) {
10195         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10196         *whitePlayer = curRound + appData.tourneyType;
10197     } else if(appData.tourneyType > 0) {
10198         *whitePlayer = curPairing;
10199         *blackPlayer = curRound + appData.tourneyType;
10200     }
10201
10202     // take care of white/black alternation per round. 
10203     // For cycles and games this is already taken care of by default, derived from matchGame!
10204     return curRound & 1;
10205 }
10206
10207 int
10208 NextTourneyGame (int nr, int *swapColors)
10209 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10210     char *p, *q;
10211     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10212     FILE *tf;
10213     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10214     tf = fopen(appData.tourneyFile, "r");
10215     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10216     ParseArgsFromFile(tf); fclose(tf);
10217     InitTimeControls(); // TC might be altered from tourney file
10218
10219     nPlayers = CountPlayers(appData.participants); // count participants
10220     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10221     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10222
10223     if(syncInterval) {
10224         p = q = appData.results;
10225         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10226         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10227             DisplayMessage(_("Waiting for other game(s)"),"");
10228             waitingForGame = TRUE;
10229             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10230             return 0;
10231         }
10232         waitingForGame = FALSE;
10233     }
10234
10235     if(appData.tourneyType < 0) {
10236         if(nr>=0 && !pairingReceived) {
10237             char buf[1<<16];
10238             if(pairing.pr == NoProc) {
10239                 if(!appData.pairingEngine[0]) {
10240                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10241                     return 0;
10242                 }
10243                 StartChessProgram(&pairing); // starts the pairing engine
10244             }
10245             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10246             SendToProgram(buf, &pairing);
10247             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10248             SendToProgram(buf, &pairing);
10249             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10250         }
10251         pairingReceived = 0;                              // ... so we continue here 
10252         *swapColors = 0;
10253         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10254         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10255         matchGame = 1; roundNr = nr / syncInterval + 1;
10256     }
10257
10258     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10259
10260     // redefine engines, engine dir, etc.
10261     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10262     if(first.pr == NoProc) {
10263       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10264       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10265     }
10266     if(second.pr == NoProc) {
10267       SwapEngines(1);
10268       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10269       SwapEngines(1);         // and make that valid for second engine by swapping
10270       InitEngine(&second, 1);
10271     }
10272     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10273     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10274     return 1;
10275 }
10276
10277 void
10278 NextMatchGame ()
10279 {   // performs game initialization that does not invoke engines, and then tries to start the game
10280     int res, firstWhite, swapColors = 0;
10281     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10282     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
10283         char buf[MSG_SIZ];
10284         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10285         if(strcmp(buf, currentDebugFile)) { // name has changed
10286             FILE *f = fopen(buf, "w");
10287             if(f) { // if opening the new file failed, just keep using the old one
10288                 ASSIGN(currentDebugFile, buf);
10289                 fclose(debugFP);
10290                 debugFP = f;
10291             }
10292             if(appData.serverFileName) {
10293                 if(serverFP) fclose(serverFP);
10294                 serverFP = fopen(appData.serverFileName, "w");
10295                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10296                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10297             }
10298         }
10299     }
10300     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10301     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10302     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10303     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10304     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10305     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10306     Reset(FALSE, first.pr != NoProc);
10307     res = LoadGameOrPosition(matchGame); // setup game
10308     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10309     if(!res) return; // abort when bad game/pos file
10310     TwoMachinesEvent();
10311 }
10312
10313 void
10314 UserAdjudicationEvent (int result)
10315 {
10316     ChessMove gameResult = GameIsDrawn;
10317
10318     if( result > 0 ) {
10319         gameResult = WhiteWins;
10320     }
10321     else if( result < 0 ) {
10322         gameResult = BlackWins;
10323     }
10324
10325     if( gameMode == TwoMachinesPlay ) {
10326         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10327     }
10328 }
10329
10330
10331 // [HGM] save: calculate checksum of game to make games easily identifiable
10332 int
10333 StringCheckSum (char *s)
10334 {
10335         int i = 0;
10336         if(s==NULL) return 0;
10337         while(*s) i = i*259 + *s++;
10338         return i;
10339 }
10340
10341 int
10342 GameCheckSum ()
10343 {
10344         int i, sum=0;
10345         for(i=backwardMostMove; i<forwardMostMove; i++) {
10346                 sum += pvInfoList[i].depth;
10347                 sum += StringCheckSum(parseList[i]);
10348                 sum += StringCheckSum(commentList[i]);
10349                 sum *= 261;
10350         }
10351         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10352         return sum + StringCheckSum(commentList[i]);
10353 } // end of save patch
10354
10355 void
10356 GameEnds (ChessMove result, char *resultDetails, int whosays)
10357 {
10358     GameMode nextGameMode;
10359     int isIcsGame;
10360     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10361
10362     if(endingGame) return; /* [HGM] crash: forbid recursion */
10363     endingGame = 1;
10364     if(twoBoards) { // [HGM] dual: switch back to one board
10365         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10366         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10367     }
10368     if (appData.debugMode) {
10369       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10370               result, resultDetails ? resultDetails : "(null)", whosays);
10371     }
10372
10373     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10374
10375     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10376         /* If we are playing on ICS, the server decides when the
10377            game is over, but the engine can offer to draw, claim
10378            a draw, or resign.
10379          */
10380 #if ZIPPY
10381         if (appData.zippyPlay && first.initDone) {
10382             if (result == GameIsDrawn) {
10383                 /* In case draw still needs to be claimed */
10384                 SendToICS(ics_prefix);
10385                 SendToICS("draw\n");
10386             } else if (StrCaseStr(resultDetails, "resign")) {
10387                 SendToICS(ics_prefix);
10388                 SendToICS("resign\n");
10389             }
10390         }
10391 #endif
10392         endingGame = 0; /* [HGM] crash */
10393         return;
10394     }
10395
10396     /* If we're loading the game from a file, stop */
10397     if (whosays == GE_FILE) {
10398       (void) StopLoadGameTimer();
10399       gameFileFP = NULL;
10400     }
10401
10402     /* Cancel draw offers */
10403     first.offeredDraw = second.offeredDraw = 0;
10404
10405     /* If this is an ICS game, only ICS can really say it's done;
10406        if not, anyone can. */
10407     isIcsGame = (gameMode == IcsPlayingWhite ||
10408                  gameMode == IcsPlayingBlack ||
10409                  gameMode == IcsObserving    ||
10410                  gameMode == IcsExamining);
10411
10412     if (!isIcsGame || whosays == GE_ICS) {
10413         /* OK -- not an ICS game, or ICS said it was done */
10414         StopClocks();
10415         if (!isIcsGame && !appData.noChessProgram)
10416           SetUserThinkingEnables();
10417
10418         /* [HGM] if a machine claims the game end we verify this claim */
10419         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10420             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10421                 char claimer;
10422                 ChessMove trueResult = (ChessMove) -1;
10423
10424                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10425                                             first.twoMachinesColor[0] :
10426                                             second.twoMachinesColor[0] ;
10427
10428                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10429                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10430                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10431                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10432                 } else
10433                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10434                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10435                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10436                 } else
10437                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10438                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10439                 }
10440
10441                 // now verify win claims, but not in drop games, as we don't understand those yet
10442                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10443                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10444                     (result == WhiteWins && claimer == 'w' ||
10445                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10446                       if (appData.debugMode) {
10447                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10448                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10449                       }
10450                       if(result != trueResult) {
10451                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10452                               result = claimer == 'w' ? BlackWins : WhiteWins;
10453                               resultDetails = buf;
10454                       }
10455                 } else
10456                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10457                     && (forwardMostMove <= backwardMostMove ||
10458                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10459                         (claimer=='b')==(forwardMostMove&1))
10460                                                                                   ) {
10461                       /* [HGM] verify: draws that were not flagged are false claims */
10462                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10463                       result = claimer == 'w' ? BlackWins : WhiteWins;
10464                       resultDetails = buf;
10465                 }
10466                 /* (Claiming a loss is accepted no questions asked!) */
10467             }
10468             /* [HGM] bare: don't allow bare King to win */
10469             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10470                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10471                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10472                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10473                && result != GameIsDrawn)
10474             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10475                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10476                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10477                         if(p >= 0 && p <= (int)WhiteKing) k++;
10478                 }
10479                 if (appData.debugMode) {
10480                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10481                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10482                 }
10483                 if(k <= 1) {
10484                         result = GameIsDrawn;
10485                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10486                         resultDetails = buf;
10487                 }
10488             }
10489         }
10490
10491
10492         if(serverMoves != NULL && !loadFlag) { char c = '=';
10493             if(result==WhiteWins) c = '+';
10494             if(result==BlackWins) c = '-';
10495             if(resultDetails != NULL)
10496                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10497         }
10498         if (resultDetails != NULL) {
10499             gameInfo.result = result;
10500             gameInfo.resultDetails = StrSave(resultDetails);
10501
10502             /* display last move only if game was not loaded from file */
10503             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10504                 DisplayMove(currentMove - 1);
10505
10506             if (forwardMostMove != 0) {
10507                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10508                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10509                                                                 ) {
10510                     if (*appData.saveGameFile != NULLCHAR) {
10511                         SaveGameToFile(appData.saveGameFile, TRUE);
10512                     } else if (appData.autoSaveGames) {
10513                         AutoSaveGame();
10514                     }
10515                     if (*appData.savePositionFile != NULLCHAR) {
10516                         SavePositionToFile(appData.savePositionFile);
10517                     }
10518                 }
10519             }
10520
10521             /* Tell program how game ended in case it is learning */
10522             /* [HGM] Moved this to after saving the PGN, just in case */
10523             /* engine died and we got here through time loss. In that */
10524             /* case we will get a fatal error writing the pipe, which */
10525             /* would otherwise lose us the PGN.                       */
10526             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10527             /* output during GameEnds should never be fatal anymore   */
10528             if (gameMode == MachinePlaysWhite ||
10529                 gameMode == MachinePlaysBlack ||
10530                 gameMode == TwoMachinesPlay ||
10531                 gameMode == IcsPlayingWhite ||
10532                 gameMode == IcsPlayingBlack ||
10533                 gameMode == BeginningOfGame) {
10534                 char buf[MSG_SIZ];
10535                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10536                         resultDetails);
10537                 if (first.pr != NoProc) {
10538                     SendToProgram(buf, &first);
10539                 }
10540                 if (second.pr != NoProc &&
10541                     gameMode == TwoMachinesPlay) {
10542                     SendToProgram(buf, &second);
10543                 }
10544             }
10545         }
10546
10547         if (appData.icsActive) {
10548             if (appData.quietPlay &&
10549                 (gameMode == IcsPlayingWhite ||
10550                  gameMode == IcsPlayingBlack)) {
10551                 SendToICS(ics_prefix);
10552                 SendToICS("set shout 1\n");
10553             }
10554             nextGameMode = IcsIdle;
10555             ics_user_moved = FALSE;
10556             /* clean up premove.  It's ugly when the game has ended and the
10557              * premove highlights are still on the board.
10558              */
10559             if (gotPremove) {
10560               gotPremove = FALSE;
10561               ClearPremoveHighlights();
10562               DrawPosition(FALSE, boards[currentMove]);
10563             }
10564             if (whosays == GE_ICS) {
10565                 switch (result) {
10566                 case WhiteWins:
10567                     if (gameMode == IcsPlayingWhite)
10568                         PlayIcsWinSound();
10569                     else if(gameMode == IcsPlayingBlack)
10570                         PlayIcsLossSound();
10571                     break;
10572                 case BlackWins:
10573                     if (gameMode == IcsPlayingBlack)
10574                         PlayIcsWinSound();
10575                     else if(gameMode == IcsPlayingWhite)
10576                         PlayIcsLossSound();
10577                     break;
10578                 case GameIsDrawn:
10579                     PlayIcsDrawSound();
10580                     break;
10581                 default:
10582                     PlayIcsUnfinishedSound();
10583                 }
10584             }
10585         } else if (gameMode == EditGame ||
10586                    gameMode == PlayFromGameFile ||
10587                    gameMode == AnalyzeMode ||
10588                    gameMode == AnalyzeFile) {
10589             nextGameMode = gameMode;
10590         } else {
10591             nextGameMode = EndOfGame;
10592         }
10593         pausing = FALSE;
10594         ModeHighlight();
10595     } else {
10596         nextGameMode = gameMode;
10597     }
10598
10599     if (appData.noChessProgram) {
10600         gameMode = nextGameMode;
10601         ModeHighlight();
10602         endingGame = 0; /* [HGM] crash */
10603         return;
10604     }
10605
10606     if (first.reuse) {
10607         /* Put first chess program into idle state */
10608         if (first.pr != NoProc &&
10609             (gameMode == MachinePlaysWhite ||
10610              gameMode == MachinePlaysBlack ||
10611              gameMode == TwoMachinesPlay ||
10612              gameMode == IcsPlayingWhite ||
10613              gameMode == IcsPlayingBlack ||
10614              gameMode == BeginningOfGame)) {
10615             SendToProgram("force\n", &first);
10616             if (first.usePing) {
10617               char buf[MSG_SIZ];
10618               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10619               SendToProgram(buf, &first);
10620             }
10621         }
10622     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10623         /* Kill off first chess program */
10624         if (first.isr != NULL)
10625           RemoveInputSource(first.isr);
10626         first.isr = NULL;
10627
10628         if (first.pr != NoProc) {
10629             ExitAnalyzeMode();
10630             DoSleep( appData.delayBeforeQuit );
10631             SendToProgram("quit\n", &first);
10632             DoSleep( appData.delayAfterQuit );
10633             DestroyChildProcess(first.pr, first.useSigterm);
10634         }
10635         first.pr = NoProc;
10636     }
10637     if (second.reuse) {
10638         /* Put second chess program into idle state */
10639         if (second.pr != NoProc &&
10640             gameMode == TwoMachinesPlay) {
10641             SendToProgram("force\n", &second);
10642             if (second.usePing) {
10643               char buf[MSG_SIZ];
10644               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10645               SendToProgram(buf, &second);
10646             }
10647         }
10648     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10649         /* Kill off second chess program */
10650         if (second.isr != NULL)
10651           RemoveInputSource(second.isr);
10652         second.isr = NULL;
10653
10654         if (second.pr != NoProc) {
10655             DoSleep( appData.delayBeforeQuit );
10656             SendToProgram("quit\n", &second);
10657             DoSleep( appData.delayAfterQuit );
10658             DestroyChildProcess(second.pr, second.useSigterm);
10659         }
10660         second.pr = NoProc;
10661     }
10662
10663     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10664         char resChar = '=';
10665         switch (result) {
10666         case WhiteWins:
10667           resChar = '+';
10668           if (first.twoMachinesColor[0] == 'w') {
10669             first.matchWins++;
10670           } else {
10671             second.matchWins++;
10672           }
10673           break;
10674         case BlackWins:
10675           resChar = '-';
10676           if (first.twoMachinesColor[0] == 'b') {
10677             first.matchWins++;
10678           } else {
10679             second.matchWins++;
10680           }
10681           break;
10682         case GameUnfinished:
10683           resChar = ' ';
10684         default:
10685           break;
10686         }
10687
10688         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10689         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10690             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10691             ReserveGame(nextGame, resChar); // sets nextGame
10692             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10693             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10694         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10695
10696         if (nextGame <= appData.matchGames && !abortMatch) {
10697             gameMode = nextGameMode;
10698             matchGame = nextGame; // this will be overruled in tourney mode!
10699             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10700             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10701             endingGame = 0; /* [HGM] crash */
10702             return;
10703         } else {
10704             gameMode = nextGameMode;
10705             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10706                      first.tidy, second.tidy,
10707                      first.matchWins, second.matchWins,
10708                      appData.matchGames - (first.matchWins + second.matchWins));
10709             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10710             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10711             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10712             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10713                 first.twoMachinesColor = "black\n";
10714                 second.twoMachinesColor = "white\n";
10715             } else {
10716                 first.twoMachinesColor = "white\n";
10717                 second.twoMachinesColor = "black\n";
10718             }
10719         }
10720     }
10721     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10722         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10723       ExitAnalyzeMode();
10724     gameMode = nextGameMode;
10725     ModeHighlight();
10726     endingGame = 0;  /* [HGM] crash */
10727     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10728         if(matchMode == TRUE) { // match through command line: exit with or without popup
10729             if(ranking) {
10730                 ToNrEvent(forwardMostMove);
10731                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10732                 else ExitEvent(0);
10733             } else DisplayFatalError(buf, 0, 0);
10734         } else { // match through menu; just stop, with or without popup
10735             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10736             ModeHighlight();
10737             if(ranking){
10738                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10739             } else DisplayNote(buf);
10740       }
10741       if(ranking) free(ranking);
10742     }
10743 }
10744
10745 /* Assumes program was just initialized (initString sent).
10746    Leaves program in force mode. */
10747 void
10748 FeedMovesToProgram (ChessProgramState *cps, int upto)
10749 {
10750     int i;
10751
10752     if (appData.debugMode)
10753       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10754               startedFromSetupPosition ? "position and " : "",
10755               backwardMostMove, upto, cps->which);
10756     if(currentlyInitializedVariant != gameInfo.variant) {
10757       char buf[MSG_SIZ];
10758         // [HGM] variantswitch: make engine aware of new variant
10759         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10760                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10761         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10762         SendToProgram(buf, cps);
10763         currentlyInitializedVariant = gameInfo.variant;
10764     }
10765     SendToProgram("force\n", cps);
10766     if (startedFromSetupPosition) {
10767         SendBoard(cps, backwardMostMove);
10768     if (appData.debugMode) {
10769         fprintf(debugFP, "feedMoves\n");
10770     }
10771     }
10772     for (i = backwardMostMove; i < upto; i++) {
10773         SendMoveToProgram(i, cps);
10774     }
10775 }
10776
10777
10778 int
10779 ResurrectChessProgram ()
10780 {
10781      /* The chess program may have exited.
10782         If so, restart it and feed it all the moves made so far. */
10783     static int doInit = 0;
10784
10785     if (appData.noChessProgram) return 1;
10786
10787     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10788         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10789         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10790         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10791     } else {
10792         if (first.pr != NoProc) return 1;
10793         StartChessProgram(&first);
10794     }
10795     InitChessProgram(&first, FALSE);
10796     FeedMovesToProgram(&first, currentMove);
10797
10798     if (!first.sendTime) {
10799         /* can't tell gnuchess what its clock should read,
10800            so we bow to its notion. */
10801         ResetClocks();
10802         timeRemaining[0][currentMove] = whiteTimeRemaining;
10803         timeRemaining[1][currentMove] = blackTimeRemaining;
10804     }
10805
10806     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10807                 appData.icsEngineAnalyze) && first.analysisSupport) {
10808       SendToProgram("analyze\n", &first);
10809       first.analyzing = TRUE;
10810     }
10811     return 1;
10812 }
10813
10814 /*
10815  * Button procedures
10816  */
10817 void
10818 Reset (int redraw, int init)
10819 {
10820     int i;
10821
10822     if (appData.debugMode) {
10823         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10824                 redraw, init, gameMode);
10825     }
10826     CleanupTail(); // [HGM] vari: delete any stored variations
10827     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10828     pausing = pauseExamInvalid = FALSE;
10829     startedFromSetupPosition = blackPlaysFirst = FALSE;
10830     firstMove = TRUE;
10831     whiteFlag = blackFlag = FALSE;
10832     userOfferedDraw = FALSE;
10833     hintRequested = bookRequested = FALSE;
10834     first.maybeThinking = FALSE;
10835     second.maybeThinking = FALSE;
10836     first.bookSuspend = FALSE; // [HGM] book
10837     second.bookSuspend = FALSE;
10838     thinkOutput[0] = NULLCHAR;
10839     lastHint[0] = NULLCHAR;
10840     ClearGameInfo(&gameInfo);
10841     gameInfo.variant = StringToVariant(appData.variant);
10842     ics_user_moved = ics_clock_paused = FALSE;
10843     ics_getting_history = H_FALSE;
10844     ics_gamenum = -1;
10845     white_holding[0] = black_holding[0] = NULLCHAR;
10846     ClearProgramStats();
10847     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10848
10849     ResetFrontEnd();
10850     ClearHighlights();
10851     flipView = appData.flipView;
10852     ClearPremoveHighlights();
10853     gotPremove = FALSE;
10854     alarmSounded = FALSE;
10855
10856     GameEnds(EndOfFile, NULL, GE_PLAYER);
10857     if(appData.serverMovesName != NULL) {
10858         /* [HGM] prepare to make moves file for broadcasting */
10859         clock_t t = clock();
10860         if(serverMoves != NULL) fclose(serverMoves);
10861         serverMoves = fopen(appData.serverMovesName, "r");
10862         if(serverMoves != NULL) {
10863             fclose(serverMoves);
10864             /* delay 15 sec before overwriting, so all clients can see end */
10865             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10866         }
10867         serverMoves = fopen(appData.serverMovesName, "w");
10868     }
10869
10870     ExitAnalyzeMode();
10871     gameMode = BeginningOfGame;
10872     ModeHighlight();
10873     if(appData.icsActive) gameInfo.variant = VariantNormal;
10874     currentMove = forwardMostMove = backwardMostMove = 0;
10875     MarkTargetSquares(1);
10876     InitPosition(redraw);
10877     for (i = 0; i < MAX_MOVES; i++) {
10878         if (commentList[i] != NULL) {
10879             free(commentList[i]);
10880             commentList[i] = NULL;
10881         }
10882     }
10883     ResetClocks();
10884     timeRemaining[0][0] = whiteTimeRemaining;
10885     timeRemaining[1][0] = blackTimeRemaining;
10886
10887     if (first.pr == NoProc) {
10888         StartChessProgram(&first);
10889     }
10890     if (init) {
10891             InitChessProgram(&first, startedFromSetupPosition);
10892     }
10893     DisplayTitle("");
10894     DisplayMessage("", "");
10895     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10896     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10897     ClearMap();        // [HGM] exclude: invalidate map
10898 }
10899
10900 void
10901 AutoPlayGameLoop ()
10902 {
10903     for (;;) {
10904         if (!AutoPlayOneMove())
10905           return;
10906         if (matchMode || appData.timeDelay == 0)
10907           continue;
10908         if (appData.timeDelay < 0)
10909           return;
10910         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10911         break;
10912     }
10913 }
10914
10915
10916 int
10917 AutoPlayOneMove ()
10918 {
10919     int fromX, fromY, toX, toY;
10920
10921     if (appData.debugMode) {
10922       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10923     }
10924
10925     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10926       return FALSE;
10927
10928     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10929       pvInfoList[currentMove].depth = programStats.depth;
10930       pvInfoList[currentMove].score = programStats.score;
10931       pvInfoList[currentMove].time  = 0;
10932       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10933     }
10934
10935     if (currentMove >= forwardMostMove) {
10936       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10937 //      gameMode = EndOfGame;
10938 //      ModeHighlight();
10939
10940       /* [AS] Clear current move marker at the end of a game */
10941       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10942
10943       return FALSE;
10944     }
10945
10946     toX = moveList[currentMove][2] - AAA;
10947     toY = moveList[currentMove][3] - ONE;
10948
10949     if (moveList[currentMove][1] == '@') {
10950         if (appData.highlightLastMove) {
10951             SetHighlights(-1, -1, toX, toY);
10952         }
10953     } else {
10954         fromX = moveList[currentMove][0] - AAA;
10955         fromY = moveList[currentMove][1] - ONE;
10956
10957         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10958
10959         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10960
10961         if (appData.highlightLastMove) {
10962             SetHighlights(fromX, fromY, toX, toY);
10963         }
10964     }
10965     DisplayMove(currentMove);
10966     SendMoveToProgram(currentMove++, &first);
10967     DisplayBothClocks();
10968     DrawPosition(FALSE, boards[currentMove]);
10969     // [HGM] PV info: always display, routine tests if empty
10970     DisplayComment(currentMove - 1, commentList[currentMove]);
10971     return TRUE;
10972 }
10973
10974
10975 int
10976 LoadGameOneMove (ChessMove readAhead)
10977 {
10978     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10979     char promoChar = NULLCHAR;
10980     ChessMove moveType;
10981     char move[MSG_SIZ];
10982     char *p, *q;
10983
10984     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10985         gameMode != AnalyzeMode && gameMode != Training) {
10986         gameFileFP = NULL;
10987         return FALSE;
10988     }
10989
10990     yyboardindex = forwardMostMove;
10991     if (readAhead != EndOfFile) {
10992       moveType = readAhead;
10993     } else {
10994       if (gameFileFP == NULL)
10995           return FALSE;
10996       moveType = (ChessMove) Myylex();
10997     }
10998
10999     done = FALSE;
11000     switch (moveType) {
11001       case Comment:
11002         if (appData.debugMode)
11003           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11004         p = yy_text;
11005
11006         /* append the comment but don't display it */
11007         AppendComment(currentMove, p, FALSE);
11008         return TRUE;
11009
11010       case WhiteCapturesEnPassant:
11011       case BlackCapturesEnPassant:
11012       case WhitePromotion:
11013       case BlackPromotion:
11014       case WhiteNonPromotion:
11015       case BlackNonPromotion:
11016       case NormalMove:
11017       case WhiteKingSideCastle:
11018       case WhiteQueenSideCastle:
11019       case BlackKingSideCastle:
11020       case BlackQueenSideCastle:
11021       case WhiteKingSideCastleWild:
11022       case WhiteQueenSideCastleWild:
11023       case BlackKingSideCastleWild:
11024       case BlackQueenSideCastleWild:
11025       /* PUSH Fabien */
11026       case WhiteHSideCastleFR:
11027       case WhiteASideCastleFR:
11028       case BlackHSideCastleFR:
11029       case BlackASideCastleFR:
11030       /* POP Fabien */
11031         if (appData.debugMode)
11032           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11033         fromX = currentMoveString[0] - AAA;
11034         fromY = currentMoveString[1] - ONE;
11035         toX = currentMoveString[2] - AAA;
11036         toY = currentMoveString[3] - ONE;
11037         promoChar = currentMoveString[4];
11038         break;
11039
11040       case WhiteDrop:
11041       case BlackDrop:
11042         if (appData.debugMode)
11043           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11044         fromX = moveType == WhiteDrop ?
11045           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11046         (int) CharToPiece(ToLower(currentMoveString[0]));
11047         fromY = DROP_RANK;
11048         toX = currentMoveString[2] - AAA;
11049         toY = currentMoveString[3] - ONE;
11050         break;
11051
11052       case WhiteWins:
11053       case BlackWins:
11054       case GameIsDrawn:
11055       case GameUnfinished:
11056         if (appData.debugMode)
11057           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11058         p = strchr(yy_text, '{');
11059         if (p == NULL) p = strchr(yy_text, '(');
11060         if (p == NULL) {
11061             p = yy_text;
11062             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11063         } else {
11064             q = strchr(p, *p == '{' ? '}' : ')');
11065             if (q != NULL) *q = NULLCHAR;
11066             p++;
11067         }
11068         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11069         GameEnds(moveType, p, GE_FILE);
11070         done = TRUE;
11071         if (cmailMsgLoaded) {
11072             ClearHighlights();
11073             flipView = WhiteOnMove(currentMove);
11074             if (moveType == GameUnfinished) flipView = !flipView;
11075             if (appData.debugMode)
11076               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11077         }
11078         break;
11079
11080       case EndOfFile:
11081         if (appData.debugMode)
11082           fprintf(debugFP, "Parser hit end of file\n");
11083         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11084           case MT_NONE:
11085           case MT_CHECK:
11086             break;
11087           case MT_CHECKMATE:
11088           case MT_STAINMATE:
11089             if (WhiteOnMove(currentMove)) {
11090                 GameEnds(BlackWins, "Black mates", GE_FILE);
11091             } else {
11092                 GameEnds(WhiteWins, "White mates", GE_FILE);
11093             }
11094             break;
11095           case MT_STALEMATE:
11096             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11097             break;
11098         }
11099         done = TRUE;
11100         break;
11101
11102       case MoveNumberOne:
11103         if (lastLoadGameStart == GNUChessGame) {
11104             /* GNUChessGames have numbers, but they aren't move numbers */
11105             if (appData.debugMode)
11106               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11107                       yy_text, (int) moveType);
11108             return LoadGameOneMove(EndOfFile); /* tail recursion */
11109         }
11110         /* else fall thru */
11111
11112       case XBoardGame:
11113       case GNUChessGame:
11114       case PGNTag:
11115         /* Reached start of next game in file */
11116         if (appData.debugMode)
11117           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11118         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11119           case MT_NONE:
11120           case MT_CHECK:
11121             break;
11122           case MT_CHECKMATE:
11123           case MT_STAINMATE:
11124             if (WhiteOnMove(currentMove)) {
11125                 GameEnds(BlackWins, "Black mates", GE_FILE);
11126             } else {
11127                 GameEnds(WhiteWins, "White mates", GE_FILE);
11128             }
11129             break;
11130           case MT_STALEMATE:
11131             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11132             break;
11133         }
11134         done = TRUE;
11135         break;
11136
11137       case PositionDiagram:     /* should not happen; ignore */
11138       case ElapsedTime:         /* ignore */
11139       case NAG:                 /* ignore */
11140         if (appData.debugMode)
11141           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11142                   yy_text, (int) moveType);
11143         return LoadGameOneMove(EndOfFile); /* tail recursion */
11144
11145       case IllegalMove:
11146         if (appData.testLegality) {
11147             if (appData.debugMode)
11148               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11149             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11150                     (forwardMostMove / 2) + 1,
11151                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11152             DisplayError(move, 0);
11153             done = TRUE;
11154         } else {
11155             if (appData.debugMode)
11156               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11157                       yy_text, currentMoveString);
11158             fromX = currentMoveString[0] - AAA;
11159             fromY = currentMoveString[1] - ONE;
11160             toX = currentMoveString[2] - AAA;
11161             toY = currentMoveString[3] - ONE;
11162             promoChar = currentMoveString[4];
11163         }
11164         break;
11165
11166       case AmbiguousMove:
11167         if (appData.debugMode)
11168           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11169         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11170                 (forwardMostMove / 2) + 1,
11171                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11172         DisplayError(move, 0);
11173         done = TRUE;
11174         break;
11175
11176       default:
11177       case ImpossibleMove:
11178         if (appData.debugMode)
11179           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11180         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11181                 (forwardMostMove / 2) + 1,
11182                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11183         DisplayError(move, 0);
11184         done = TRUE;
11185         break;
11186     }
11187
11188     if (done) {
11189         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11190             DrawPosition(FALSE, boards[currentMove]);
11191             DisplayBothClocks();
11192             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11193               DisplayComment(currentMove - 1, commentList[currentMove]);
11194         }
11195         (void) StopLoadGameTimer();
11196         gameFileFP = NULL;
11197         cmailOldMove = forwardMostMove;
11198         return FALSE;
11199     } else {
11200         /* currentMoveString is set as a side-effect of yylex */
11201
11202         thinkOutput[0] = NULLCHAR;
11203         MakeMove(fromX, fromY, toX, toY, promoChar);
11204         currentMove = forwardMostMove;
11205         return TRUE;
11206     }
11207 }
11208
11209 /* Load the nth game from the given file */
11210 int
11211 LoadGameFromFile (char *filename, int n, char *title, int useList)
11212 {
11213     FILE *f;
11214     char buf[MSG_SIZ];
11215
11216     if (strcmp(filename, "-") == 0) {
11217         f = stdin;
11218         title = "stdin";
11219     } else {
11220         f = fopen(filename, "rb");
11221         if (f == NULL) {
11222           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11223             DisplayError(buf, errno);
11224             return FALSE;
11225         }
11226     }
11227     if (fseek(f, 0, 0) == -1) {
11228         /* f is not seekable; probably a pipe */
11229         useList = FALSE;
11230     }
11231     if (useList && n == 0) {
11232         int error = GameListBuild(f);
11233         if (error) {
11234             DisplayError(_("Cannot build game list"), error);
11235         } else if (!ListEmpty(&gameList) &&
11236                    ((ListGame *) gameList.tailPred)->number > 1) {
11237             GameListPopUp(f, title);
11238             return TRUE;
11239         }
11240         GameListDestroy();
11241         n = 1;
11242     }
11243     if (n == 0) n = 1;
11244     return LoadGame(f, n, title, FALSE);
11245 }
11246
11247
11248 void
11249 MakeRegisteredMove ()
11250 {
11251     int fromX, fromY, toX, toY;
11252     char promoChar;
11253     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11254         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11255           case CMAIL_MOVE:
11256           case CMAIL_DRAW:
11257             if (appData.debugMode)
11258               fprintf(debugFP, "Restoring %s for game %d\n",
11259                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11260
11261             thinkOutput[0] = NULLCHAR;
11262             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11263             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11264             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11265             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11266             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11267             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11268             MakeMove(fromX, fromY, toX, toY, promoChar);
11269             ShowMove(fromX, fromY, toX, toY);
11270
11271             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11272               case MT_NONE:
11273               case MT_CHECK:
11274                 break;
11275
11276               case MT_CHECKMATE:
11277               case MT_STAINMATE:
11278                 if (WhiteOnMove(currentMove)) {
11279                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11280                 } else {
11281                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11282                 }
11283                 break;
11284
11285               case MT_STALEMATE:
11286                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11287                 break;
11288             }
11289
11290             break;
11291
11292           case CMAIL_RESIGN:
11293             if (WhiteOnMove(currentMove)) {
11294                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11295             } else {
11296                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11297             }
11298             break;
11299
11300           case CMAIL_ACCEPT:
11301             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11302             break;
11303
11304           default:
11305             break;
11306         }
11307     }
11308
11309     return;
11310 }
11311
11312 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11313 int
11314 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11315 {
11316     int retVal;
11317
11318     if (gameNumber > nCmailGames) {
11319         DisplayError(_("No more games in this message"), 0);
11320         return FALSE;
11321     }
11322     if (f == lastLoadGameFP) {
11323         int offset = gameNumber - lastLoadGameNumber;
11324         if (offset == 0) {
11325             cmailMsg[0] = NULLCHAR;
11326             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11327                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11328                 nCmailMovesRegistered--;
11329             }
11330             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11331             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11332                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11333             }
11334         } else {
11335             if (! RegisterMove()) return FALSE;
11336         }
11337     }
11338
11339     retVal = LoadGame(f, gameNumber, title, useList);
11340
11341     /* Make move registered during previous look at this game, if any */
11342     MakeRegisteredMove();
11343
11344     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11345         commentList[currentMove]
11346           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11347         DisplayComment(currentMove - 1, commentList[currentMove]);
11348     }
11349
11350     return retVal;
11351 }
11352
11353 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11354 int
11355 ReloadGame (int offset)
11356 {
11357     int gameNumber = lastLoadGameNumber + offset;
11358     if (lastLoadGameFP == NULL) {
11359         DisplayError(_("No game has been loaded yet"), 0);
11360         return FALSE;
11361     }
11362     if (gameNumber <= 0) {
11363         DisplayError(_("Can't back up any further"), 0);
11364         return FALSE;
11365     }
11366     if (cmailMsgLoaded) {
11367         return CmailLoadGame(lastLoadGameFP, gameNumber,
11368                              lastLoadGameTitle, lastLoadGameUseList);
11369     } else {
11370         return LoadGame(lastLoadGameFP, gameNumber,
11371                         lastLoadGameTitle, lastLoadGameUseList);
11372     }
11373 }
11374
11375 int keys[EmptySquare+1];
11376
11377 int
11378 PositionMatches (Board b1, Board b2)
11379 {
11380     int r, f, sum=0;
11381     switch(appData.searchMode) {
11382         case 1: return CompareWithRights(b1, b2);
11383         case 2:
11384             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11385                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11386             }
11387             return TRUE;
11388         case 3:
11389             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11390               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11391                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11392             }
11393             return sum==0;
11394         case 4:
11395             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11396                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11397             }
11398             return sum==0;
11399     }
11400     return TRUE;
11401 }
11402
11403 #define Q_PROMO  4
11404 #define Q_EP     3
11405 #define Q_BCASTL 2
11406 #define Q_WCASTL 1
11407
11408 int pieceList[256], quickBoard[256];
11409 ChessSquare pieceType[256] = { EmptySquare };
11410 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11411 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11412 int soughtTotal, turn;
11413 Boolean epOK, flipSearch;
11414
11415 typedef struct {
11416     unsigned char piece, to;
11417 } Move;
11418
11419 #define DSIZE (250000)
11420
11421 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11422 Move *moveDatabase = initialSpace;
11423 unsigned int movePtr, dataSize = DSIZE;
11424
11425 int
11426 MakePieceList (Board board, int *counts)
11427 {
11428     int r, f, n=Q_PROMO, total=0;
11429     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11430     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11431         int sq = f + (r<<4);
11432         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11433             quickBoard[sq] = ++n;
11434             pieceList[n] = sq;
11435             pieceType[n] = board[r][f];
11436             counts[board[r][f]]++;
11437             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11438             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11439             total++;
11440         }
11441     }
11442     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11443     return total;
11444 }
11445
11446 void
11447 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11448 {
11449     int sq = fromX + (fromY<<4);
11450     int piece = quickBoard[sq];
11451     quickBoard[sq] = 0;
11452     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11453     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11454         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11455         moveDatabase[movePtr++].piece = Q_WCASTL;
11456         quickBoard[sq] = piece;
11457         piece = quickBoard[from]; quickBoard[from] = 0;
11458         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11459     } else
11460     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11461         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11462         moveDatabase[movePtr++].piece = Q_BCASTL;
11463         quickBoard[sq] = piece;
11464         piece = quickBoard[from]; quickBoard[from] = 0;
11465         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11466     } else
11467     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11468         quickBoard[(fromY<<4)+toX] = 0;
11469         moveDatabase[movePtr].piece = Q_EP;
11470         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11471         moveDatabase[movePtr].to = sq;
11472     } else
11473     if(promoPiece != pieceType[piece]) {
11474         moveDatabase[movePtr++].piece = Q_PROMO;
11475         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11476     }
11477     moveDatabase[movePtr].piece = piece;
11478     quickBoard[sq] = piece;
11479     movePtr++;
11480 }
11481
11482 int
11483 PackGame (Board board)
11484 {
11485     Move *newSpace = NULL;
11486     moveDatabase[movePtr].piece = 0; // terminate previous game
11487     if(movePtr > dataSize) {
11488         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11489         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11490         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11491         if(newSpace) {
11492             int i;
11493             Move *p = moveDatabase, *q = newSpace;
11494             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11495             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11496             moveDatabase = newSpace;
11497         } else { // calloc failed, we must be out of memory. Too bad...
11498             dataSize = 0; // prevent calloc events for all subsequent games
11499             return 0;     // and signal this one isn't cached
11500         }
11501     }
11502     movePtr++;
11503     MakePieceList(board, counts);
11504     return movePtr;
11505 }
11506
11507 int
11508 QuickCompare (Board board, int *minCounts, int *maxCounts)
11509 {   // compare according to search mode
11510     int r, f;
11511     switch(appData.searchMode)
11512     {
11513       case 1: // exact position match
11514         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11515         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11516             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11517         }
11518         break;
11519       case 2: // can have extra material on empty squares
11520         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11521             if(board[r][f] == EmptySquare) continue;
11522             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11523         }
11524         break;
11525       case 3: // material with exact Pawn structure
11526         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11527             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11528             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11529         } // fall through to material comparison
11530       case 4: // exact material
11531         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11532         break;
11533       case 6: // material range with given imbalance
11534         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11535         // fall through to range comparison
11536       case 5: // material range
11537         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11538     }
11539     return TRUE;
11540 }
11541
11542 int
11543 QuickScan (Board board, Move *move)
11544 {   // reconstruct game,and compare all positions in it
11545     int cnt=0, stretch=0, total = MakePieceList(board, counts), delayedKing = -1;
11546     do {
11547         int piece = move->piece;
11548         int to = move->to, from = pieceList[piece];
11549         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11550           if(!piece) return -1;
11551           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11552             piece = (++move)->piece;
11553             from = pieceList[piece];
11554             counts[pieceType[piece]]--;
11555             pieceType[piece] = (ChessSquare) move->to;
11556             counts[move->to]++;
11557           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11558             counts[pieceType[quickBoard[to]]]--;
11559             quickBoard[to] = 0; total--;
11560             move++;
11561             continue;
11562           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11563             int rook;
11564             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11565             from  = pieceList[piece]; // so this must be King
11566             quickBoard[from] = 0;
11567             pieceList[piece] = to;
11568             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11569             quickBoard[from] = 0; // rook
11570             quickBoard[to] = piece;
11571             to = move->to; piece = move->piece;
11572             goto aftercastle;
11573           }
11574         }
11575         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11576         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11577         quickBoard[from] = 0;
11578       aftercastle:
11579         quickBoard[to] = piece;
11580         pieceList[piece] = to;
11581         cnt++; turn ^= 3;
11582         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11583            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11584            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11585                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11586           ) {
11587             static int lastCounts[EmptySquare+1];
11588             int i;
11589             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11590             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11591         } else stretch = 0;
11592         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11593         move++; delayedKing = -1;
11594     } while(1);
11595 }
11596
11597 void
11598 InitSearch ()
11599 {
11600     int r, f;
11601     flipSearch = FALSE;
11602     CopyBoard(soughtBoard, boards[currentMove]);
11603     soughtTotal = MakePieceList(soughtBoard, maxSought);
11604     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11605     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11606     CopyBoard(reverseBoard, boards[currentMove]);
11607     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11608         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11609         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11610         reverseBoard[r][f] = piece;
11611     }
11612     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11613     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11614     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11615                  || (boards[currentMove][CASTLING][2] == NoRights || 
11616                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11617                  && (boards[currentMove][CASTLING][5] == NoRights || 
11618                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11619       ) {
11620         flipSearch = TRUE;
11621         CopyBoard(flipBoard, soughtBoard);
11622         CopyBoard(rotateBoard, reverseBoard);
11623         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11624             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11625             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11626         }
11627     }
11628     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11629     if(appData.searchMode >= 5) {
11630         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11631         MakePieceList(soughtBoard, minSought);
11632         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11633     }
11634     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11635         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11636 }
11637
11638 GameInfo dummyInfo;
11639
11640 int
11641 GameContainsPosition (FILE *f, ListGame *lg)
11642 {
11643     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11644     int fromX, fromY, toX, toY;
11645     char promoChar;
11646     static int initDone=FALSE;
11647
11648     // weed out games based on numerical tag comparison
11649     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11650     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11651     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11652     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11653     if(!initDone) {
11654         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11655         initDone = TRUE;
11656     }
11657     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11658     else CopyBoard(boards[scratch], initialPosition); // default start position
11659     if(lg->moves) {
11660         turn = btm + 1;
11661         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11662         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11663     }
11664     if(btm) plyNr++;
11665     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11666     fseek(f, lg->offset, 0);
11667     yynewfile(f);
11668     while(1) {
11669         yyboardindex = scratch;
11670         quickFlag = plyNr+1;
11671         next = Myylex();
11672         quickFlag = 0;
11673         switch(next) {
11674             case PGNTag:
11675                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11676             default:
11677                 continue;
11678
11679             case XBoardGame:
11680             case GNUChessGame:
11681                 if(plyNr) return -1; // after we have seen moves, this is for new game
11682               continue;
11683
11684             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11685             case ImpossibleMove:
11686             case WhiteWins: // game ends here with these four
11687             case BlackWins:
11688             case GameIsDrawn:
11689             case GameUnfinished:
11690                 return -1;
11691
11692             case IllegalMove:
11693                 if(appData.testLegality) return -1;
11694             case WhiteCapturesEnPassant:
11695             case BlackCapturesEnPassant:
11696             case WhitePromotion:
11697             case BlackPromotion:
11698             case WhiteNonPromotion:
11699             case BlackNonPromotion:
11700             case NormalMove:
11701             case WhiteKingSideCastle:
11702             case WhiteQueenSideCastle:
11703             case BlackKingSideCastle:
11704             case BlackQueenSideCastle:
11705             case WhiteKingSideCastleWild:
11706             case WhiteQueenSideCastleWild:
11707             case BlackKingSideCastleWild:
11708             case BlackQueenSideCastleWild:
11709             case WhiteHSideCastleFR:
11710             case WhiteASideCastleFR:
11711             case BlackHSideCastleFR:
11712             case BlackASideCastleFR:
11713                 fromX = currentMoveString[0] - AAA;
11714                 fromY = currentMoveString[1] - ONE;
11715                 toX = currentMoveString[2] - AAA;
11716                 toY = currentMoveString[3] - ONE;
11717                 promoChar = currentMoveString[4];
11718                 break;
11719             case WhiteDrop:
11720             case BlackDrop:
11721                 fromX = next == WhiteDrop ?
11722                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11723                   (int) CharToPiece(ToLower(currentMoveString[0]));
11724                 fromY = DROP_RANK;
11725                 toX = currentMoveString[2] - AAA;
11726                 toY = currentMoveString[3] - ONE;
11727                 promoChar = 0;
11728                 break;
11729         }
11730         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11731         plyNr++;
11732         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11733         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11734         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11735         if(appData.findMirror) {
11736             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11737             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11738         }
11739     }
11740 }
11741
11742 /* Load the nth game from open file f */
11743 int
11744 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11745 {
11746     ChessMove cm;
11747     char buf[MSG_SIZ];
11748     int gn = gameNumber;
11749     ListGame *lg = NULL;
11750     int numPGNTags = 0;
11751     int err, pos = -1;
11752     GameMode oldGameMode;
11753     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11754
11755     if (appData.debugMode)
11756         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11757
11758     if (gameMode == Training )
11759         SetTrainingModeOff();
11760
11761     oldGameMode = gameMode;
11762     if (gameMode != BeginningOfGame) {
11763       Reset(FALSE, TRUE);
11764     }
11765
11766     gameFileFP = f;
11767     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11768         fclose(lastLoadGameFP);
11769     }
11770
11771     if (useList) {
11772         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11773
11774         if (lg) {
11775             fseek(f, lg->offset, 0);
11776             GameListHighlight(gameNumber);
11777             pos = lg->position;
11778             gn = 1;
11779         }
11780         else {
11781             DisplayError(_("Game number out of range"), 0);
11782             return FALSE;
11783         }
11784     } else {
11785         GameListDestroy();
11786         if (fseek(f, 0, 0) == -1) {
11787             if (f == lastLoadGameFP ?
11788                 gameNumber == lastLoadGameNumber + 1 :
11789                 gameNumber == 1) {
11790                 gn = 1;
11791             } else {
11792                 DisplayError(_("Can't seek on game file"), 0);
11793                 return FALSE;
11794             }
11795         }
11796     }
11797     lastLoadGameFP = f;
11798     lastLoadGameNumber = gameNumber;
11799     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11800     lastLoadGameUseList = useList;
11801
11802     yynewfile(f);
11803
11804     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11805       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11806                 lg->gameInfo.black);
11807             DisplayTitle(buf);
11808     } else if (*title != NULLCHAR) {
11809         if (gameNumber > 1) {
11810           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11811             DisplayTitle(buf);
11812         } else {
11813             DisplayTitle(title);
11814         }
11815     }
11816
11817     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11818         gameMode = PlayFromGameFile;
11819         ModeHighlight();
11820     }
11821
11822     currentMove = forwardMostMove = backwardMostMove = 0;
11823     CopyBoard(boards[0], initialPosition);
11824     StopClocks();
11825
11826     /*
11827      * Skip the first gn-1 games in the file.
11828      * Also skip over anything that precedes an identifiable
11829      * start of game marker, to avoid being confused by
11830      * garbage at the start of the file.  Currently
11831      * recognized start of game markers are the move number "1",
11832      * the pattern "gnuchess .* game", the pattern
11833      * "^[#;%] [^ ]* game file", and a PGN tag block.
11834      * A game that starts with one of the latter two patterns
11835      * will also have a move number 1, possibly
11836      * following a position diagram.
11837      * 5-4-02: Let's try being more lenient and allowing a game to
11838      * start with an unnumbered move.  Does that break anything?
11839      */
11840     cm = lastLoadGameStart = EndOfFile;
11841     while (gn > 0) {
11842         yyboardindex = forwardMostMove;
11843         cm = (ChessMove) Myylex();
11844         switch (cm) {
11845           case EndOfFile:
11846             if (cmailMsgLoaded) {
11847                 nCmailGames = CMAIL_MAX_GAMES - gn;
11848             } else {
11849                 Reset(TRUE, TRUE);
11850                 DisplayError(_("Game not found in file"), 0);
11851             }
11852             return FALSE;
11853
11854           case GNUChessGame:
11855           case XBoardGame:
11856             gn--;
11857             lastLoadGameStart = cm;
11858             break;
11859
11860           case MoveNumberOne:
11861             switch (lastLoadGameStart) {
11862               case GNUChessGame:
11863               case XBoardGame:
11864               case PGNTag:
11865                 break;
11866               case MoveNumberOne:
11867               case EndOfFile:
11868                 gn--;           /* count this game */
11869                 lastLoadGameStart = cm;
11870                 break;
11871               default:
11872                 /* impossible */
11873                 break;
11874             }
11875             break;
11876
11877           case PGNTag:
11878             switch (lastLoadGameStart) {
11879               case GNUChessGame:
11880               case PGNTag:
11881               case MoveNumberOne:
11882               case EndOfFile:
11883                 gn--;           /* count this game */
11884                 lastLoadGameStart = cm;
11885                 break;
11886               case XBoardGame:
11887                 lastLoadGameStart = cm; /* game counted already */
11888                 break;
11889               default:
11890                 /* impossible */
11891                 break;
11892             }
11893             if (gn > 0) {
11894                 do {
11895                     yyboardindex = forwardMostMove;
11896                     cm = (ChessMove) Myylex();
11897                 } while (cm == PGNTag || cm == Comment);
11898             }
11899             break;
11900
11901           case WhiteWins:
11902           case BlackWins:
11903           case GameIsDrawn:
11904             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11905                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11906                     != CMAIL_OLD_RESULT) {
11907                     nCmailResults ++ ;
11908                     cmailResult[  CMAIL_MAX_GAMES
11909                                 - gn - 1] = CMAIL_OLD_RESULT;
11910                 }
11911             }
11912             break;
11913
11914           case NormalMove:
11915             /* Only a NormalMove can be at the start of a game
11916              * without a position diagram. */
11917             if (lastLoadGameStart == EndOfFile ) {
11918               gn--;
11919               lastLoadGameStart = MoveNumberOne;
11920             }
11921             break;
11922
11923           default:
11924             break;
11925         }
11926     }
11927
11928     if (appData.debugMode)
11929       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11930
11931     if (cm == XBoardGame) {
11932         /* Skip any header junk before position diagram and/or move 1 */
11933         for (;;) {
11934             yyboardindex = forwardMostMove;
11935             cm = (ChessMove) Myylex();
11936
11937             if (cm == EndOfFile ||
11938                 cm == GNUChessGame || cm == XBoardGame) {
11939                 /* Empty game; pretend end-of-file and handle later */
11940                 cm = EndOfFile;
11941                 break;
11942             }
11943
11944             if (cm == MoveNumberOne || cm == PositionDiagram ||
11945                 cm == PGNTag || cm == Comment)
11946               break;
11947         }
11948     } else if (cm == GNUChessGame) {
11949         if (gameInfo.event != NULL) {
11950             free(gameInfo.event);
11951         }
11952         gameInfo.event = StrSave(yy_text);
11953     }
11954
11955     startedFromSetupPosition = FALSE;
11956     while (cm == PGNTag) {
11957         if (appData.debugMode)
11958           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11959         err = ParsePGNTag(yy_text, &gameInfo);
11960         if (!err) numPGNTags++;
11961
11962         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11963         if(gameInfo.variant != oldVariant) {
11964             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11965             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11966             InitPosition(TRUE);
11967             oldVariant = gameInfo.variant;
11968             if (appData.debugMode)
11969               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11970         }
11971
11972
11973         if (gameInfo.fen != NULL) {
11974           Board initial_position;
11975           startedFromSetupPosition = TRUE;
11976           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11977             Reset(TRUE, TRUE);
11978             DisplayError(_("Bad FEN position in file"), 0);
11979             return FALSE;
11980           }
11981           CopyBoard(boards[0], initial_position);
11982           if (blackPlaysFirst) {
11983             currentMove = forwardMostMove = backwardMostMove = 1;
11984             CopyBoard(boards[1], initial_position);
11985             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11986             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11987             timeRemaining[0][1] = whiteTimeRemaining;
11988             timeRemaining[1][1] = blackTimeRemaining;
11989             if (commentList[0] != NULL) {
11990               commentList[1] = commentList[0];
11991               commentList[0] = NULL;
11992             }
11993           } else {
11994             currentMove = forwardMostMove = backwardMostMove = 0;
11995           }
11996           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11997           {   int i;
11998               initialRulePlies = FENrulePlies;
11999               for( i=0; i< nrCastlingRights; i++ )
12000                   initialRights[i] = initial_position[CASTLING][i];
12001           }
12002           yyboardindex = forwardMostMove;
12003           free(gameInfo.fen);
12004           gameInfo.fen = NULL;
12005         }
12006
12007         yyboardindex = forwardMostMove;
12008         cm = (ChessMove) Myylex();
12009
12010         /* Handle comments interspersed among the tags */
12011         while (cm == Comment) {
12012             char *p;
12013             if (appData.debugMode)
12014               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12015             p = yy_text;
12016             AppendComment(currentMove, p, FALSE);
12017             yyboardindex = forwardMostMove;
12018             cm = (ChessMove) Myylex();
12019         }
12020     }
12021
12022     /* don't rely on existence of Event tag since if game was
12023      * pasted from clipboard the Event tag may not exist
12024      */
12025     if (numPGNTags > 0){
12026         char *tags;
12027         if (gameInfo.variant == VariantNormal) {
12028           VariantClass v = StringToVariant(gameInfo.event);
12029           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12030           if(v < VariantShogi) gameInfo.variant = v;
12031         }
12032         if (!matchMode) {
12033           if( appData.autoDisplayTags ) {
12034             tags = PGNTags(&gameInfo);
12035             TagsPopUp(tags, CmailMsg());
12036             free(tags);
12037           }
12038         }
12039     } else {
12040         /* Make something up, but don't display it now */
12041         SetGameInfo();
12042         TagsPopDown();
12043     }
12044
12045     if (cm == PositionDiagram) {
12046         int i, j;
12047         char *p;
12048         Board initial_position;
12049
12050         if (appData.debugMode)
12051           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12052
12053         if (!startedFromSetupPosition) {
12054             p = yy_text;
12055             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12056               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12057                 switch (*p) {
12058                   case '{':
12059                   case '[':
12060                   case '-':
12061                   case ' ':
12062                   case '\t':
12063                   case '\n':
12064                   case '\r':
12065                     break;
12066                   default:
12067                     initial_position[i][j++] = CharToPiece(*p);
12068                     break;
12069                 }
12070             while (*p == ' ' || *p == '\t' ||
12071                    *p == '\n' || *p == '\r') p++;
12072
12073             if (strncmp(p, "black", strlen("black"))==0)
12074               blackPlaysFirst = TRUE;
12075             else
12076               blackPlaysFirst = FALSE;
12077             startedFromSetupPosition = TRUE;
12078
12079             CopyBoard(boards[0], initial_position);
12080             if (blackPlaysFirst) {
12081                 currentMove = forwardMostMove = backwardMostMove = 1;
12082                 CopyBoard(boards[1], initial_position);
12083                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12084                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12085                 timeRemaining[0][1] = whiteTimeRemaining;
12086                 timeRemaining[1][1] = blackTimeRemaining;
12087                 if (commentList[0] != NULL) {
12088                     commentList[1] = commentList[0];
12089                     commentList[0] = NULL;
12090                 }
12091             } else {
12092                 currentMove = forwardMostMove = backwardMostMove = 0;
12093             }
12094         }
12095         yyboardindex = forwardMostMove;
12096         cm = (ChessMove) Myylex();
12097     }
12098
12099     if (first.pr == NoProc) {
12100         StartChessProgram(&first);
12101     }
12102     InitChessProgram(&first, FALSE);
12103     SendToProgram("force\n", &first);
12104     if (startedFromSetupPosition) {
12105         SendBoard(&first, forwardMostMove);
12106     if (appData.debugMode) {
12107         fprintf(debugFP, "Load Game\n");
12108     }
12109         DisplayBothClocks();
12110     }
12111
12112     /* [HGM] server: flag to write setup moves in broadcast file as one */
12113     loadFlag = appData.suppressLoadMoves;
12114
12115     while (cm == Comment) {
12116         char *p;
12117         if (appData.debugMode)
12118           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12119         p = yy_text;
12120         AppendComment(currentMove, p, FALSE);
12121         yyboardindex = forwardMostMove;
12122         cm = (ChessMove) Myylex();
12123     }
12124
12125     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12126         cm == WhiteWins || cm == BlackWins ||
12127         cm == GameIsDrawn || cm == GameUnfinished) {
12128         DisplayMessage("", _("No moves in game"));
12129         if (cmailMsgLoaded) {
12130             if (appData.debugMode)
12131               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12132             ClearHighlights();
12133             flipView = FALSE;
12134         }
12135         DrawPosition(FALSE, boards[currentMove]);
12136         DisplayBothClocks();
12137         gameMode = EditGame;
12138         ModeHighlight();
12139         gameFileFP = NULL;
12140         cmailOldMove = 0;
12141         return TRUE;
12142     }
12143
12144     // [HGM] PV info: routine tests if comment empty
12145     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12146         DisplayComment(currentMove - 1, commentList[currentMove]);
12147     }
12148     if (!matchMode && appData.timeDelay != 0)
12149       DrawPosition(FALSE, boards[currentMove]);
12150
12151     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12152       programStats.ok_to_send = 1;
12153     }
12154
12155     /* if the first token after the PGN tags is a move
12156      * and not move number 1, retrieve it from the parser
12157      */
12158     if (cm != MoveNumberOne)
12159         LoadGameOneMove(cm);
12160
12161     /* load the remaining moves from the file */
12162     while (LoadGameOneMove(EndOfFile)) {
12163       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12164       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12165     }
12166
12167     /* rewind to the start of the game */
12168     currentMove = backwardMostMove;
12169
12170     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12171
12172     if (oldGameMode == AnalyzeFile ||
12173         oldGameMode == AnalyzeMode) {
12174       AnalyzeFileEvent();
12175     }
12176
12177     if (!matchMode && pos > 0) {
12178         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12179     } else
12180     if (matchMode || appData.timeDelay == 0) {
12181       ToEndEvent();
12182     } else if (appData.timeDelay > 0) {
12183       AutoPlayGameLoop();
12184     }
12185
12186     if (appData.debugMode)
12187         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12188
12189     loadFlag = 0; /* [HGM] true game starts */
12190     return TRUE;
12191 }
12192
12193 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12194 int
12195 ReloadPosition (int offset)
12196 {
12197     int positionNumber = lastLoadPositionNumber + offset;
12198     if (lastLoadPositionFP == NULL) {
12199         DisplayError(_("No position has been loaded yet"), 0);
12200         return FALSE;
12201     }
12202     if (positionNumber <= 0) {
12203         DisplayError(_("Can't back up any further"), 0);
12204         return FALSE;
12205     }
12206     return LoadPosition(lastLoadPositionFP, positionNumber,
12207                         lastLoadPositionTitle);
12208 }
12209
12210 /* Load the nth position from the given file */
12211 int
12212 LoadPositionFromFile (char *filename, int n, char *title)
12213 {
12214     FILE *f;
12215     char buf[MSG_SIZ];
12216
12217     if (strcmp(filename, "-") == 0) {
12218         return LoadPosition(stdin, n, "stdin");
12219     } else {
12220         f = fopen(filename, "rb");
12221         if (f == NULL) {
12222             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12223             DisplayError(buf, errno);
12224             return FALSE;
12225         } else {
12226             return LoadPosition(f, n, title);
12227         }
12228     }
12229 }
12230
12231 /* Load the nth position from the given open file, and close it */
12232 int
12233 LoadPosition (FILE *f, int positionNumber, char *title)
12234 {
12235     char *p, line[MSG_SIZ];
12236     Board initial_position;
12237     int i, j, fenMode, pn;
12238
12239     if (gameMode == Training )
12240         SetTrainingModeOff();
12241
12242     if (gameMode != BeginningOfGame) {
12243         Reset(FALSE, TRUE);
12244     }
12245     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12246         fclose(lastLoadPositionFP);
12247     }
12248     if (positionNumber == 0) positionNumber = 1;
12249     lastLoadPositionFP = f;
12250     lastLoadPositionNumber = positionNumber;
12251     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12252     if (first.pr == NoProc && !appData.noChessProgram) {
12253       StartChessProgram(&first);
12254       InitChessProgram(&first, FALSE);
12255     }
12256     pn = positionNumber;
12257     if (positionNumber < 0) {
12258         /* Negative position number means to seek to that byte offset */
12259         if (fseek(f, -positionNumber, 0) == -1) {
12260             DisplayError(_("Can't seek on position file"), 0);
12261             return FALSE;
12262         };
12263         pn = 1;
12264     } else {
12265         if (fseek(f, 0, 0) == -1) {
12266             if (f == lastLoadPositionFP ?
12267                 positionNumber == lastLoadPositionNumber + 1 :
12268                 positionNumber == 1) {
12269                 pn = 1;
12270             } else {
12271                 DisplayError(_("Can't seek on position file"), 0);
12272                 return FALSE;
12273             }
12274         }
12275     }
12276     /* See if this file is FEN or old-style xboard */
12277     if (fgets(line, MSG_SIZ, f) == NULL) {
12278         DisplayError(_("Position not found in file"), 0);
12279         return FALSE;
12280     }
12281     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12282     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12283
12284     if (pn >= 2) {
12285         if (fenMode || line[0] == '#') pn--;
12286         while (pn > 0) {
12287             /* skip positions before number pn */
12288             if (fgets(line, MSG_SIZ, f) == NULL) {
12289                 Reset(TRUE, TRUE);
12290                 DisplayError(_("Position not found in file"), 0);
12291                 return FALSE;
12292             }
12293             if (fenMode || line[0] == '#') pn--;
12294         }
12295     }
12296
12297     if (fenMode) {
12298         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12299             DisplayError(_("Bad FEN position in file"), 0);
12300             return FALSE;
12301         }
12302     } else {
12303         (void) fgets(line, MSG_SIZ, f);
12304         (void) fgets(line, MSG_SIZ, f);
12305
12306         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12307             (void) fgets(line, MSG_SIZ, f);
12308             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12309                 if (*p == ' ')
12310                   continue;
12311                 initial_position[i][j++] = CharToPiece(*p);
12312             }
12313         }
12314
12315         blackPlaysFirst = FALSE;
12316         if (!feof(f)) {
12317             (void) fgets(line, MSG_SIZ, f);
12318             if (strncmp(line, "black", strlen("black"))==0)
12319               blackPlaysFirst = TRUE;
12320         }
12321     }
12322     startedFromSetupPosition = TRUE;
12323
12324     CopyBoard(boards[0], initial_position);
12325     if (blackPlaysFirst) {
12326         currentMove = forwardMostMove = backwardMostMove = 1;
12327         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12328         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12329         CopyBoard(boards[1], initial_position);
12330         DisplayMessage("", _("Black to play"));
12331     } else {
12332         currentMove = forwardMostMove = backwardMostMove = 0;
12333         DisplayMessage("", _("White to play"));
12334     }
12335     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12336     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12337         SendToProgram("force\n", &first);
12338         SendBoard(&first, forwardMostMove);
12339     }
12340     if (appData.debugMode) {
12341 int i, j;
12342   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12343   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12344         fprintf(debugFP, "Load Position\n");
12345     }
12346
12347     if (positionNumber > 1) {
12348       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12349         DisplayTitle(line);
12350     } else {
12351         DisplayTitle(title);
12352     }
12353     gameMode = EditGame;
12354     ModeHighlight();
12355     ResetClocks();
12356     timeRemaining[0][1] = whiteTimeRemaining;
12357     timeRemaining[1][1] = blackTimeRemaining;
12358     DrawPosition(FALSE, boards[currentMove]);
12359
12360     return TRUE;
12361 }
12362
12363
12364 void
12365 CopyPlayerNameIntoFileName (char **dest, char *src)
12366 {
12367     while (*src != NULLCHAR && *src != ',') {
12368         if (*src == ' ') {
12369             *(*dest)++ = '_';
12370             src++;
12371         } else {
12372             *(*dest)++ = *src++;
12373         }
12374     }
12375 }
12376
12377 char *
12378 DefaultFileName (char *ext)
12379 {
12380     static char def[MSG_SIZ];
12381     char *p;
12382
12383     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12384         p = def;
12385         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12386         *p++ = '-';
12387         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12388         *p++ = '.';
12389         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12390     } else {
12391         def[0] = NULLCHAR;
12392     }
12393     return def;
12394 }
12395
12396 /* Save the current game to the given file */
12397 int
12398 SaveGameToFile (char *filename, int append)
12399 {
12400     FILE *f;
12401     char buf[MSG_SIZ];
12402     int result, i, t,tot=0;
12403
12404     if (strcmp(filename, "-") == 0) {
12405         return SaveGame(stdout, 0, NULL);
12406     } else {
12407         for(i=0; i<10; i++) { // upto 10 tries
12408              f = fopen(filename, append ? "a" : "w");
12409              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12410              if(f || errno != 13) break;
12411              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12412              tot += t;
12413         }
12414         if (f == NULL) {
12415             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12416             DisplayError(buf, errno);
12417             return FALSE;
12418         } else {
12419             safeStrCpy(buf, lastMsg, MSG_SIZ);
12420             DisplayMessage(_("Waiting for access to save file"), "");
12421             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12422             DisplayMessage(_("Saving game"), "");
12423             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12424             result = SaveGame(f, 0, NULL);
12425             DisplayMessage(buf, "");
12426             return result;
12427         }
12428     }
12429 }
12430
12431 char *
12432 SavePart (char *str)
12433 {
12434     static char buf[MSG_SIZ];
12435     char *p;
12436
12437     p = strchr(str, ' ');
12438     if (p == NULL) return str;
12439     strncpy(buf, str, p - str);
12440     buf[p - str] = NULLCHAR;
12441     return buf;
12442 }
12443
12444 #define PGN_MAX_LINE 75
12445
12446 #define PGN_SIDE_WHITE  0
12447 #define PGN_SIDE_BLACK  1
12448
12449 static int
12450 FindFirstMoveOutOfBook (int side)
12451 {
12452     int result = -1;
12453
12454     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12455         int index = backwardMostMove;
12456         int has_book_hit = 0;
12457
12458         if( (index % 2) != side ) {
12459             index++;
12460         }
12461
12462         while( index < forwardMostMove ) {
12463             /* Check to see if engine is in book */
12464             int depth = pvInfoList[index].depth;
12465             int score = pvInfoList[index].score;
12466             int in_book = 0;
12467
12468             if( depth <= 2 ) {
12469                 in_book = 1;
12470             }
12471             else if( score == 0 && depth == 63 ) {
12472                 in_book = 1; /* Zappa */
12473             }
12474             else if( score == 2 && depth == 99 ) {
12475                 in_book = 1; /* Abrok */
12476             }
12477
12478             has_book_hit += in_book;
12479
12480             if( ! in_book ) {
12481                 result = index;
12482
12483                 break;
12484             }
12485
12486             index += 2;
12487         }
12488     }
12489
12490     return result;
12491 }
12492
12493 void
12494 GetOutOfBookInfo (char * buf)
12495 {
12496     int oob[2];
12497     int i;
12498     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12499
12500     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12501     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12502
12503     *buf = '\0';
12504
12505     if( oob[0] >= 0 || oob[1] >= 0 ) {
12506         for( i=0; i<2; i++ ) {
12507             int idx = oob[i];
12508
12509             if( idx >= 0 ) {
12510                 if( i > 0 && oob[0] >= 0 ) {
12511                     strcat( buf, "   " );
12512                 }
12513
12514                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12515                 sprintf( buf+strlen(buf), "%s%.2f",
12516                     pvInfoList[idx].score >= 0 ? "+" : "",
12517                     pvInfoList[idx].score / 100.0 );
12518             }
12519         }
12520     }
12521 }
12522
12523 /* Save game in PGN style and close the file */
12524 int
12525 SaveGamePGN (FILE *f)
12526 {
12527     int i, offset, linelen, newblock;
12528     time_t tm;
12529 //    char *movetext;
12530     char numtext[32];
12531     int movelen, numlen, blank;
12532     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12533
12534     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12535
12536     tm = time((time_t *) NULL);
12537
12538     PrintPGNTags(f, &gameInfo);
12539
12540     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12541
12542     if (backwardMostMove > 0 || startedFromSetupPosition) {
12543         char *fen = PositionToFEN(backwardMostMove, NULL);
12544         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12545         fprintf(f, "\n{--------------\n");
12546         PrintPosition(f, backwardMostMove);
12547         fprintf(f, "--------------}\n");
12548         free(fen);
12549     }
12550     else {
12551         /* [AS] Out of book annotation */
12552         if( appData.saveOutOfBookInfo ) {
12553             char buf[64];
12554
12555             GetOutOfBookInfo( buf );
12556
12557             if( buf[0] != '\0' ) {
12558                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12559             }
12560         }
12561
12562         fprintf(f, "\n");
12563     }
12564
12565     i = backwardMostMove;
12566     linelen = 0;
12567     newblock = TRUE;
12568
12569     while (i < forwardMostMove) {
12570         /* Print comments preceding this move */
12571         if (commentList[i] != NULL) {
12572             if (linelen > 0) fprintf(f, "\n");
12573             fprintf(f, "%s", commentList[i]);
12574             linelen = 0;
12575             newblock = TRUE;
12576         }
12577
12578         /* Format move number */
12579         if ((i % 2) == 0)
12580           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12581         else
12582           if (newblock)
12583             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12584           else
12585             numtext[0] = NULLCHAR;
12586
12587         numlen = strlen(numtext);
12588         newblock = FALSE;
12589
12590         /* Print move number */
12591         blank = linelen > 0 && numlen > 0;
12592         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12593             fprintf(f, "\n");
12594             linelen = 0;
12595             blank = 0;
12596         }
12597         if (blank) {
12598             fprintf(f, " ");
12599             linelen++;
12600         }
12601         fprintf(f, "%s", numtext);
12602         linelen += numlen;
12603
12604         /* Get move */
12605         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12606         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12607
12608         /* Print move */
12609         blank = linelen > 0 && movelen > 0;
12610         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12611             fprintf(f, "\n");
12612             linelen = 0;
12613             blank = 0;
12614         }
12615         if (blank) {
12616             fprintf(f, " ");
12617             linelen++;
12618         }
12619         fprintf(f, "%s", move_buffer);
12620         linelen += movelen;
12621
12622         /* [AS] Add PV info if present */
12623         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12624             /* [HGM] add time */
12625             char buf[MSG_SIZ]; int seconds;
12626
12627             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12628
12629             if( seconds <= 0)
12630               buf[0] = 0;
12631             else
12632               if( seconds < 30 )
12633                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12634               else
12635                 {
12636                   seconds = (seconds + 4)/10; // round to full seconds
12637                   if( seconds < 60 )
12638                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12639                   else
12640                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12641                 }
12642
12643             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12644                       pvInfoList[i].score >= 0 ? "+" : "",
12645                       pvInfoList[i].score / 100.0,
12646                       pvInfoList[i].depth,
12647                       buf );
12648
12649             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12650
12651             /* Print score/depth */
12652             blank = linelen > 0 && movelen > 0;
12653             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12654                 fprintf(f, "\n");
12655                 linelen = 0;
12656                 blank = 0;
12657             }
12658             if (blank) {
12659                 fprintf(f, " ");
12660                 linelen++;
12661             }
12662             fprintf(f, "%s", move_buffer);
12663             linelen += movelen;
12664         }
12665
12666         i++;
12667     }
12668
12669     /* Start a new line */
12670     if (linelen > 0) fprintf(f, "\n");
12671
12672     /* Print comments after last move */
12673     if (commentList[i] != NULL) {
12674         fprintf(f, "%s\n", commentList[i]);
12675     }
12676
12677     /* Print result */
12678     if (gameInfo.resultDetails != NULL &&
12679         gameInfo.resultDetails[0] != NULLCHAR) {
12680         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12681                 PGNResult(gameInfo.result));
12682     } else {
12683         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12684     }
12685
12686     fclose(f);
12687     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12688     return TRUE;
12689 }
12690
12691 /* Save game in old style and close the file */
12692 int
12693 SaveGameOldStyle (FILE *f)
12694 {
12695     int i, offset;
12696     time_t tm;
12697
12698     tm = time((time_t *) NULL);
12699
12700     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12701     PrintOpponents(f);
12702
12703     if (backwardMostMove > 0 || startedFromSetupPosition) {
12704         fprintf(f, "\n[--------------\n");
12705         PrintPosition(f, backwardMostMove);
12706         fprintf(f, "--------------]\n");
12707     } else {
12708         fprintf(f, "\n");
12709     }
12710
12711     i = backwardMostMove;
12712     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12713
12714     while (i < forwardMostMove) {
12715         if (commentList[i] != NULL) {
12716             fprintf(f, "[%s]\n", commentList[i]);
12717         }
12718
12719         if ((i % 2) == 1) {
12720             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12721             i++;
12722         } else {
12723             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12724             i++;
12725             if (commentList[i] != NULL) {
12726                 fprintf(f, "\n");
12727                 continue;
12728             }
12729             if (i >= forwardMostMove) {
12730                 fprintf(f, "\n");
12731                 break;
12732             }
12733             fprintf(f, "%s\n", parseList[i]);
12734             i++;
12735         }
12736     }
12737
12738     if (commentList[i] != NULL) {
12739         fprintf(f, "[%s]\n", commentList[i]);
12740     }
12741
12742     /* This isn't really the old style, but it's close enough */
12743     if (gameInfo.resultDetails != NULL &&
12744         gameInfo.resultDetails[0] != NULLCHAR) {
12745         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12746                 gameInfo.resultDetails);
12747     } else {
12748         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12749     }
12750
12751     fclose(f);
12752     return TRUE;
12753 }
12754
12755 /* Save the current game to open file f and close the file */
12756 int
12757 SaveGame (FILE *f, int dummy, char *dummy2)
12758 {
12759     if (gameMode == EditPosition) EditPositionDone(TRUE);
12760     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12761     if (appData.oldSaveStyle)
12762       return SaveGameOldStyle(f);
12763     else
12764       return SaveGamePGN(f);
12765 }
12766
12767 /* Save the current position to the given file */
12768 int
12769 SavePositionToFile (char *filename)
12770 {
12771     FILE *f;
12772     char buf[MSG_SIZ];
12773
12774     if (strcmp(filename, "-") == 0) {
12775         return SavePosition(stdout, 0, NULL);
12776     } else {
12777         f = fopen(filename, "a");
12778         if (f == NULL) {
12779             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12780             DisplayError(buf, errno);
12781             return FALSE;
12782         } else {
12783             safeStrCpy(buf, lastMsg, MSG_SIZ);
12784             DisplayMessage(_("Waiting for access to save file"), "");
12785             flock(fileno(f), LOCK_EX); // [HGM] lock
12786             DisplayMessage(_("Saving position"), "");
12787             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12788             SavePosition(f, 0, NULL);
12789             DisplayMessage(buf, "");
12790             return TRUE;
12791         }
12792     }
12793 }
12794
12795 /* Save the current position to the given open file and close the file */
12796 int
12797 SavePosition (FILE *f, int dummy, char *dummy2)
12798 {
12799     time_t tm;
12800     char *fen;
12801
12802     if (gameMode == EditPosition) EditPositionDone(TRUE);
12803     if (appData.oldSaveStyle) {
12804         tm = time((time_t *) NULL);
12805
12806         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12807         PrintOpponents(f);
12808         fprintf(f, "[--------------\n");
12809         PrintPosition(f, currentMove);
12810         fprintf(f, "--------------]\n");
12811     } else {
12812         fen = PositionToFEN(currentMove, NULL);
12813         fprintf(f, "%s\n", fen);
12814         free(fen);
12815     }
12816     fclose(f);
12817     return TRUE;
12818 }
12819
12820 void
12821 ReloadCmailMsgEvent (int unregister)
12822 {
12823 #if !WIN32
12824     static char *inFilename = NULL;
12825     static char *outFilename;
12826     int i;
12827     struct stat inbuf, outbuf;
12828     int status;
12829
12830     /* Any registered moves are unregistered if unregister is set, */
12831     /* i.e. invoked by the signal handler */
12832     if (unregister) {
12833         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12834             cmailMoveRegistered[i] = FALSE;
12835             if (cmailCommentList[i] != NULL) {
12836                 free(cmailCommentList[i]);
12837                 cmailCommentList[i] = NULL;
12838             }
12839         }
12840         nCmailMovesRegistered = 0;
12841     }
12842
12843     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12844         cmailResult[i] = CMAIL_NOT_RESULT;
12845     }
12846     nCmailResults = 0;
12847
12848     if (inFilename == NULL) {
12849         /* Because the filenames are static they only get malloced once  */
12850         /* and they never get freed                                      */
12851         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12852         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12853
12854         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12855         sprintf(outFilename, "%s.out", appData.cmailGameName);
12856     }
12857
12858     status = stat(outFilename, &outbuf);
12859     if (status < 0) {
12860         cmailMailedMove = FALSE;
12861     } else {
12862         status = stat(inFilename, &inbuf);
12863         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12864     }
12865
12866     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12867        counts the games, notes how each one terminated, etc.
12868
12869        It would be nice to remove this kludge and instead gather all
12870        the information while building the game list.  (And to keep it
12871        in the game list nodes instead of having a bunch of fixed-size
12872        parallel arrays.)  Note this will require getting each game's
12873        termination from the PGN tags, as the game list builder does
12874        not process the game moves.  --mann
12875        */
12876     cmailMsgLoaded = TRUE;
12877     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12878
12879     /* Load first game in the file or popup game menu */
12880     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12881
12882 #endif /* !WIN32 */
12883     return;
12884 }
12885
12886 int
12887 RegisterMove ()
12888 {
12889     FILE *f;
12890     char string[MSG_SIZ];
12891
12892     if (   cmailMailedMove
12893         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12894         return TRUE;            /* Allow free viewing  */
12895     }
12896
12897     /* Unregister move to ensure that we don't leave RegisterMove        */
12898     /* with the move registered when the conditions for registering no   */
12899     /* longer hold                                                       */
12900     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12901         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12902         nCmailMovesRegistered --;
12903
12904         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12905           {
12906               free(cmailCommentList[lastLoadGameNumber - 1]);
12907               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12908           }
12909     }
12910
12911     if (cmailOldMove == -1) {
12912         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12913         return FALSE;
12914     }
12915
12916     if (currentMove > cmailOldMove + 1) {
12917         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12918         return FALSE;
12919     }
12920
12921     if (currentMove < cmailOldMove) {
12922         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12923         return FALSE;
12924     }
12925
12926     if (forwardMostMove > currentMove) {
12927         /* Silently truncate extra moves */
12928         TruncateGame();
12929     }
12930
12931     if (   (currentMove == cmailOldMove + 1)
12932         || (   (currentMove == cmailOldMove)
12933             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12934                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12935         if (gameInfo.result != GameUnfinished) {
12936             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12937         }
12938
12939         if (commentList[currentMove] != NULL) {
12940             cmailCommentList[lastLoadGameNumber - 1]
12941               = StrSave(commentList[currentMove]);
12942         }
12943         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12944
12945         if (appData.debugMode)
12946           fprintf(debugFP, "Saving %s for game %d\n",
12947                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12948
12949         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12950
12951         f = fopen(string, "w");
12952         if (appData.oldSaveStyle) {
12953             SaveGameOldStyle(f); /* also closes the file */
12954
12955             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12956             f = fopen(string, "w");
12957             SavePosition(f, 0, NULL); /* also closes the file */
12958         } else {
12959             fprintf(f, "{--------------\n");
12960             PrintPosition(f, currentMove);
12961             fprintf(f, "--------------}\n\n");
12962
12963             SaveGame(f, 0, NULL); /* also closes the file*/
12964         }
12965
12966         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12967         nCmailMovesRegistered ++;
12968     } else if (nCmailGames == 1) {
12969         DisplayError(_("You have not made a move yet"), 0);
12970         return FALSE;
12971     }
12972
12973     return TRUE;
12974 }
12975
12976 void
12977 MailMoveEvent ()
12978 {
12979 #if !WIN32
12980     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12981     FILE *commandOutput;
12982     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12983     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12984     int nBuffers;
12985     int i;
12986     int archived;
12987     char *arcDir;
12988
12989     if (! cmailMsgLoaded) {
12990         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12991         return;
12992     }
12993
12994     if (nCmailGames == nCmailResults) {
12995         DisplayError(_("No unfinished games"), 0);
12996         return;
12997     }
12998
12999 #if CMAIL_PROHIBIT_REMAIL
13000     if (cmailMailedMove) {
13001       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);
13002         DisplayError(msg, 0);
13003         return;
13004     }
13005 #endif
13006
13007     if (! (cmailMailedMove || RegisterMove())) return;
13008
13009     if (   cmailMailedMove
13010         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13011       snprintf(string, MSG_SIZ, partCommandString,
13012                appData.debugMode ? " -v" : "", appData.cmailGameName);
13013         commandOutput = popen(string, "r");
13014
13015         if (commandOutput == NULL) {
13016             DisplayError(_("Failed to invoke cmail"), 0);
13017         } else {
13018             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13019                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13020             }
13021             if (nBuffers > 1) {
13022                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13023                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13024                 nBytes = MSG_SIZ - 1;
13025             } else {
13026                 (void) memcpy(msg, buffer, nBytes);
13027             }
13028             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13029
13030             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13031                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13032
13033                 archived = TRUE;
13034                 for (i = 0; i < nCmailGames; i ++) {
13035                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13036                         archived = FALSE;
13037                     }
13038                 }
13039                 if (   archived
13040                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13041                         != NULL)) {
13042                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13043                            arcDir,
13044                            appData.cmailGameName,
13045                            gameInfo.date);
13046                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13047                     cmailMsgLoaded = FALSE;
13048                 }
13049             }
13050
13051             DisplayInformation(msg);
13052             pclose(commandOutput);
13053         }
13054     } else {
13055         if ((*cmailMsg) != '\0') {
13056             DisplayInformation(cmailMsg);
13057         }
13058     }
13059
13060     return;
13061 #endif /* !WIN32 */
13062 }
13063
13064 char *
13065 CmailMsg ()
13066 {
13067 #if WIN32
13068     return NULL;
13069 #else
13070     int  prependComma = 0;
13071     char number[5];
13072     char string[MSG_SIZ];       /* Space for game-list */
13073     int  i;
13074
13075     if (!cmailMsgLoaded) return "";
13076
13077     if (cmailMailedMove) {
13078       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13079     } else {
13080         /* Create a list of games left */
13081       snprintf(string, MSG_SIZ, "[");
13082         for (i = 0; i < nCmailGames; i ++) {
13083             if (! (   cmailMoveRegistered[i]
13084                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13085                 if (prependComma) {
13086                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13087                 } else {
13088                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13089                     prependComma = 1;
13090                 }
13091
13092                 strcat(string, number);
13093             }
13094         }
13095         strcat(string, "]");
13096
13097         if (nCmailMovesRegistered + nCmailResults == 0) {
13098             switch (nCmailGames) {
13099               case 1:
13100                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13101                 break;
13102
13103               case 2:
13104                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13105                 break;
13106
13107               default:
13108                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13109                          nCmailGames);
13110                 break;
13111             }
13112         } else {
13113             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13114               case 1:
13115                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13116                          string);
13117                 break;
13118
13119               case 0:
13120                 if (nCmailResults == nCmailGames) {
13121                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13122                 } else {
13123                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13124                 }
13125                 break;
13126
13127               default:
13128                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13129                          string);
13130             }
13131         }
13132     }
13133     return cmailMsg;
13134 #endif /* WIN32 */
13135 }
13136
13137 void
13138 ResetGameEvent ()
13139 {
13140     if (gameMode == Training)
13141       SetTrainingModeOff();
13142
13143     Reset(TRUE, TRUE);
13144     cmailMsgLoaded = FALSE;
13145     if (appData.icsActive) {
13146       SendToICS(ics_prefix);
13147       SendToICS("refresh\n");
13148     }
13149 }
13150
13151 void
13152 ExitEvent (int status)
13153 {
13154     exiting++;
13155     if (exiting > 2) {
13156       /* Give up on clean exit */
13157       exit(status);
13158     }
13159     if (exiting > 1) {
13160       /* Keep trying for clean exit */
13161       return;
13162     }
13163
13164     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13165
13166     if (telnetISR != NULL) {
13167       RemoveInputSource(telnetISR);
13168     }
13169     if (icsPR != NoProc) {
13170       DestroyChildProcess(icsPR, TRUE);
13171     }
13172
13173     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13174     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13175
13176     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13177     /* make sure this other one finishes before killing it!                  */
13178     if(endingGame) { int count = 0;
13179         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13180         while(endingGame && count++ < 10) DoSleep(1);
13181         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13182     }
13183
13184     /* Kill off chess programs */
13185     if (first.pr != NoProc) {
13186         ExitAnalyzeMode();
13187
13188         DoSleep( appData.delayBeforeQuit );
13189         SendToProgram("quit\n", &first);
13190         DoSleep( appData.delayAfterQuit );
13191         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13192     }
13193     if (second.pr != NoProc) {
13194         DoSleep( appData.delayBeforeQuit );
13195         SendToProgram("quit\n", &second);
13196         DoSleep( appData.delayAfterQuit );
13197         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13198     }
13199     if (first.isr != NULL) {
13200         RemoveInputSource(first.isr);
13201     }
13202     if (second.isr != NULL) {
13203         RemoveInputSource(second.isr);
13204     }
13205
13206     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13207     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13208
13209     ShutDownFrontEnd();
13210     exit(status);
13211 }
13212
13213 void
13214 PauseEvent ()
13215 {
13216     if (appData.debugMode)
13217         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13218     if (pausing) {
13219         pausing = FALSE;
13220         ModeHighlight();
13221         if (gameMode == MachinePlaysWhite ||
13222             gameMode == MachinePlaysBlack) {
13223             StartClocks();
13224         } else {
13225             DisplayBothClocks();
13226         }
13227         if (gameMode == PlayFromGameFile) {
13228             if (appData.timeDelay >= 0)
13229                 AutoPlayGameLoop();
13230         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13231             Reset(FALSE, TRUE);
13232             SendToICS(ics_prefix);
13233             SendToICS("refresh\n");
13234         } else if (currentMove < forwardMostMove) {
13235             ForwardInner(forwardMostMove);
13236         }
13237         pauseExamInvalid = FALSE;
13238     } else {
13239         switch (gameMode) {
13240           default:
13241             return;
13242           case IcsExamining:
13243             pauseExamForwardMostMove = forwardMostMove;
13244             pauseExamInvalid = FALSE;
13245             /* fall through */
13246           case IcsObserving:
13247           case IcsPlayingWhite:
13248           case IcsPlayingBlack:
13249             pausing = TRUE;
13250             ModeHighlight();
13251             return;
13252           case PlayFromGameFile:
13253             (void) StopLoadGameTimer();
13254             pausing = TRUE;
13255             ModeHighlight();
13256             break;
13257           case BeginningOfGame:
13258             if (appData.icsActive) return;
13259             /* else fall through */
13260           case MachinePlaysWhite:
13261           case MachinePlaysBlack:
13262           case TwoMachinesPlay:
13263             if (forwardMostMove == 0)
13264               return;           /* don't pause if no one has moved */
13265             if ((gameMode == MachinePlaysWhite &&
13266                  !WhiteOnMove(forwardMostMove)) ||
13267                 (gameMode == MachinePlaysBlack &&
13268                  WhiteOnMove(forwardMostMove))) {
13269                 StopClocks();
13270             }
13271             pausing = TRUE;
13272             ModeHighlight();
13273             break;
13274         }
13275     }
13276 }
13277
13278 void
13279 EditCommentEvent ()
13280 {
13281     char title[MSG_SIZ];
13282
13283     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13284       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13285     } else {
13286       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13287                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13288                parseList[currentMove - 1]);
13289     }
13290
13291     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13292 }
13293
13294
13295 void
13296 EditTagsEvent ()
13297 {
13298     char *tags = PGNTags(&gameInfo);
13299     bookUp = FALSE;
13300     EditTagsPopUp(tags, NULL);
13301     free(tags);
13302 }
13303
13304 void
13305 AnalyzeModeEvent ()
13306 {
13307     if (appData.noChessProgram || gameMode == AnalyzeMode)
13308       return;
13309
13310     if (gameMode != AnalyzeFile) {
13311         if (!appData.icsEngineAnalyze) {
13312                EditGameEvent();
13313                if (gameMode != EditGame) return;
13314         }
13315         ResurrectChessProgram();
13316         SendToProgram("analyze\n", &first);
13317         first.analyzing = TRUE;
13318         /*first.maybeThinking = TRUE;*/
13319         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13320         EngineOutputPopUp();
13321     }
13322     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13323     pausing = FALSE;
13324     ModeHighlight();
13325     SetGameInfo();
13326
13327     StartAnalysisClock();
13328     GetTimeMark(&lastNodeCountTime);
13329     lastNodeCount = 0;
13330 }
13331
13332 void
13333 AnalyzeFileEvent ()
13334 {
13335     if (appData.noChessProgram || gameMode == AnalyzeFile)
13336       return;
13337
13338     if (gameMode != AnalyzeMode) {
13339         EditGameEvent();
13340         if (gameMode != EditGame) return;
13341         ResurrectChessProgram();
13342         SendToProgram("analyze\n", &first);
13343         first.analyzing = TRUE;
13344         /*first.maybeThinking = TRUE;*/
13345         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13346         EngineOutputPopUp();
13347     }
13348     gameMode = AnalyzeFile;
13349     pausing = FALSE;
13350     ModeHighlight();
13351     SetGameInfo();
13352
13353     StartAnalysisClock();
13354     GetTimeMark(&lastNodeCountTime);
13355     lastNodeCount = 0;
13356     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13357 }
13358
13359 void
13360 MachineWhiteEvent ()
13361 {
13362     char buf[MSG_SIZ];
13363     char *bookHit = NULL;
13364
13365     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13366       return;
13367
13368
13369     if (gameMode == PlayFromGameFile ||
13370         gameMode == TwoMachinesPlay  ||
13371         gameMode == Training         ||
13372         gameMode == AnalyzeMode      ||
13373         gameMode == EndOfGame)
13374         EditGameEvent();
13375
13376     if (gameMode == EditPosition)
13377         EditPositionDone(TRUE);
13378
13379     if (!WhiteOnMove(currentMove)) {
13380         DisplayError(_("It is not White's turn"), 0);
13381         return;
13382     }
13383
13384     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13385       ExitAnalyzeMode();
13386
13387     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13388         gameMode == AnalyzeFile)
13389         TruncateGame();
13390
13391     ResurrectChessProgram();    /* in case it isn't running */
13392     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13393         gameMode = MachinePlaysWhite;
13394         ResetClocks();
13395     } else
13396     gameMode = MachinePlaysWhite;
13397     pausing = FALSE;
13398     ModeHighlight();
13399     SetGameInfo();
13400     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13401     DisplayTitle(buf);
13402     if (first.sendName) {
13403       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13404       SendToProgram(buf, &first);
13405     }
13406     if (first.sendTime) {
13407       if (first.useColors) {
13408         SendToProgram("black\n", &first); /*gnu kludge*/
13409       }
13410       SendTimeRemaining(&first, TRUE);
13411     }
13412     if (first.useColors) {
13413       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13414     }
13415     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13416     SetMachineThinkingEnables();
13417     first.maybeThinking = TRUE;
13418     StartClocks();
13419     firstMove = FALSE;
13420
13421     if (appData.autoFlipView && !flipView) {
13422       flipView = !flipView;
13423       DrawPosition(FALSE, NULL);
13424       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13425     }
13426
13427     if(bookHit) { // [HGM] book: simulate book reply
13428         static char bookMove[MSG_SIZ]; // a bit generous?
13429
13430         programStats.nodes = programStats.depth = programStats.time =
13431         programStats.score = programStats.got_only_move = 0;
13432         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13433
13434         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13435         strcat(bookMove, bookHit);
13436         HandleMachineMove(bookMove, &first);
13437     }
13438 }
13439
13440 void
13441 MachineBlackEvent ()
13442 {
13443   char buf[MSG_SIZ];
13444   char *bookHit = NULL;
13445
13446     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13447         return;
13448
13449
13450     if (gameMode == PlayFromGameFile ||
13451         gameMode == TwoMachinesPlay  ||
13452         gameMode == Training         ||
13453         gameMode == AnalyzeMode      ||
13454         gameMode == EndOfGame)
13455         EditGameEvent();
13456
13457     if (gameMode == EditPosition)
13458         EditPositionDone(TRUE);
13459
13460     if (WhiteOnMove(currentMove)) {
13461         DisplayError(_("It is not Black's turn"), 0);
13462         return;
13463     }
13464
13465     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13466       ExitAnalyzeMode();
13467
13468     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13469         gameMode == AnalyzeFile)
13470         TruncateGame();
13471
13472     ResurrectChessProgram();    /* in case it isn't running */
13473     gameMode = MachinePlaysBlack;
13474     pausing = FALSE;
13475     ModeHighlight();
13476     SetGameInfo();
13477     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13478     DisplayTitle(buf);
13479     if (first.sendName) {
13480       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13481       SendToProgram(buf, &first);
13482     }
13483     if (first.sendTime) {
13484       if (first.useColors) {
13485         SendToProgram("white\n", &first); /*gnu kludge*/
13486       }
13487       SendTimeRemaining(&first, FALSE);
13488     }
13489     if (first.useColors) {
13490       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13491     }
13492     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13493     SetMachineThinkingEnables();
13494     first.maybeThinking = TRUE;
13495     StartClocks();
13496
13497     if (appData.autoFlipView && flipView) {
13498       flipView = !flipView;
13499       DrawPosition(FALSE, NULL);
13500       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13501     }
13502     if(bookHit) { // [HGM] book: simulate book reply
13503         static char bookMove[MSG_SIZ]; // a bit generous?
13504
13505         programStats.nodes = programStats.depth = programStats.time =
13506         programStats.score = programStats.got_only_move = 0;
13507         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13508
13509         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13510         strcat(bookMove, bookHit);
13511         HandleMachineMove(bookMove, &first);
13512     }
13513 }
13514
13515
13516 void
13517 DisplayTwoMachinesTitle ()
13518 {
13519     char buf[MSG_SIZ];
13520     if (appData.matchGames > 0) {
13521         if(appData.tourneyFile[0]) {
13522           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13523                    gameInfo.white, _("vs."), gameInfo.black,
13524                    nextGame+1, appData.matchGames+1,
13525                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13526         } else 
13527         if (first.twoMachinesColor[0] == 'w') {
13528           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13529                    gameInfo.white, _("vs."),  gameInfo.black,
13530                    first.matchWins, second.matchWins,
13531                    matchGame - 1 - (first.matchWins + second.matchWins));
13532         } else {
13533           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13534                    gameInfo.white, _("vs."), gameInfo.black,
13535                    second.matchWins, first.matchWins,
13536                    matchGame - 1 - (first.matchWins + second.matchWins));
13537         }
13538     } else {
13539       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13540     }
13541     DisplayTitle(buf);
13542 }
13543
13544 void
13545 SettingsMenuIfReady ()
13546 {
13547   if (second.lastPing != second.lastPong) {
13548     DisplayMessage("", _("Waiting for second chess program"));
13549     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13550     return;
13551   }
13552   ThawUI();
13553   DisplayMessage("", "");
13554   SettingsPopUp(&second);
13555 }
13556
13557 int
13558 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13559 {
13560     char buf[MSG_SIZ];
13561     if (cps->pr == NoProc) {
13562         StartChessProgram(cps);
13563         if (cps->protocolVersion == 1) {
13564           retry();
13565         } else {
13566           /* kludge: allow timeout for initial "feature" command */
13567           FreezeUI();
13568           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13569           DisplayMessage("", buf);
13570           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13571         }
13572         return 1;
13573     }
13574     return 0;
13575 }
13576
13577 void
13578 TwoMachinesEvent P((void))
13579 {
13580     int i;
13581     char buf[MSG_SIZ];
13582     ChessProgramState *onmove;
13583     char *bookHit = NULL;
13584     static int stalling = 0;
13585     TimeMark now;
13586     long wait;
13587
13588     if (appData.noChessProgram) return;
13589
13590     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13591         DisplayError("second engine does not play this", 0);
13592         return;
13593     }
13594
13595     switch (gameMode) {
13596       case TwoMachinesPlay:
13597         return;
13598       case MachinePlaysWhite:
13599       case MachinePlaysBlack:
13600         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13601             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13602             return;
13603         }
13604         /* fall through */
13605       case BeginningOfGame:
13606       case PlayFromGameFile:
13607       case EndOfGame:
13608         EditGameEvent();
13609         if (gameMode != EditGame) return;
13610         break;
13611       case EditPosition:
13612         EditPositionDone(TRUE);
13613         break;
13614       case AnalyzeMode:
13615       case AnalyzeFile:
13616         ExitAnalyzeMode();
13617         break;
13618       case EditGame:
13619       default:
13620         break;
13621     }
13622
13623 //    forwardMostMove = currentMove;
13624     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13625
13626     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13627
13628     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13629     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13630       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13631       return;
13632     }
13633     if(!stalling) {
13634       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13635       SendToProgram("force\n", &second);
13636       stalling = 1;
13637       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13638       return;
13639     }
13640     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13641     if(appData.matchPause>10000 || appData.matchPause<10)
13642                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13643     wait = SubtractTimeMarks(&now, &pauseStart);
13644     if(wait < appData.matchPause) {
13645         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13646         return;
13647     }
13648     // we are now committed to starting the game
13649     stalling = 0;
13650     DisplayMessage("", "");
13651     if (startedFromSetupPosition) {
13652         SendBoard(&second, backwardMostMove);
13653     if (appData.debugMode) {
13654         fprintf(debugFP, "Two Machines\n");
13655     }
13656     }
13657     for (i = backwardMostMove; i < forwardMostMove; i++) {
13658         SendMoveToProgram(i, &second);
13659     }
13660
13661     gameMode = TwoMachinesPlay;
13662     pausing = FALSE;
13663     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13664     SetGameInfo();
13665     DisplayTwoMachinesTitle();
13666     firstMove = TRUE;
13667     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13668         onmove = &first;
13669     } else {
13670         onmove = &second;
13671     }
13672     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13673     SendToProgram(first.computerString, &first);
13674     if (first.sendName) {
13675       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13676       SendToProgram(buf, &first);
13677     }
13678     SendToProgram(second.computerString, &second);
13679     if (second.sendName) {
13680       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13681       SendToProgram(buf, &second);
13682     }
13683
13684     ResetClocks();
13685     if (!first.sendTime || !second.sendTime) {
13686         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13687         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13688     }
13689     if (onmove->sendTime) {
13690       if (onmove->useColors) {
13691         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13692       }
13693       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13694     }
13695     if (onmove->useColors) {
13696       SendToProgram(onmove->twoMachinesColor, onmove);
13697     }
13698     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13699 //    SendToProgram("go\n", onmove);
13700     onmove->maybeThinking = TRUE;
13701     SetMachineThinkingEnables();
13702
13703     StartClocks();
13704
13705     if(bookHit) { // [HGM] book: simulate book reply
13706         static char bookMove[MSG_SIZ]; // a bit generous?
13707
13708         programStats.nodes = programStats.depth = programStats.time =
13709         programStats.score = programStats.got_only_move = 0;
13710         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13711
13712         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13713         strcat(bookMove, bookHit);
13714         savedMessage = bookMove; // args for deferred call
13715         savedState = onmove;
13716         ScheduleDelayedEvent(DeferredBookMove, 1);
13717     }
13718 }
13719
13720 void
13721 TrainingEvent ()
13722 {
13723     if (gameMode == Training) {
13724       SetTrainingModeOff();
13725       gameMode = PlayFromGameFile;
13726       DisplayMessage("", _("Training mode off"));
13727     } else {
13728       gameMode = Training;
13729       animateTraining = appData.animate;
13730
13731       /* make sure we are not already at the end of the game */
13732       if (currentMove < forwardMostMove) {
13733         SetTrainingModeOn();
13734         DisplayMessage("", _("Training mode on"));
13735       } else {
13736         gameMode = PlayFromGameFile;
13737         DisplayError(_("Already at end of game"), 0);
13738       }
13739     }
13740     ModeHighlight();
13741 }
13742
13743 void
13744 IcsClientEvent ()
13745 {
13746     if (!appData.icsActive) return;
13747     switch (gameMode) {
13748       case IcsPlayingWhite:
13749       case IcsPlayingBlack:
13750       case IcsObserving:
13751       case IcsIdle:
13752       case BeginningOfGame:
13753       case IcsExamining:
13754         return;
13755
13756       case EditGame:
13757         break;
13758
13759       case EditPosition:
13760         EditPositionDone(TRUE);
13761         break;
13762
13763       case AnalyzeMode:
13764       case AnalyzeFile:
13765         ExitAnalyzeMode();
13766         break;
13767
13768       default:
13769         EditGameEvent();
13770         break;
13771     }
13772
13773     gameMode = IcsIdle;
13774     ModeHighlight();
13775     return;
13776 }
13777
13778 void
13779 EditGameEvent ()
13780 {
13781     int i;
13782
13783     switch (gameMode) {
13784       case Training:
13785         SetTrainingModeOff();
13786         break;
13787       case MachinePlaysWhite:
13788       case MachinePlaysBlack:
13789       case BeginningOfGame:
13790         SendToProgram("force\n", &first);
13791         SetUserThinkingEnables();
13792         break;
13793       case PlayFromGameFile:
13794         (void) StopLoadGameTimer();
13795         if (gameFileFP != NULL) {
13796             gameFileFP = NULL;
13797         }
13798         break;
13799       case EditPosition:
13800         EditPositionDone(TRUE);
13801         break;
13802       case AnalyzeMode:
13803       case AnalyzeFile:
13804         ExitAnalyzeMode();
13805         SendToProgram("force\n", &first);
13806         break;
13807       case TwoMachinesPlay:
13808         GameEnds(EndOfFile, NULL, GE_PLAYER);
13809         ResurrectChessProgram();
13810         SetUserThinkingEnables();
13811         break;
13812       case EndOfGame:
13813         ResurrectChessProgram();
13814         break;
13815       case IcsPlayingBlack:
13816       case IcsPlayingWhite:
13817         DisplayError(_("Warning: You are still playing a game"), 0);
13818         break;
13819       case IcsObserving:
13820         DisplayError(_("Warning: You are still observing a game"), 0);
13821         break;
13822       case IcsExamining:
13823         DisplayError(_("Warning: You are still examining a game"), 0);
13824         break;
13825       case IcsIdle:
13826         break;
13827       case EditGame:
13828       default:
13829         return;
13830     }
13831
13832     pausing = FALSE;
13833     StopClocks();
13834     first.offeredDraw = second.offeredDraw = 0;
13835
13836     if (gameMode == PlayFromGameFile) {
13837         whiteTimeRemaining = timeRemaining[0][currentMove];
13838         blackTimeRemaining = timeRemaining[1][currentMove];
13839         DisplayTitle("");
13840     }
13841
13842     if (gameMode == MachinePlaysWhite ||
13843         gameMode == MachinePlaysBlack ||
13844         gameMode == TwoMachinesPlay ||
13845         gameMode == EndOfGame) {
13846         i = forwardMostMove;
13847         while (i > currentMove) {
13848             SendToProgram("undo\n", &first);
13849             i--;
13850         }
13851         if(!adjustedClock) {
13852         whiteTimeRemaining = timeRemaining[0][currentMove];
13853         blackTimeRemaining = timeRemaining[1][currentMove];
13854         DisplayBothClocks();
13855         }
13856         if (whiteFlag || blackFlag) {
13857             whiteFlag = blackFlag = 0;
13858         }
13859         DisplayTitle("");
13860     }
13861
13862     gameMode = EditGame;
13863     ModeHighlight();
13864     SetGameInfo();
13865 }
13866
13867
13868 void
13869 EditPositionEvent ()
13870 {
13871     if (gameMode == EditPosition) {
13872         EditGameEvent();
13873         return;
13874     }
13875
13876     EditGameEvent();
13877     if (gameMode != EditGame) return;
13878
13879     gameMode = EditPosition;
13880     ModeHighlight();
13881     SetGameInfo();
13882     if (currentMove > 0)
13883       CopyBoard(boards[0], boards[currentMove]);
13884
13885     blackPlaysFirst = !WhiteOnMove(currentMove);
13886     ResetClocks();
13887     currentMove = forwardMostMove = backwardMostMove = 0;
13888     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13889     DisplayMove(-1);
13890     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13891 }
13892
13893 void
13894 ExitAnalyzeMode ()
13895 {
13896     /* [DM] icsEngineAnalyze - possible call from other functions */
13897     if (appData.icsEngineAnalyze) {
13898         appData.icsEngineAnalyze = FALSE;
13899
13900         DisplayMessage("",_("Close ICS engine analyze..."));
13901     }
13902     if (first.analysisSupport && first.analyzing) {
13903       SendToProgram("exit\n", &first);
13904       first.analyzing = FALSE;
13905     }
13906     thinkOutput[0] = NULLCHAR;
13907 }
13908
13909 void
13910 EditPositionDone (Boolean fakeRights)
13911 {
13912     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13913
13914     startedFromSetupPosition = TRUE;
13915     InitChessProgram(&first, FALSE);
13916     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13917       boards[0][EP_STATUS] = EP_NONE;
13918       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13919     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13920         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13921         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13922       } else boards[0][CASTLING][2] = NoRights;
13923     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13924         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13925         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13926       } else boards[0][CASTLING][5] = NoRights;
13927     }
13928     SendToProgram("force\n", &first);
13929     if (blackPlaysFirst) {
13930         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13931         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13932         currentMove = forwardMostMove = backwardMostMove = 1;
13933         CopyBoard(boards[1], boards[0]);
13934     } else {
13935         currentMove = forwardMostMove = backwardMostMove = 0;
13936     }
13937     SendBoard(&first, forwardMostMove);
13938     if (appData.debugMode) {
13939         fprintf(debugFP, "EditPosDone\n");
13940     }
13941     DisplayTitle("");
13942     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13943     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13944     gameMode = EditGame;
13945     ModeHighlight();
13946     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13947     ClearHighlights(); /* [AS] */
13948 }
13949
13950 /* Pause for `ms' milliseconds */
13951 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13952 void
13953 TimeDelay (long ms)
13954 {
13955     TimeMark m1, m2;
13956
13957     GetTimeMark(&m1);
13958     do {
13959         GetTimeMark(&m2);
13960     } while (SubtractTimeMarks(&m2, &m1) < ms);
13961 }
13962
13963 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13964 void
13965 SendMultiLineToICS (char *buf)
13966 {
13967     char temp[MSG_SIZ+1], *p;
13968     int len;
13969
13970     len = strlen(buf);
13971     if (len > MSG_SIZ)
13972       len = MSG_SIZ;
13973
13974     strncpy(temp, buf, len);
13975     temp[len] = 0;
13976
13977     p = temp;
13978     while (*p) {
13979         if (*p == '\n' || *p == '\r')
13980           *p = ' ';
13981         ++p;
13982     }
13983
13984     strcat(temp, "\n");
13985     SendToICS(temp);
13986     SendToPlayer(temp, strlen(temp));
13987 }
13988
13989 void
13990 SetWhiteToPlayEvent ()
13991 {
13992     if (gameMode == EditPosition) {
13993         blackPlaysFirst = FALSE;
13994         DisplayBothClocks();    /* works because currentMove is 0 */
13995     } else if (gameMode == IcsExamining) {
13996         SendToICS(ics_prefix);
13997         SendToICS("tomove white\n");
13998     }
13999 }
14000
14001 void
14002 SetBlackToPlayEvent ()
14003 {
14004     if (gameMode == EditPosition) {
14005         blackPlaysFirst = TRUE;
14006         currentMove = 1;        /* kludge */
14007         DisplayBothClocks();
14008         currentMove = 0;
14009     } else if (gameMode == IcsExamining) {
14010         SendToICS(ics_prefix);
14011         SendToICS("tomove black\n");
14012     }
14013 }
14014
14015 void
14016 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14017 {
14018     char buf[MSG_SIZ];
14019     ChessSquare piece = boards[0][y][x];
14020
14021     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14022
14023     switch (selection) {
14024       case ClearBoard:
14025         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14026             SendToICS(ics_prefix);
14027             SendToICS("bsetup clear\n");
14028         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14029             SendToICS(ics_prefix);
14030             SendToICS("clearboard\n");
14031         } else {
14032             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14033                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14034                 for (y = 0; y < BOARD_HEIGHT; y++) {
14035                     if (gameMode == IcsExamining) {
14036                         if (boards[currentMove][y][x] != EmptySquare) {
14037                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14038                                     AAA + x, ONE + y);
14039                             SendToICS(buf);
14040                         }
14041                     } else {
14042                         boards[0][y][x] = p;
14043                     }
14044                 }
14045             }
14046         }
14047         if (gameMode == EditPosition) {
14048             DrawPosition(FALSE, boards[0]);
14049         }
14050         break;
14051
14052       case WhitePlay:
14053         SetWhiteToPlayEvent();
14054         break;
14055
14056       case BlackPlay:
14057         SetBlackToPlayEvent();
14058         break;
14059
14060       case EmptySquare:
14061         if (gameMode == IcsExamining) {
14062             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14063             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14064             SendToICS(buf);
14065         } else {
14066             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14067                 if(x == BOARD_LEFT-2) {
14068                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14069                     boards[0][y][1] = 0;
14070                 } else
14071                 if(x == BOARD_RGHT+1) {
14072                     if(y >= gameInfo.holdingsSize) break;
14073                     boards[0][y][BOARD_WIDTH-2] = 0;
14074                 } else break;
14075             }
14076             boards[0][y][x] = EmptySquare;
14077             DrawPosition(FALSE, boards[0]);
14078         }
14079         break;
14080
14081       case PromotePiece:
14082         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14083            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14084             selection = (ChessSquare) (PROMOTED piece);
14085         } else if(piece == EmptySquare) selection = WhiteSilver;
14086         else selection = (ChessSquare)((int)piece - 1);
14087         goto defaultlabel;
14088
14089       case DemotePiece:
14090         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14091            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14092             selection = (ChessSquare) (DEMOTED piece);
14093         } else if(piece == EmptySquare) selection = BlackSilver;
14094         else selection = (ChessSquare)((int)piece + 1);
14095         goto defaultlabel;
14096
14097       case WhiteQueen:
14098       case BlackQueen:
14099         if(gameInfo.variant == VariantShatranj ||
14100            gameInfo.variant == VariantXiangqi  ||
14101            gameInfo.variant == VariantCourier  ||
14102            gameInfo.variant == VariantMakruk     )
14103             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14104         goto defaultlabel;
14105
14106       case WhiteKing:
14107       case BlackKing:
14108         if(gameInfo.variant == VariantXiangqi)
14109             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14110         if(gameInfo.variant == VariantKnightmate)
14111             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14112       default:
14113         defaultlabel:
14114         if (gameMode == IcsExamining) {
14115             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14116             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14117                      PieceToChar(selection), AAA + x, ONE + y);
14118             SendToICS(buf);
14119         } else {
14120             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14121                 int n;
14122                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14123                     n = PieceToNumber(selection - BlackPawn);
14124                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14125                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14126                     boards[0][BOARD_HEIGHT-1-n][1]++;
14127                 } else
14128                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14129                     n = PieceToNumber(selection);
14130                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14131                     boards[0][n][BOARD_WIDTH-1] = selection;
14132                     boards[0][n][BOARD_WIDTH-2]++;
14133                 }
14134             } else
14135             boards[0][y][x] = selection;
14136             DrawPosition(TRUE, boards[0]);
14137             ClearHighlights();
14138             fromX = fromY = -1;
14139         }
14140         break;
14141     }
14142 }
14143
14144
14145 void
14146 DropMenuEvent (ChessSquare selection, int x, int y)
14147 {
14148     ChessMove moveType;
14149
14150     switch (gameMode) {
14151       case IcsPlayingWhite:
14152       case MachinePlaysBlack:
14153         if (!WhiteOnMove(currentMove)) {
14154             DisplayMoveError(_("It is Black's turn"));
14155             return;
14156         }
14157         moveType = WhiteDrop;
14158         break;
14159       case IcsPlayingBlack:
14160       case MachinePlaysWhite:
14161         if (WhiteOnMove(currentMove)) {
14162             DisplayMoveError(_("It is White's turn"));
14163             return;
14164         }
14165         moveType = BlackDrop;
14166         break;
14167       case EditGame:
14168         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14169         break;
14170       default:
14171         return;
14172     }
14173
14174     if (moveType == BlackDrop && selection < BlackPawn) {
14175       selection = (ChessSquare) ((int) selection
14176                                  + (int) BlackPawn - (int) WhitePawn);
14177     }
14178     if (boards[currentMove][y][x] != EmptySquare) {
14179         DisplayMoveError(_("That square is occupied"));
14180         return;
14181     }
14182
14183     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14184 }
14185
14186 void
14187 AcceptEvent ()
14188 {
14189     /* Accept a pending offer of any kind from opponent */
14190
14191     if (appData.icsActive) {
14192         SendToICS(ics_prefix);
14193         SendToICS("accept\n");
14194     } else if (cmailMsgLoaded) {
14195         if (currentMove == cmailOldMove &&
14196             commentList[cmailOldMove] != NULL &&
14197             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14198                    "Black offers a draw" : "White offers a draw")) {
14199             TruncateGame();
14200             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14201             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14202         } else {
14203             DisplayError(_("There is no pending offer on this move"), 0);
14204             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14205         }
14206     } else {
14207         /* Not used for offers from chess program */
14208     }
14209 }
14210
14211 void
14212 DeclineEvent ()
14213 {
14214     /* Decline a pending offer of any kind from opponent */
14215
14216     if (appData.icsActive) {
14217         SendToICS(ics_prefix);
14218         SendToICS("decline\n");
14219     } else if (cmailMsgLoaded) {
14220         if (currentMove == cmailOldMove &&
14221             commentList[cmailOldMove] != NULL &&
14222             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14223                    "Black offers a draw" : "White offers a draw")) {
14224 #ifdef NOTDEF
14225             AppendComment(cmailOldMove, "Draw declined", TRUE);
14226             DisplayComment(cmailOldMove - 1, "Draw declined");
14227 #endif /*NOTDEF*/
14228         } else {
14229             DisplayError(_("There is no pending offer on this move"), 0);
14230         }
14231     } else {
14232         /* Not used for offers from chess program */
14233     }
14234 }
14235
14236 void
14237 RematchEvent ()
14238 {
14239     /* Issue ICS rematch command */
14240     if (appData.icsActive) {
14241         SendToICS(ics_prefix);
14242         SendToICS("rematch\n");
14243     }
14244 }
14245
14246 void
14247 CallFlagEvent ()
14248 {
14249     /* Call your opponent's flag (claim a win on time) */
14250     if (appData.icsActive) {
14251         SendToICS(ics_prefix);
14252         SendToICS("flag\n");
14253     } else {
14254         switch (gameMode) {
14255           default:
14256             return;
14257           case MachinePlaysWhite:
14258             if (whiteFlag) {
14259                 if (blackFlag)
14260                   GameEnds(GameIsDrawn, "Both players ran out of time",
14261                            GE_PLAYER);
14262                 else
14263                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14264             } else {
14265                 DisplayError(_("Your opponent is not out of time"), 0);
14266             }
14267             break;
14268           case MachinePlaysBlack:
14269             if (blackFlag) {
14270                 if (whiteFlag)
14271                   GameEnds(GameIsDrawn, "Both players ran out of time",
14272                            GE_PLAYER);
14273                 else
14274                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14275             } else {
14276                 DisplayError(_("Your opponent is not out of time"), 0);
14277             }
14278             break;
14279         }
14280     }
14281 }
14282
14283 void
14284 ClockClick (int which)
14285 {       // [HGM] code moved to back-end from winboard.c
14286         if(which) { // black clock
14287           if (gameMode == EditPosition || gameMode == IcsExamining) {
14288             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14289             SetBlackToPlayEvent();
14290           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14291           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14292           } else if (shiftKey) {
14293             AdjustClock(which, -1);
14294           } else if (gameMode == IcsPlayingWhite ||
14295                      gameMode == MachinePlaysBlack) {
14296             CallFlagEvent();
14297           }
14298         } else { // white clock
14299           if (gameMode == EditPosition || gameMode == IcsExamining) {
14300             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14301             SetWhiteToPlayEvent();
14302           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14303           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14304           } else if (shiftKey) {
14305             AdjustClock(which, -1);
14306           } else if (gameMode == IcsPlayingBlack ||
14307                    gameMode == MachinePlaysWhite) {
14308             CallFlagEvent();
14309           }
14310         }
14311 }
14312
14313 void
14314 DrawEvent ()
14315 {
14316     /* Offer draw or accept pending draw offer from opponent */
14317
14318     if (appData.icsActive) {
14319         /* Note: tournament rules require draw offers to be
14320            made after you make your move but before you punch
14321            your clock.  Currently ICS doesn't let you do that;
14322            instead, you immediately punch your clock after making
14323            a move, but you can offer a draw at any time. */
14324
14325         SendToICS(ics_prefix);
14326         SendToICS("draw\n");
14327         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14328     } else if (cmailMsgLoaded) {
14329         if (currentMove == cmailOldMove &&
14330             commentList[cmailOldMove] != NULL &&
14331             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14332                    "Black offers a draw" : "White offers a draw")) {
14333             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14334             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14335         } else if (currentMove == cmailOldMove + 1) {
14336             char *offer = WhiteOnMove(cmailOldMove) ?
14337               "White offers a draw" : "Black offers a draw";
14338             AppendComment(currentMove, offer, TRUE);
14339             DisplayComment(currentMove - 1, offer);
14340             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14341         } else {
14342             DisplayError(_("You must make your move before offering a draw"), 0);
14343             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14344         }
14345     } else if (first.offeredDraw) {
14346         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14347     } else {
14348         if (first.sendDrawOffers) {
14349             SendToProgram("draw\n", &first);
14350             userOfferedDraw = TRUE;
14351         }
14352     }
14353 }
14354
14355 void
14356 AdjournEvent ()
14357 {
14358     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14359
14360     if (appData.icsActive) {
14361         SendToICS(ics_prefix);
14362         SendToICS("adjourn\n");
14363     } else {
14364         /* Currently GNU Chess doesn't offer or accept Adjourns */
14365     }
14366 }
14367
14368
14369 void
14370 AbortEvent ()
14371 {
14372     /* Offer Abort or accept pending Abort offer from opponent */
14373
14374     if (appData.icsActive) {
14375         SendToICS(ics_prefix);
14376         SendToICS("abort\n");
14377     } else {
14378         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14379     }
14380 }
14381
14382 void
14383 ResignEvent ()
14384 {
14385     /* Resign.  You can do this even if it's not your turn. */
14386
14387     if (appData.icsActive) {
14388         SendToICS(ics_prefix);
14389         SendToICS("resign\n");
14390     } else {
14391         switch (gameMode) {
14392           case MachinePlaysWhite:
14393             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14394             break;
14395           case MachinePlaysBlack:
14396             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14397             break;
14398           case EditGame:
14399             if (cmailMsgLoaded) {
14400                 TruncateGame();
14401                 if (WhiteOnMove(cmailOldMove)) {
14402                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14403                 } else {
14404                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14405                 }
14406                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14407             }
14408             break;
14409           default:
14410             break;
14411         }
14412     }
14413 }
14414
14415
14416 void
14417 StopObservingEvent ()
14418 {
14419     /* Stop observing current games */
14420     SendToICS(ics_prefix);
14421     SendToICS("unobserve\n");
14422 }
14423
14424 void
14425 StopExaminingEvent ()
14426 {
14427     /* Stop observing current game */
14428     SendToICS(ics_prefix);
14429     SendToICS("unexamine\n");
14430 }
14431
14432 void
14433 ForwardInner (int target)
14434 {
14435     int limit; int oldSeekGraphUp = seekGraphUp;
14436
14437     if (appData.debugMode)
14438         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14439                 target, currentMove, forwardMostMove);
14440
14441     if (gameMode == EditPosition)
14442       return;
14443
14444     seekGraphUp = FALSE;
14445     MarkTargetSquares(1);
14446
14447     if (gameMode == PlayFromGameFile && !pausing)
14448       PauseEvent();
14449
14450     if (gameMode == IcsExamining && pausing)
14451       limit = pauseExamForwardMostMove;
14452     else
14453       limit = forwardMostMove;
14454
14455     if (target > limit) target = limit;
14456
14457     if (target > 0 && moveList[target - 1][0]) {
14458         int fromX, fromY, toX, toY;
14459         toX = moveList[target - 1][2] - AAA;
14460         toY = moveList[target - 1][3] - ONE;
14461         if (moveList[target - 1][1] == '@') {
14462             if (appData.highlightLastMove) {
14463                 SetHighlights(-1, -1, toX, toY);
14464             }
14465         } else {
14466             fromX = moveList[target - 1][0] - AAA;
14467             fromY = moveList[target - 1][1] - ONE;
14468             if (target == currentMove + 1) {
14469                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14470             }
14471             if (appData.highlightLastMove) {
14472                 SetHighlights(fromX, fromY, toX, toY);
14473             }
14474         }
14475     }
14476     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14477         gameMode == Training || gameMode == PlayFromGameFile ||
14478         gameMode == AnalyzeFile) {
14479         while (currentMove < target) {
14480             SendMoveToProgram(currentMove++, &first);
14481         }
14482     } else {
14483         currentMove = target;
14484     }
14485
14486     if (gameMode == EditGame || gameMode == EndOfGame) {
14487         whiteTimeRemaining = timeRemaining[0][currentMove];
14488         blackTimeRemaining = timeRemaining[1][currentMove];
14489     }
14490     DisplayBothClocks();
14491     DisplayMove(currentMove - 1);
14492     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14493     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14494     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14495         DisplayComment(currentMove - 1, commentList[currentMove]);
14496     }
14497     ClearMap(); // [HGM] exclude: invalidate map
14498 }
14499
14500
14501 void
14502 ForwardEvent ()
14503 {
14504     if (gameMode == IcsExamining && !pausing) {
14505         SendToICS(ics_prefix);
14506         SendToICS("forward\n");
14507     } else {
14508         ForwardInner(currentMove + 1);
14509     }
14510 }
14511
14512 void
14513 ToEndEvent ()
14514 {
14515     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14516         /* to optimze, we temporarily turn off analysis mode while we feed
14517          * the remaining moves to the engine. Otherwise we get analysis output
14518          * after each move.
14519          */
14520         if (first.analysisSupport) {
14521           SendToProgram("exit\nforce\n", &first);
14522           first.analyzing = FALSE;
14523         }
14524     }
14525
14526     if (gameMode == IcsExamining && !pausing) {
14527         SendToICS(ics_prefix);
14528         SendToICS("forward 999999\n");
14529     } else {
14530         ForwardInner(forwardMostMove);
14531     }
14532
14533     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14534         /* we have fed all the moves, so reactivate analysis mode */
14535         SendToProgram("analyze\n", &first);
14536         first.analyzing = TRUE;
14537         /*first.maybeThinking = TRUE;*/
14538         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14539     }
14540 }
14541
14542 void
14543 BackwardInner (int target)
14544 {
14545     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14546
14547     if (appData.debugMode)
14548         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14549                 target, currentMove, forwardMostMove);
14550
14551     if (gameMode == EditPosition) return;
14552     seekGraphUp = FALSE;
14553     MarkTargetSquares(1);
14554     if (currentMove <= backwardMostMove) {
14555         ClearHighlights();
14556         DrawPosition(full_redraw, boards[currentMove]);
14557         return;
14558     }
14559     if (gameMode == PlayFromGameFile && !pausing)
14560       PauseEvent();
14561
14562     if (moveList[target][0]) {
14563         int fromX, fromY, toX, toY;
14564         toX = moveList[target][2] - AAA;
14565         toY = moveList[target][3] - ONE;
14566         if (moveList[target][1] == '@') {
14567             if (appData.highlightLastMove) {
14568                 SetHighlights(-1, -1, toX, toY);
14569             }
14570         } else {
14571             fromX = moveList[target][0] - AAA;
14572             fromY = moveList[target][1] - ONE;
14573             if (target == currentMove - 1) {
14574                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14575             }
14576             if (appData.highlightLastMove) {
14577                 SetHighlights(fromX, fromY, toX, toY);
14578             }
14579         }
14580     }
14581     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14582         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14583         while (currentMove > target) {
14584             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14585                 // null move cannot be undone. Reload program with move history before it.
14586                 int i;
14587                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14588                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14589                 }
14590                 SendBoard(&first, i); 
14591                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14592                 break;
14593             }
14594             SendToProgram("undo\n", &first);
14595             currentMove--;
14596         }
14597     } else {
14598         currentMove = target;
14599     }
14600
14601     if (gameMode == EditGame || gameMode == EndOfGame) {
14602         whiteTimeRemaining = timeRemaining[0][currentMove];
14603         blackTimeRemaining = timeRemaining[1][currentMove];
14604     }
14605     DisplayBothClocks();
14606     DisplayMove(currentMove - 1);
14607     DrawPosition(full_redraw, boards[currentMove]);
14608     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14609     // [HGM] PV info: routine tests if comment empty
14610     DisplayComment(currentMove - 1, commentList[currentMove]);
14611     ClearMap(); // [HGM] exclude: invalidate map
14612 }
14613
14614 void
14615 BackwardEvent ()
14616 {
14617     if (gameMode == IcsExamining && !pausing) {
14618         SendToICS(ics_prefix);
14619         SendToICS("backward\n");
14620     } else {
14621         BackwardInner(currentMove - 1);
14622     }
14623 }
14624
14625 void
14626 ToStartEvent ()
14627 {
14628     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14629         /* to optimize, we temporarily turn off analysis mode while we undo
14630          * all the moves. Otherwise we get analysis output after each undo.
14631          */
14632         if (first.analysisSupport) {
14633           SendToProgram("exit\nforce\n", &first);
14634           first.analyzing = FALSE;
14635         }
14636     }
14637
14638     if (gameMode == IcsExamining && !pausing) {
14639         SendToICS(ics_prefix);
14640         SendToICS("backward 999999\n");
14641     } else {
14642         BackwardInner(backwardMostMove);
14643     }
14644
14645     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14646         /* we have fed all the moves, so reactivate analysis mode */
14647         SendToProgram("analyze\n", &first);
14648         first.analyzing = TRUE;
14649         /*first.maybeThinking = TRUE;*/
14650         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14651     }
14652 }
14653
14654 void
14655 ToNrEvent (int to)
14656 {
14657   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14658   if (to >= forwardMostMove) to = forwardMostMove;
14659   if (to <= backwardMostMove) to = backwardMostMove;
14660   if (to < currentMove) {
14661     BackwardInner(to);
14662   } else {
14663     ForwardInner(to);
14664   }
14665 }
14666
14667 void
14668 RevertEvent (Boolean annotate)
14669 {
14670     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14671         return;
14672     }
14673     if (gameMode != IcsExamining) {
14674         DisplayError(_("You are not examining a game"), 0);
14675         return;
14676     }
14677     if (pausing) {
14678         DisplayError(_("You can't revert while pausing"), 0);
14679         return;
14680     }
14681     SendToICS(ics_prefix);
14682     SendToICS("revert\n");
14683 }
14684
14685 void
14686 RetractMoveEvent ()
14687 {
14688     switch (gameMode) {
14689       case MachinePlaysWhite:
14690       case MachinePlaysBlack:
14691         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14692             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14693             return;
14694         }
14695         if (forwardMostMove < 2) return;
14696         currentMove = forwardMostMove = forwardMostMove - 2;
14697         whiteTimeRemaining = timeRemaining[0][currentMove];
14698         blackTimeRemaining = timeRemaining[1][currentMove];
14699         DisplayBothClocks();
14700         DisplayMove(currentMove - 1);
14701         ClearHighlights();/*!! could figure this out*/
14702         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14703         SendToProgram("remove\n", &first);
14704         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14705         break;
14706
14707       case BeginningOfGame:
14708       default:
14709         break;
14710
14711       case IcsPlayingWhite:
14712       case IcsPlayingBlack:
14713         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14714             SendToICS(ics_prefix);
14715             SendToICS("takeback 2\n");
14716         } else {
14717             SendToICS(ics_prefix);
14718             SendToICS("takeback 1\n");
14719         }
14720         break;
14721     }
14722 }
14723
14724 void
14725 MoveNowEvent ()
14726 {
14727     ChessProgramState *cps;
14728
14729     switch (gameMode) {
14730       case MachinePlaysWhite:
14731         if (!WhiteOnMove(forwardMostMove)) {
14732             DisplayError(_("It is your turn"), 0);
14733             return;
14734         }
14735         cps = &first;
14736         break;
14737       case MachinePlaysBlack:
14738         if (WhiteOnMove(forwardMostMove)) {
14739             DisplayError(_("It is your turn"), 0);
14740             return;
14741         }
14742         cps = &first;
14743         break;
14744       case TwoMachinesPlay:
14745         if (WhiteOnMove(forwardMostMove) ==
14746             (first.twoMachinesColor[0] == 'w')) {
14747             cps = &first;
14748         } else {
14749             cps = &second;
14750         }
14751         break;
14752       case BeginningOfGame:
14753       default:
14754         return;
14755     }
14756     SendToProgram("?\n", cps);
14757 }
14758
14759 void
14760 TruncateGameEvent ()
14761 {
14762     EditGameEvent();
14763     if (gameMode != EditGame) return;
14764     TruncateGame();
14765 }
14766
14767 void
14768 TruncateGame ()
14769 {
14770     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14771     if (forwardMostMove > currentMove) {
14772         if (gameInfo.resultDetails != NULL) {
14773             free(gameInfo.resultDetails);
14774             gameInfo.resultDetails = NULL;
14775             gameInfo.result = GameUnfinished;
14776         }
14777         forwardMostMove = currentMove;
14778         HistorySet(parseList, backwardMostMove, forwardMostMove,
14779                    currentMove-1);
14780     }
14781 }
14782
14783 void
14784 HintEvent ()
14785 {
14786     if (appData.noChessProgram) return;
14787     switch (gameMode) {
14788       case MachinePlaysWhite:
14789         if (WhiteOnMove(forwardMostMove)) {
14790             DisplayError(_("Wait until your turn"), 0);
14791             return;
14792         }
14793         break;
14794       case BeginningOfGame:
14795       case MachinePlaysBlack:
14796         if (!WhiteOnMove(forwardMostMove)) {
14797             DisplayError(_("Wait until your turn"), 0);
14798             return;
14799         }
14800         break;
14801       default:
14802         DisplayError(_("No hint available"), 0);
14803         return;
14804     }
14805     SendToProgram("hint\n", &first);
14806     hintRequested = TRUE;
14807 }
14808
14809 void
14810 BookEvent ()
14811 {
14812     if (appData.noChessProgram) return;
14813     switch (gameMode) {
14814       case MachinePlaysWhite:
14815         if (WhiteOnMove(forwardMostMove)) {
14816             DisplayError(_("Wait until your turn"), 0);
14817             return;
14818         }
14819         break;
14820       case BeginningOfGame:
14821       case MachinePlaysBlack:
14822         if (!WhiteOnMove(forwardMostMove)) {
14823             DisplayError(_("Wait until your turn"), 0);
14824             return;
14825         }
14826         break;
14827       case EditPosition:
14828         EditPositionDone(TRUE);
14829         break;
14830       case TwoMachinesPlay:
14831         return;
14832       default:
14833         break;
14834     }
14835     SendToProgram("bk\n", &first);
14836     bookOutput[0] = NULLCHAR;
14837     bookRequested = TRUE;
14838 }
14839
14840 void
14841 AboutGameEvent ()
14842 {
14843     char *tags = PGNTags(&gameInfo);
14844     TagsPopUp(tags, CmailMsg());
14845     free(tags);
14846 }
14847
14848 /* end button procedures */
14849
14850 void
14851 PrintPosition (FILE *fp, int move)
14852 {
14853     int i, j;
14854
14855     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14856         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14857             char c = PieceToChar(boards[move][i][j]);
14858             fputc(c == 'x' ? '.' : c, fp);
14859             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14860         }
14861     }
14862     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14863       fprintf(fp, "white to play\n");
14864     else
14865       fprintf(fp, "black to play\n");
14866 }
14867
14868 void
14869 PrintOpponents (FILE *fp)
14870 {
14871     if (gameInfo.white != NULL) {
14872         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14873     } else {
14874         fprintf(fp, "\n");
14875     }
14876 }
14877
14878 /* Find last component of program's own name, using some heuristics */
14879 void
14880 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14881 {
14882     char *p, *q, c;
14883     int local = (strcmp(host, "localhost") == 0);
14884     while (!local && (p = strchr(prog, ';')) != NULL) {
14885         p++;
14886         while (*p == ' ') p++;
14887         prog = p;
14888     }
14889     if (*prog == '"' || *prog == '\'') {
14890         q = strchr(prog + 1, *prog);
14891     } else {
14892         q = strchr(prog, ' ');
14893     }
14894     if (q == NULL) q = prog + strlen(prog);
14895     p = q;
14896     while (p >= prog && *p != '/' && *p != '\\') p--;
14897     p++;
14898     if(p == prog && *p == '"') p++;
14899     c = *q; *q = 0;
14900     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14901     memcpy(buf, p, q - p);
14902     buf[q - p] = NULLCHAR;
14903     if (!local) {
14904         strcat(buf, "@");
14905         strcat(buf, host);
14906     }
14907 }
14908
14909 char *
14910 TimeControlTagValue ()
14911 {
14912     char buf[MSG_SIZ];
14913     if (!appData.clockMode) {
14914       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14915     } else if (movesPerSession > 0) {
14916       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14917     } else if (timeIncrement == 0) {
14918       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14919     } else {
14920       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14921     }
14922     return StrSave(buf);
14923 }
14924
14925 void
14926 SetGameInfo ()
14927 {
14928     /* This routine is used only for certain modes */
14929     VariantClass v = gameInfo.variant;
14930     ChessMove r = GameUnfinished;
14931     char *p = NULL;
14932
14933     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14934         r = gameInfo.result;
14935         p = gameInfo.resultDetails;
14936         gameInfo.resultDetails = NULL;
14937     }
14938     ClearGameInfo(&gameInfo);
14939     gameInfo.variant = v;
14940
14941     switch (gameMode) {
14942       case MachinePlaysWhite:
14943         gameInfo.event = StrSave( appData.pgnEventHeader );
14944         gameInfo.site = StrSave(HostName());
14945         gameInfo.date = PGNDate();
14946         gameInfo.round = StrSave("-");
14947         gameInfo.white = StrSave(first.tidy);
14948         gameInfo.black = StrSave(UserName());
14949         gameInfo.timeControl = TimeControlTagValue();
14950         break;
14951
14952       case MachinePlaysBlack:
14953         gameInfo.event = StrSave( appData.pgnEventHeader );
14954         gameInfo.site = StrSave(HostName());
14955         gameInfo.date = PGNDate();
14956         gameInfo.round = StrSave("-");
14957         gameInfo.white = StrSave(UserName());
14958         gameInfo.black = StrSave(first.tidy);
14959         gameInfo.timeControl = TimeControlTagValue();
14960         break;
14961
14962       case TwoMachinesPlay:
14963         gameInfo.event = StrSave( appData.pgnEventHeader );
14964         gameInfo.site = StrSave(HostName());
14965         gameInfo.date = PGNDate();
14966         if (roundNr > 0) {
14967             char buf[MSG_SIZ];
14968             snprintf(buf, MSG_SIZ, "%d", roundNr);
14969             gameInfo.round = StrSave(buf);
14970         } else {
14971             gameInfo.round = StrSave("-");
14972         }
14973         if (first.twoMachinesColor[0] == 'w') {
14974             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14975             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14976         } else {
14977             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14978             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14979         }
14980         gameInfo.timeControl = TimeControlTagValue();
14981         break;
14982
14983       case EditGame:
14984         gameInfo.event = StrSave("Edited game");
14985         gameInfo.site = StrSave(HostName());
14986         gameInfo.date = PGNDate();
14987         gameInfo.round = StrSave("-");
14988         gameInfo.white = StrSave("-");
14989         gameInfo.black = StrSave("-");
14990         gameInfo.result = r;
14991         gameInfo.resultDetails = p;
14992         break;
14993
14994       case EditPosition:
14995         gameInfo.event = StrSave("Edited position");
14996         gameInfo.site = StrSave(HostName());
14997         gameInfo.date = PGNDate();
14998         gameInfo.round = StrSave("-");
14999         gameInfo.white = StrSave("-");
15000         gameInfo.black = StrSave("-");
15001         break;
15002
15003       case IcsPlayingWhite:
15004       case IcsPlayingBlack:
15005       case IcsObserving:
15006       case IcsExamining:
15007         break;
15008
15009       case PlayFromGameFile:
15010         gameInfo.event = StrSave("Game from non-PGN file");
15011         gameInfo.site = StrSave(HostName());
15012         gameInfo.date = PGNDate();
15013         gameInfo.round = StrSave("-");
15014         gameInfo.white = StrSave("?");
15015         gameInfo.black = StrSave("?");
15016         break;
15017
15018       default:
15019         break;
15020     }
15021 }
15022
15023 void
15024 ReplaceComment (int index, char *text)
15025 {
15026     int len;
15027     char *p;
15028     float score;
15029
15030     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15031        pvInfoList[index-1].depth == len &&
15032        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15033        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15034     while (*text == '\n') text++;
15035     len = strlen(text);
15036     while (len > 0 && text[len - 1] == '\n') len--;
15037
15038     if (commentList[index] != NULL)
15039       free(commentList[index]);
15040
15041     if (len == 0) {
15042         commentList[index] = NULL;
15043         return;
15044     }
15045   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15046       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15047       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15048     commentList[index] = (char *) malloc(len + 2);
15049     strncpy(commentList[index], text, len);
15050     commentList[index][len] = '\n';
15051     commentList[index][len + 1] = NULLCHAR;
15052   } else {
15053     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15054     char *p;
15055     commentList[index] = (char *) malloc(len + 7);
15056     safeStrCpy(commentList[index], "{\n", 3);
15057     safeStrCpy(commentList[index]+2, text, len+1);
15058     commentList[index][len+2] = NULLCHAR;
15059     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15060     strcat(commentList[index], "\n}\n");
15061   }
15062 }
15063
15064 void
15065 CrushCRs (char *text)
15066 {
15067   char *p = text;
15068   char *q = text;
15069   char ch;
15070
15071   do {
15072     ch = *p++;
15073     if (ch == '\r') continue;
15074     *q++ = ch;
15075   } while (ch != '\0');
15076 }
15077
15078 void
15079 AppendComment (int index, char *text, Boolean addBraces)
15080 /* addBraces  tells if we should add {} */
15081 {
15082     int oldlen, len;
15083     char *old;
15084
15085 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15086     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15087
15088     CrushCRs(text);
15089     while (*text == '\n') text++;
15090     len = strlen(text);
15091     while (len > 0 && text[len - 1] == '\n') len--;
15092     text[len] = NULLCHAR;
15093
15094     if (len == 0) return;
15095
15096     if (commentList[index] != NULL) {
15097       Boolean addClosingBrace = addBraces;
15098         old = commentList[index];
15099         oldlen = strlen(old);
15100         while(commentList[index][oldlen-1] ==  '\n')
15101           commentList[index][--oldlen] = NULLCHAR;
15102         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15103         safeStrCpy(commentList[index], old, oldlen + len + 6);
15104         free(old);
15105         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15106         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15107           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15108           while (*text == '\n') { text++; len--; }
15109           commentList[index][--oldlen] = NULLCHAR;
15110       }
15111         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15112         else          strcat(commentList[index], "\n");
15113         strcat(commentList[index], text);
15114         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15115         else          strcat(commentList[index], "\n");
15116     } else {
15117         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15118         if(addBraces)
15119           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15120         else commentList[index][0] = NULLCHAR;
15121         strcat(commentList[index], text);
15122         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15123         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15124     }
15125 }
15126
15127 static char *
15128 FindStr (char * text, char * sub_text)
15129 {
15130     char * result = strstr( text, sub_text );
15131
15132     if( result != NULL ) {
15133         result += strlen( sub_text );
15134     }
15135
15136     return result;
15137 }
15138
15139 /* [AS] Try to extract PV info from PGN comment */
15140 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15141 char *
15142 GetInfoFromComment (int index, char * text)
15143 {
15144     char * sep = text, *p;
15145
15146     if( text != NULL && index > 0 ) {
15147         int score = 0;
15148         int depth = 0;
15149         int time = -1, sec = 0, deci;
15150         char * s_eval = FindStr( text, "[%eval " );
15151         char * s_emt = FindStr( text, "[%emt " );
15152
15153         if( s_eval != NULL || s_emt != NULL ) {
15154             /* New style */
15155             char delim;
15156
15157             if( s_eval != NULL ) {
15158                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15159                     return text;
15160                 }
15161
15162                 if( delim != ']' ) {
15163                     return text;
15164                 }
15165             }
15166
15167             if( s_emt != NULL ) {
15168             }
15169                 return text;
15170         }
15171         else {
15172             /* We expect something like: [+|-]nnn.nn/dd */
15173             int score_lo = 0;
15174
15175             if(*text != '{') return text; // [HGM] braces: must be normal comment
15176
15177             sep = strchr( text, '/' );
15178             if( sep == NULL || sep < (text+4) ) {
15179                 return text;
15180             }
15181
15182             p = text;
15183             if(p[1] == '(') { // comment starts with PV
15184                p = strchr(p, ')'); // locate end of PV
15185                if(p == NULL || sep < p+5) return text;
15186                // at this point we have something like "{(.*) +0.23/6 ..."
15187                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15188                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15189                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15190             }
15191             time = -1; sec = -1; deci = -1;
15192             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15193                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15194                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15195                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15196                 return text;
15197             }
15198
15199             if( score_lo < 0 || score_lo >= 100 ) {
15200                 return text;
15201             }
15202
15203             if(sec >= 0) time = 600*time + 10*sec; else
15204             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15205
15206             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15207
15208             /* [HGM] PV time: now locate end of PV info */
15209             while( *++sep >= '0' && *sep <= '9'); // strip depth
15210             if(time >= 0)
15211             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15212             if(sec >= 0)
15213             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15214             if(deci >= 0)
15215             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15216             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15217         }
15218
15219         if( depth <= 0 ) {
15220             return text;
15221         }
15222
15223         if( time < 0 ) {
15224             time = -1;
15225         }
15226
15227         pvInfoList[index-1].depth = depth;
15228         pvInfoList[index-1].score = score;
15229         pvInfoList[index-1].time  = 10*time; // centi-sec
15230         if(*sep == '}') *sep = 0; else *--sep = '{';
15231         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15232     }
15233     return sep;
15234 }
15235
15236 void
15237 SendToProgram (char *message, ChessProgramState *cps)
15238 {
15239     int count, outCount, error;
15240     char buf[MSG_SIZ];
15241
15242     if (cps->pr == NoProc) return;
15243     Attention(cps);
15244
15245     if (appData.debugMode) {
15246         TimeMark now;
15247         GetTimeMark(&now);
15248         fprintf(debugFP, "%ld >%-6s: %s",
15249                 SubtractTimeMarks(&now, &programStartTime),
15250                 cps->which, message);
15251         if(serverFP)
15252             fprintf(serverFP, "%ld >%-6s: %s",
15253                 SubtractTimeMarks(&now, &programStartTime),
15254                 cps->which, message), fflush(serverFP);
15255     }
15256
15257     count = strlen(message);
15258     outCount = OutputToProcess(cps->pr, message, count, &error);
15259     if (outCount < count && !exiting
15260                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15261       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15262       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15263         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15264             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15265                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15266                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15267                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15268             } else {
15269                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15270                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15271                 gameInfo.result = res;
15272             }
15273             gameInfo.resultDetails = StrSave(buf);
15274         }
15275         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15276         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15277     }
15278 }
15279
15280 void
15281 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15282 {
15283     char *end_str;
15284     char buf[MSG_SIZ];
15285     ChessProgramState *cps = (ChessProgramState *)closure;
15286
15287     if (isr != cps->isr) return; /* Killed intentionally */
15288     if (count <= 0) {
15289         if (count == 0) {
15290             RemoveInputSource(cps->isr);
15291             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15292                     _(cps->which), cps->program);
15293             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15294             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15295                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15296                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15297                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15298                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15299                 } else {
15300                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15301                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15302                     gameInfo.result = res;
15303                 }
15304                 gameInfo.resultDetails = StrSave(buf);
15305             }
15306             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15307             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15308         } else {
15309             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15310                     _(cps->which), cps->program);
15311             RemoveInputSource(cps->isr);
15312
15313             /* [AS] Program is misbehaving badly... kill it */
15314             if( count == -2 ) {
15315                 DestroyChildProcess( cps->pr, 9 );
15316                 cps->pr = NoProc;
15317             }
15318
15319             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15320         }
15321         return;
15322     }
15323
15324     if ((end_str = strchr(message, '\r')) != NULL)
15325       *end_str = NULLCHAR;
15326     if ((end_str = strchr(message, '\n')) != NULL)
15327       *end_str = NULLCHAR;
15328
15329     if (appData.debugMode) {
15330         TimeMark now; int print = 1;
15331         char *quote = ""; char c; int i;
15332
15333         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15334                 char start = message[0];
15335                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15336                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15337                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15338                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15339                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15340                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15341                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15342                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15343                    sscanf(message, "hint: %c", &c)!=1 && 
15344                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15345                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15346                     print = (appData.engineComments >= 2);
15347                 }
15348                 message[0] = start; // restore original message
15349         }
15350         if(print) {
15351                 GetTimeMark(&now);
15352                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15353                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15354                         quote,
15355                         message);
15356                 if(serverFP)
15357                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15358                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15359                         quote,
15360                         message), fflush(serverFP);
15361         }
15362     }
15363
15364     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15365     if (appData.icsEngineAnalyze) {
15366         if (strstr(message, "whisper") != NULL ||
15367              strstr(message, "kibitz") != NULL ||
15368             strstr(message, "tellics") != NULL) return;
15369     }
15370
15371     HandleMachineMove(message, cps);
15372 }
15373
15374
15375 void
15376 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15377 {
15378     char buf[MSG_SIZ];
15379     int seconds;
15380
15381     if( timeControl_2 > 0 ) {
15382         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15383             tc = timeControl_2;
15384         }
15385     }
15386     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15387     inc /= cps->timeOdds;
15388     st  /= cps->timeOdds;
15389
15390     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15391
15392     if (st > 0) {
15393       /* Set exact time per move, normally using st command */
15394       if (cps->stKludge) {
15395         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15396         seconds = st % 60;
15397         if (seconds == 0) {
15398           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15399         } else {
15400           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15401         }
15402       } else {
15403         snprintf(buf, MSG_SIZ, "st %d\n", st);
15404       }
15405     } else {
15406       /* Set conventional or incremental time control, using level command */
15407       if (seconds == 0) {
15408         /* Note old gnuchess bug -- minutes:seconds used to not work.
15409            Fixed in later versions, but still avoid :seconds
15410            when seconds is 0. */
15411         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15412       } else {
15413         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15414                  seconds, inc/1000.);
15415       }
15416     }
15417     SendToProgram(buf, cps);
15418
15419     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15420     /* Orthogonally, limit search to given depth */
15421     if (sd > 0) {
15422       if (cps->sdKludge) {
15423         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15424       } else {
15425         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15426       }
15427       SendToProgram(buf, cps);
15428     }
15429
15430     if(cps->nps >= 0) { /* [HGM] nps */
15431         if(cps->supportsNPS == FALSE)
15432           cps->nps = -1; // don't use if engine explicitly says not supported!
15433         else {
15434           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15435           SendToProgram(buf, cps);
15436         }
15437     }
15438 }
15439
15440 ChessProgramState *
15441 WhitePlayer ()
15442 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15443 {
15444     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15445        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15446         return &second;
15447     return &first;
15448 }
15449
15450 void
15451 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15452 {
15453     char message[MSG_SIZ];
15454     long time, otime;
15455
15456     /* Note: this routine must be called when the clocks are stopped
15457        or when they have *just* been set or switched; otherwise
15458        it will be off by the time since the current tick started.
15459     */
15460     if (machineWhite) {
15461         time = whiteTimeRemaining / 10;
15462         otime = blackTimeRemaining / 10;
15463     } else {
15464         time = blackTimeRemaining / 10;
15465         otime = whiteTimeRemaining / 10;
15466     }
15467     /* [HGM] translate opponent's time by time-odds factor */
15468     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15469
15470     if (time <= 0) time = 1;
15471     if (otime <= 0) otime = 1;
15472
15473     snprintf(message, MSG_SIZ, "time %ld\n", time);
15474     SendToProgram(message, cps);
15475
15476     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15477     SendToProgram(message, cps);
15478 }
15479
15480 int
15481 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15482 {
15483   char buf[MSG_SIZ];
15484   int len = strlen(name);
15485   int val;
15486
15487   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15488     (*p) += len + 1;
15489     sscanf(*p, "%d", &val);
15490     *loc = (val != 0);
15491     while (**p && **p != ' ')
15492       (*p)++;
15493     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15494     SendToProgram(buf, cps);
15495     return TRUE;
15496   }
15497   return FALSE;
15498 }
15499
15500 int
15501 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15502 {
15503   char buf[MSG_SIZ];
15504   int len = strlen(name);
15505   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15506     (*p) += len + 1;
15507     sscanf(*p, "%d", loc);
15508     while (**p && **p != ' ') (*p)++;
15509     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15510     SendToProgram(buf, cps);
15511     return TRUE;
15512   }
15513   return FALSE;
15514 }
15515
15516 int
15517 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15518 {
15519   char buf[MSG_SIZ];
15520   int len = strlen(name);
15521   if (strncmp((*p), name, len) == 0
15522       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15523     (*p) += len + 2;
15524     sscanf(*p, "%[^\"]", loc);
15525     while (**p && **p != '\"') (*p)++;
15526     if (**p == '\"') (*p)++;
15527     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15528     SendToProgram(buf, cps);
15529     return TRUE;
15530   }
15531   return FALSE;
15532 }
15533
15534 int
15535 ParseOption (Option *opt, ChessProgramState *cps)
15536 // [HGM] options: process the string that defines an engine option, and determine
15537 // name, type, default value, and allowed value range
15538 {
15539         char *p, *q, buf[MSG_SIZ];
15540         int n, min = (-1)<<31, max = 1<<31, def;
15541
15542         if(p = strstr(opt->name, " -spin ")) {
15543             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15544             if(max < min) max = min; // enforce consistency
15545             if(def < min) def = min;
15546             if(def > max) def = max;
15547             opt->value = def;
15548             opt->min = min;
15549             opt->max = max;
15550             opt->type = Spin;
15551         } else if((p = strstr(opt->name, " -slider "))) {
15552             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15553             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15554             if(max < min) max = min; // enforce consistency
15555             if(def < min) def = min;
15556             if(def > max) def = max;
15557             opt->value = def;
15558             opt->min = min;
15559             opt->max = max;
15560             opt->type = Spin; // Slider;
15561         } else if((p = strstr(opt->name, " -string "))) {
15562             opt->textValue = p+9;
15563             opt->type = TextBox;
15564         } else if((p = strstr(opt->name, " -file "))) {
15565             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15566             opt->textValue = p+7;
15567             opt->type = FileName; // FileName;
15568         } else if((p = strstr(opt->name, " -path "))) {
15569             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15570             opt->textValue = p+7;
15571             opt->type = PathName; // PathName;
15572         } else if(p = strstr(opt->name, " -check ")) {
15573             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15574             opt->value = (def != 0);
15575             opt->type = CheckBox;
15576         } else if(p = strstr(opt->name, " -combo ")) {
15577             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15578             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15579             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15580             opt->value = n = 0;
15581             while(q = StrStr(q, " /// ")) {
15582                 n++; *q = 0;    // count choices, and null-terminate each of them
15583                 q += 5;
15584                 if(*q == '*') { // remember default, which is marked with * prefix
15585                     q++;
15586                     opt->value = n;
15587                 }
15588                 cps->comboList[cps->comboCnt++] = q;
15589             }
15590             cps->comboList[cps->comboCnt++] = NULL;
15591             opt->max = n + 1;
15592             opt->type = ComboBox;
15593         } else if(p = strstr(opt->name, " -button")) {
15594             opt->type = Button;
15595         } else if(p = strstr(opt->name, " -save")) {
15596             opt->type = SaveButton;
15597         } else return FALSE;
15598         *p = 0; // terminate option name
15599         // now look if the command-line options define a setting for this engine option.
15600         if(cps->optionSettings && cps->optionSettings[0])
15601             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15602         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15603           snprintf(buf, MSG_SIZ, "option %s", p);
15604                 if(p = strstr(buf, ",")) *p = 0;
15605                 if(q = strchr(buf, '=')) switch(opt->type) {
15606                     case ComboBox:
15607                         for(n=0; n<opt->max; n++)
15608                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15609                         break;
15610                     case TextBox:
15611                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15612                         break;
15613                     case Spin:
15614                     case CheckBox:
15615                         opt->value = atoi(q+1);
15616                     default:
15617                         break;
15618                 }
15619                 strcat(buf, "\n");
15620                 SendToProgram(buf, cps);
15621         }
15622         return TRUE;
15623 }
15624
15625 void
15626 FeatureDone (ChessProgramState *cps, int val)
15627 {
15628   DelayedEventCallback cb = GetDelayedEvent();
15629   if ((cb == InitBackEnd3 && cps == &first) ||
15630       (cb == SettingsMenuIfReady && cps == &second) ||
15631       (cb == LoadEngine) ||
15632       (cb == TwoMachinesEventIfReady)) {
15633     CancelDelayedEvent();
15634     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15635   }
15636   cps->initDone = val;
15637 }
15638
15639 /* Parse feature command from engine */
15640 void
15641 ParseFeatures (char *args, ChessProgramState *cps)
15642 {
15643   char *p = args;
15644   char *q;
15645   int val;
15646   char buf[MSG_SIZ];
15647
15648   for (;;) {
15649     while (*p == ' ') p++;
15650     if (*p == NULLCHAR) return;
15651
15652     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15653     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15654     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15655     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15656     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15657     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15658     if (BoolFeature(&p, "reuse", &val, cps)) {
15659       /* Engine can disable reuse, but can't enable it if user said no */
15660       if (!val) cps->reuse = FALSE;
15661       continue;
15662     }
15663     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15664     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15665       if (gameMode == TwoMachinesPlay) {
15666         DisplayTwoMachinesTitle();
15667       } else {
15668         DisplayTitle("");
15669       }
15670       continue;
15671     }
15672     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15673     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15674     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15675     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15676     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15677     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15678     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15679     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15680     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15681     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15682     if (IntFeature(&p, "done", &val, cps)) {
15683       FeatureDone(cps, val);
15684       continue;
15685     }
15686     /* Added by Tord: */
15687     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15688     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15689     /* End of additions by Tord */
15690
15691     /* [HGM] added features: */
15692     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15693     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15694     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15695     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15696     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15697     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15698     if (StringFeature(&p, "option", buf, cps)) {
15699         FREE(cps->option[cps->nrOptions].name);
15700         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15701         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15702         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15703           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15704             SendToProgram(buf, cps);
15705             continue;
15706         }
15707         if(cps->nrOptions >= MAX_OPTIONS) {
15708             cps->nrOptions--;
15709             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15710             DisplayError(buf, 0);
15711         }
15712         continue;
15713     }
15714     /* End of additions by HGM */
15715
15716     /* unknown feature: complain and skip */
15717     q = p;
15718     while (*q && *q != '=') q++;
15719     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15720     SendToProgram(buf, cps);
15721     p = q;
15722     if (*p == '=') {
15723       p++;
15724       if (*p == '\"') {
15725         p++;
15726         while (*p && *p != '\"') p++;
15727         if (*p == '\"') p++;
15728       } else {
15729         while (*p && *p != ' ') p++;
15730       }
15731     }
15732   }
15733
15734 }
15735
15736 void
15737 PeriodicUpdatesEvent (int newState)
15738 {
15739     if (newState == appData.periodicUpdates)
15740       return;
15741
15742     appData.periodicUpdates=newState;
15743
15744     /* Display type changes, so update it now */
15745 //    DisplayAnalysis();
15746
15747     /* Get the ball rolling again... */
15748     if (newState) {
15749         AnalysisPeriodicEvent(1);
15750         StartAnalysisClock();
15751     }
15752 }
15753
15754 void
15755 PonderNextMoveEvent (int newState)
15756 {
15757     if (newState == appData.ponderNextMove) return;
15758     if (gameMode == EditPosition) EditPositionDone(TRUE);
15759     if (newState) {
15760         SendToProgram("hard\n", &first);
15761         if (gameMode == TwoMachinesPlay) {
15762             SendToProgram("hard\n", &second);
15763         }
15764     } else {
15765         SendToProgram("easy\n", &first);
15766         thinkOutput[0] = NULLCHAR;
15767         if (gameMode == TwoMachinesPlay) {
15768             SendToProgram("easy\n", &second);
15769         }
15770     }
15771     appData.ponderNextMove = newState;
15772 }
15773
15774 void
15775 NewSettingEvent (int option, int *feature, char *command, int value)
15776 {
15777     char buf[MSG_SIZ];
15778
15779     if (gameMode == EditPosition) EditPositionDone(TRUE);
15780     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15781     if(feature == NULL || *feature) SendToProgram(buf, &first);
15782     if (gameMode == TwoMachinesPlay) {
15783         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15784     }
15785 }
15786
15787 void
15788 ShowThinkingEvent ()
15789 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15790 {
15791     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15792     int newState = appData.showThinking
15793         // [HGM] thinking: other features now need thinking output as well
15794         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15795
15796     if (oldState == newState) return;
15797     oldState = newState;
15798     if (gameMode == EditPosition) EditPositionDone(TRUE);
15799     if (oldState) {
15800         SendToProgram("post\n", &first);
15801         if (gameMode == TwoMachinesPlay) {
15802             SendToProgram("post\n", &second);
15803         }
15804     } else {
15805         SendToProgram("nopost\n", &first);
15806         thinkOutput[0] = NULLCHAR;
15807         if (gameMode == TwoMachinesPlay) {
15808             SendToProgram("nopost\n", &second);
15809         }
15810     }
15811 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15812 }
15813
15814 void
15815 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15816 {
15817   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15818   if (pr == NoProc) return;
15819   AskQuestion(title, question, replyPrefix, pr);
15820 }
15821
15822 void
15823 TypeInEvent (char firstChar)
15824 {
15825     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15826         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15827         gameMode == AnalyzeMode || gameMode == EditGame || 
15828         gameMode == EditPosition || gameMode == IcsExamining ||
15829         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15830         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15831                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15832                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15833         gameMode == Training) PopUpMoveDialog(firstChar);
15834 }
15835
15836 void
15837 TypeInDoneEvent (char *move)
15838 {
15839         Board board;
15840         int n, fromX, fromY, toX, toY;
15841         char promoChar;
15842         ChessMove moveType;
15843
15844         // [HGM] FENedit
15845         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15846                 EditPositionPasteFEN(move);
15847                 return;
15848         }
15849         // [HGM] movenum: allow move number to be typed in any mode
15850         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15851           ToNrEvent(2*n-1);
15852           return;
15853         }
15854         // undocumented kludge: allow command-line option to be typed in!
15855         // (potentially fatal, and does not implement the effect of the option.)
15856         // should only be used for options that are values on which future decisions will be made,
15857         // and definitely not on options that would be used during initialization.
15858         if(strstr(move, "!!! -") == move) {
15859             ParseArgsFromString(move+4);
15860             return;
15861         }
15862
15863       if (gameMode != EditGame && currentMove != forwardMostMove && 
15864         gameMode != Training) {
15865         DisplayMoveError(_("Displayed move is not current"));
15866       } else {
15867         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15868           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15869         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15870         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15871           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15872           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15873         } else {
15874           DisplayMoveError(_("Could not parse move"));
15875         }
15876       }
15877 }
15878
15879 void
15880 DisplayMove (int moveNumber)
15881 {
15882     char message[MSG_SIZ];
15883     char res[MSG_SIZ];
15884     char cpThinkOutput[MSG_SIZ];
15885
15886     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15887
15888     if (moveNumber == forwardMostMove - 1 ||
15889         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15890
15891         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15892
15893         if (strchr(cpThinkOutput, '\n')) {
15894             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15895         }
15896     } else {
15897         *cpThinkOutput = NULLCHAR;
15898     }
15899
15900     /* [AS] Hide thinking from human user */
15901     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15902         *cpThinkOutput = NULLCHAR;
15903         if( thinkOutput[0] != NULLCHAR ) {
15904             int i;
15905
15906             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15907                 cpThinkOutput[i] = '.';
15908             }
15909             cpThinkOutput[i] = NULLCHAR;
15910             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15911         }
15912     }
15913
15914     if (moveNumber == forwardMostMove - 1 &&
15915         gameInfo.resultDetails != NULL) {
15916         if (gameInfo.resultDetails[0] == NULLCHAR) {
15917           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15918         } else {
15919           snprintf(res, MSG_SIZ, " {%s} %s",
15920                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15921         }
15922     } else {
15923         res[0] = NULLCHAR;
15924     }
15925
15926     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15927         DisplayMessage(res, cpThinkOutput);
15928     } else {
15929       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15930                 WhiteOnMove(moveNumber) ? " " : ".. ",
15931                 parseList[moveNumber], res);
15932         DisplayMessage(message, cpThinkOutput);
15933     }
15934 }
15935
15936 void
15937 DisplayComment (int moveNumber, char *text)
15938 {
15939     char title[MSG_SIZ];
15940
15941     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15942       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15943     } else {
15944       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15945               WhiteOnMove(moveNumber) ? " " : ".. ",
15946               parseList[moveNumber]);
15947     }
15948     if (text != NULL && (appData.autoDisplayComment || commentUp))
15949         CommentPopUp(title, text);
15950 }
15951
15952 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15953  * might be busy thinking or pondering.  It can be omitted if your
15954  * gnuchess is configured to stop thinking immediately on any user
15955  * input.  However, that gnuchess feature depends on the FIONREAD
15956  * ioctl, which does not work properly on some flavors of Unix.
15957  */
15958 void
15959 Attention (ChessProgramState *cps)
15960 {
15961 #if ATTENTION
15962     if (!cps->useSigint) return;
15963     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15964     switch (gameMode) {
15965       case MachinePlaysWhite:
15966       case MachinePlaysBlack:
15967       case TwoMachinesPlay:
15968       case IcsPlayingWhite:
15969       case IcsPlayingBlack:
15970       case AnalyzeMode:
15971       case AnalyzeFile:
15972         /* Skip if we know it isn't thinking */
15973         if (!cps->maybeThinking) return;
15974         if (appData.debugMode)
15975           fprintf(debugFP, "Interrupting %s\n", cps->which);
15976         InterruptChildProcess(cps->pr);
15977         cps->maybeThinking = FALSE;
15978         break;
15979       default:
15980         break;
15981     }
15982 #endif /*ATTENTION*/
15983 }
15984
15985 int
15986 CheckFlags ()
15987 {
15988     if (whiteTimeRemaining <= 0) {
15989         if (!whiteFlag) {
15990             whiteFlag = TRUE;
15991             if (appData.icsActive) {
15992                 if (appData.autoCallFlag &&
15993                     gameMode == IcsPlayingBlack && !blackFlag) {
15994                   SendToICS(ics_prefix);
15995                   SendToICS("flag\n");
15996                 }
15997             } else {
15998                 if (blackFlag) {
15999                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16000                 } else {
16001                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16002                     if (appData.autoCallFlag) {
16003                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16004                         return TRUE;
16005                     }
16006                 }
16007             }
16008         }
16009     }
16010     if (blackTimeRemaining <= 0) {
16011         if (!blackFlag) {
16012             blackFlag = TRUE;
16013             if (appData.icsActive) {
16014                 if (appData.autoCallFlag &&
16015                     gameMode == IcsPlayingWhite && !whiteFlag) {
16016                   SendToICS(ics_prefix);
16017                   SendToICS("flag\n");
16018                 }
16019             } else {
16020                 if (whiteFlag) {
16021                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16022                 } else {
16023                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16024                     if (appData.autoCallFlag) {
16025                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16026                         return TRUE;
16027                     }
16028                 }
16029             }
16030         }
16031     }
16032     return FALSE;
16033 }
16034
16035 void
16036 CheckTimeControl ()
16037 {
16038     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16039         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16040
16041     /*
16042      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16043      */
16044     if ( !WhiteOnMove(forwardMostMove) ) {
16045         /* White made time control */
16046         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16047         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16048         /* [HGM] time odds: correct new time quota for time odds! */
16049                                             / WhitePlayer()->timeOdds;
16050         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16051     } else {
16052         lastBlack -= blackTimeRemaining;
16053         /* Black made time control */
16054         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16055                                             / WhitePlayer()->other->timeOdds;
16056         lastWhite = whiteTimeRemaining;
16057     }
16058 }
16059
16060 void
16061 DisplayBothClocks ()
16062 {
16063     int wom = gameMode == EditPosition ?
16064       !blackPlaysFirst : WhiteOnMove(currentMove);
16065     DisplayWhiteClock(whiteTimeRemaining, wom);
16066     DisplayBlackClock(blackTimeRemaining, !wom);
16067 }
16068
16069
16070 /* Timekeeping seems to be a portability nightmare.  I think everyone
16071    has ftime(), but I'm really not sure, so I'm including some ifdefs
16072    to use other calls if you don't.  Clocks will be less accurate if
16073    you have neither ftime nor gettimeofday.
16074 */
16075
16076 /* VS 2008 requires the #include outside of the function */
16077 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16078 #include <sys/timeb.h>
16079 #endif
16080
16081 /* Get the current time as a TimeMark */
16082 void
16083 GetTimeMark (TimeMark *tm)
16084 {
16085 #if HAVE_GETTIMEOFDAY
16086
16087     struct timeval timeVal;
16088     struct timezone timeZone;
16089
16090     gettimeofday(&timeVal, &timeZone);
16091     tm->sec = (long) timeVal.tv_sec;
16092     tm->ms = (int) (timeVal.tv_usec / 1000L);
16093
16094 #else /*!HAVE_GETTIMEOFDAY*/
16095 #if HAVE_FTIME
16096
16097 // include <sys/timeb.h> / moved to just above start of function
16098     struct timeb timeB;
16099
16100     ftime(&timeB);
16101     tm->sec = (long) timeB.time;
16102     tm->ms = (int) timeB.millitm;
16103
16104 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16105     tm->sec = (long) time(NULL);
16106     tm->ms = 0;
16107 #endif
16108 #endif
16109 }
16110
16111 /* Return the difference in milliseconds between two
16112    time marks.  We assume the difference will fit in a long!
16113 */
16114 long
16115 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16116 {
16117     return 1000L*(tm2->sec - tm1->sec) +
16118            (long) (tm2->ms - tm1->ms);
16119 }
16120
16121
16122 /*
16123  * Code to manage the game clocks.
16124  *
16125  * In tournament play, black starts the clock and then white makes a move.
16126  * We give the human user a slight advantage if he is playing white---the
16127  * clocks don't run until he makes his first move, so it takes zero time.
16128  * Also, we don't account for network lag, so we could get out of sync
16129  * with GNU Chess's clock -- but then, referees are always right.
16130  */
16131
16132 static TimeMark tickStartTM;
16133 static long intendedTickLength;
16134
16135 long
16136 NextTickLength (long timeRemaining)
16137 {
16138     long nominalTickLength, nextTickLength;
16139
16140     if (timeRemaining > 0L && timeRemaining <= 10000L)
16141       nominalTickLength = 100L;
16142     else
16143       nominalTickLength = 1000L;
16144     nextTickLength = timeRemaining % nominalTickLength;
16145     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16146
16147     return nextTickLength;
16148 }
16149
16150 /* Adjust clock one minute up or down */
16151 void
16152 AdjustClock (Boolean which, int dir)
16153 {
16154     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16155     if(which) blackTimeRemaining += 60000*dir;
16156     else      whiteTimeRemaining += 60000*dir;
16157     DisplayBothClocks();
16158     adjustedClock = TRUE;
16159 }
16160
16161 /* Stop clocks and reset to a fresh time control */
16162 void
16163 ResetClocks ()
16164 {
16165     (void) StopClockTimer();
16166     if (appData.icsActive) {
16167         whiteTimeRemaining = blackTimeRemaining = 0;
16168     } else if (searchTime) {
16169         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16170         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16171     } else { /* [HGM] correct new time quote for time odds */
16172         whiteTC = blackTC = fullTimeControlString;
16173         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16174         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16175     }
16176     if (whiteFlag || blackFlag) {
16177         DisplayTitle("");
16178         whiteFlag = blackFlag = FALSE;
16179     }
16180     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16181     DisplayBothClocks();
16182     adjustedClock = FALSE;
16183 }
16184
16185 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16186
16187 /* Decrement running clock by amount of time that has passed */
16188 void
16189 DecrementClocks ()
16190 {
16191     long timeRemaining;
16192     long lastTickLength, fudge;
16193     TimeMark now;
16194
16195     if (!appData.clockMode) return;
16196     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16197
16198     GetTimeMark(&now);
16199
16200     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16201
16202     /* Fudge if we woke up a little too soon */
16203     fudge = intendedTickLength - lastTickLength;
16204     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16205
16206     if (WhiteOnMove(forwardMostMove)) {
16207         if(whiteNPS >= 0) lastTickLength = 0;
16208         timeRemaining = whiteTimeRemaining -= lastTickLength;
16209         if(timeRemaining < 0 && !appData.icsActive) {
16210             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16211             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16212                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16213                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16214             }
16215         }
16216         DisplayWhiteClock(whiteTimeRemaining - fudge,
16217                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16218     } else {
16219         if(blackNPS >= 0) lastTickLength = 0;
16220         timeRemaining = blackTimeRemaining -= lastTickLength;
16221         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16222             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16223             if(suddenDeath) {
16224                 blackStartMove = forwardMostMove;
16225                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16226             }
16227         }
16228         DisplayBlackClock(blackTimeRemaining - fudge,
16229                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16230     }
16231     if (CheckFlags()) return;
16232
16233     if(twoBoards) { // count down secondary board's clocks as well
16234         activePartnerTime -= lastTickLength;
16235         partnerUp = 1;
16236         if(activePartner == 'W')
16237             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16238         else
16239             DisplayBlackClock(activePartnerTime, TRUE);
16240         partnerUp = 0;
16241     }
16242
16243     tickStartTM = now;
16244     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16245     StartClockTimer(intendedTickLength);
16246
16247     /* if the time remaining has fallen below the alarm threshold, sound the
16248      * alarm. if the alarm has sounded and (due to a takeback or time control
16249      * with increment) the time remaining has increased to a level above the
16250      * threshold, reset the alarm so it can sound again.
16251      */
16252
16253     if (appData.icsActive && appData.icsAlarm) {
16254
16255         /* make sure we are dealing with the user's clock */
16256         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16257                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16258            )) return;
16259
16260         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16261             alarmSounded = FALSE;
16262         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16263             PlayAlarmSound();
16264             alarmSounded = TRUE;
16265         }
16266     }
16267 }
16268
16269
16270 /* A player has just moved, so stop the previously running
16271    clock and (if in clock mode) start the other one.
16272    We redisplay both clocks in case we're in ICS mode, because
16273    ICS gives us an update to both clocks after every move.
16274    Note that this routine is called *after* forwardMostMove
16275    is updated, so the last fractional tick must be subtracted
16276    from the color that is *not* on move now.
16277 */
16278 void
16279 SwitchClocks (int newMoveNr)
16280 {
16281     long lastTickLength;
16282     TimeMark now;
16283     int flagged = FALSE;
16284
16285     GetTimeMark(&now);
16286
16287     if (StopClockTimer() && appData.clockMode) {
16288         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16289         if (!WhiteOnMove(forwardMostMove)) {
16290             if(blackNPS >= 0) lastTickLength = 0;
16291             blackTimeRemaining -= lastTickLength;
16292            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16293 //         if(pvInfoList[forwardMostMove].time == -1)
16294                  pvInfoList[forwardMostMove].time =               // use GUI time
16295                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16296         } else {
16297            if(whiteNPS >= 0) lastTickLength = 0;
16298            whiteTimeRemaining -= lastTickLength;
16299            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16300 //         if(pvInfoList[forwardMostMove].time == -1)
16301                  pvInfoList[forwardMostMove].time =
16302                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16303         }
16304         flagged = CheckFlags();
16305     }
16306     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16307     CheckTimeControl();
16308
16309     if (flagged || !appData.clockMode) return;
16310
16311     switch (gameMode) {
16312       case MachinePlaysBlack:
16313       case MachinePlaysWhite:
16314       case BeginningOfGame:
16315         if (pausing) return;
16316         break;
16317
16318       case EditGame:
16319       case PlayFromGameFile:
16320       case IcsExamining:
16321         return;
16322
16323       default:
16324         break;
16325     }
16326
16327     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16328         if(WhiteOnMove(forwardMostMove))
16329              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16330         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16331     }
16332
16333     tickStartTM = now;
16334     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16335       whiteTimeRemaining : blackTimeRemaining);
16336     StartClockTimer(intendedTickLength);
16337 }
16338
16339
16340 /* Stop both clocks */
16341 void
16342 StopClocks ()
16343 {
16344     long lastTickLength;
16345     TimeMark now;
16346
16347     if (!StopClockTimer()) return;
16348     if (!appData.clockMode) return;
16349
16350     GetTimeMark(&now);
16351
16352     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16353     if (WhiteOnMove(forwardMostMove)) {
16354         if(whiteNPS >= 0) lastTickLength = 0;
16355         whiteTimeRemaining -= lastTickLength;
16356         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16357     } else {
16358         if(blackNPS >= 0) lastTickLength = 0;
16359         blackTimeRemaining -= lastTickLength;
16360         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16361     }
16362     CheckFlags();
16363 }
16364
16365 /* Start clock of player on move.  Time may have been reset, so
16366    if clock is already running, stop and restart it. */
16367 void
16368 StartClocks ()
16369 {
16370     (void) StopClockTimer(); /* in case it was running already */
16371     DisplayBothClocks();
16372     if (CheckFlags()) return;
16373
16374     if (!appData.clockMode) return;
16375     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16376
16377     GetTimeMark(&tickStartTM);
16378     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16379       whiteTimeRemaining : blackTimeRemaining);
16380
16381    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16382     whiteNPS = blackNPS = -1;
16383     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16384        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16385         whiteNPS = first.nps;
16386     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16387        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16388         blackNPS = first.nps;
16389     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16390         whiteNPS = second.nps;
16391     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16392         blackNPS = second.nps;
16393     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16394
16395     StartClockTimer(intendedTickLength);
16396 }
16397
16398 char *
16399 TimeString (long ms)
16400 {
16401     long second, minute, hour, day;
16402     char *sign = "";
16403     static char buf[32];
16404
16405     if (ms > 0 && ms <= 9900) {
16406       /* convert milliseconds to tenths, rounding up */
16407       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16408
16409       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16410       return buf;
16411     }
16412
16413     /* convert milliseconds to seconds, rounding up */
16414     /* use floating point to avoid strangeness of integer division
16415        with negative dividends on many machines */
16416     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16417
16418     if (second < 0) {
16419         sign = "-";
16420         second = -second;
16421     }
16422
16423     day = second / (60 * 60 * 24);
16424     second = second % (60 * 60 * 24);
16425     hour = second / (60 * 60);
16426     second = second % (60 * 60);
16427     minute = second / 60;
16428     second = second % 60;
16429
16430     if (day > 0)
16431       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16432               sign, day, hour, minute, second);
16433     else if (hour > 0)
16434       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16435     else
16436       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16437
16438     return buf;
16439 }
16440
16441
16442 /*
16443  * This is necessary because some C libraries aren't ANSI C compliant yet.
16444  */
16445 char *
16446 StrStr (char *string, char *match)
16447 {
16448     int i, length;
16449
16450     length = strlen(match);
16451
16452     for (i = strlen(string) - length; i >= 0; i--, string++)
16453       if (!strncmp(match, string, length))
16454         return string;
16455
16456     return NULL;
16457 }
16458
16459 char *
16460 StrCaseStr (char *string, char *match)
16461 {
16462     int i, j, length;
16463
16464     length = strlen(match);
16465
16466     for (i = strlen(string) - length; i >= 0; i--, string++) {
16467         for (j = 0; j < length; j++) {
16468             if (ToLower(match[j]) != ToLower(string[j]))
16469               break;
16470         }
16471         if (j == length) return string;
16472     }
16473
16474     return NULL;
16475 }
16476
16477 #ifndef _amigados
16478 int
16479 StrCaseCmp (char *s1, char *s2)
16480 {
16481     char c1, c2;
16482
16483     for (;;) {
16484         c1 = ToLower(*s1++);
16485         c2 = ToLower(*s2++);
16486         if (c1 > c2) return 1;
16487         if (c1 < c2) return -1;
16488         if (c1 == NULLCHAR) return 0;
16489     }
16490 }
16491
16492
16493 int
16494 ToLower (int c)
16495 {
16496     return isupper(c) ? tolower(c) : c;
16497 }
16498
16499
16500 int
16501 ToUpper (int c)
16502 {
16503     return islower(c) ? toupper(c) : c;
16504 }
16505 #endif /* !_amigados    */
16506
16507 char *
16508 StrSave (char *s)
16509 {
16510   char *ret;
16511
16512   if ((ret = (char *) malloc(strlen(s) + 1)))
16513     {
16514       safeStrCpy(ret, s, strlen(s)+1);
16515     }
16516   return ret;
16517 }
16518
16519 char *
16520 StrSavePtr (char *s, char **savePtr)
16521 {
16522     if (*savePtr) {
16523         free(*savePtr);
16524     }
16525     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16526       safeStrCpy(*savePtr, s, strlen(s)+1);
16527     }
16528     return(*savePtr);
16529 }
16530
16531 char *
16532 PGNDate ()
16533 {
16534     time_t clock;
16535     struct tm *tm;
16536     char buf[MSG_SIZ];
16537
16538     clock = time((time_t *)NULL);
16539     tm = localtime(&clock);
16540     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16541             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16542     return StrSave(buf);
16543 }
16544
16545
16546 char *
16547 PositionToFEN (int move, char *overrideCastling)
16548 {
16549     int i, j, fromX, fromY, toX, toY;
16550     int whiteToPlay;
16551     char buf[MSG_SIZ];
16552     char *p, *q;
16553     int emptycount;
16554     ChessSquare piece;
16555
16556     whiteToPlay = (gameMode == EditPosition) ?
16557       !blackPlaysFirst : (move % 2 == 0);
16558     p = buf;
16559
16560     /* Piece placement data */
16561     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16562         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16563         emptycount = 0;
16564         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16565             if (boards[move][i][j] == EmptySquare) {
16566                 emptycount++;
16567             } else { ChessSquare piece = boards[move][i][j];
16568                 if (emptycount > 0) {
16569                     if(emptycount<10) /* [HGM] can be >= 10 */
16570                         *p++ = '0' + emptycount;
16571                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16572                     emptycount = 0;
16573                 }
16574                 if(PieceToChar(piece) == '+') {
16575                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16576                     *p++ = '+';
16577                     piece = (ChessSquare)(DEMOTED piece);
16578                 }
16579                 *p++ = PieceToChar(piece);
16580                 if(p[-1] == '~') {
16581                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16582                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16583                     *p++ = '~';
16584                 }
16585             }
16586         }
16587         if (emptycount > 0) {
16588             if(emptycount<10) /* [HGM] can be >= 10 */
16589                 *p++ = '0' + emptycount;
16590             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16591             emptycount = 0;
16592         }
16593         *p++ = '/';
16594     }
16595     *(p - 1) = ' ';
16596
16597     /* [HGM] print Crazyhouse or Shogi holdings */
16598     if( gameInfo.holdingsWidth ) {
16599         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16600         q = p;
16601         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16602             piece = boards[move][i][BOARD_WIDTH-1];
16603             if( piece != EmptySquare )
16604               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16605                   *p++ = PieceToChar(piece);
16606         }
16607         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16608             piece = boards[move][BOARD_HEIGHT-i-1][0];
16609             if( piece != EmptySquare )
16610               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16611                   *p++ = PieceToChar(piece);
16612         }
16613
16614         if( q == p ) *p++ = '-';
16615         *p++ = ']';
16616         *p++ = ' ';
16617     }
16618
16619     /* Active color */
16620     *p++ = whiteToPlay ? 'w' : 'b';
16621     *p++ = ' ';
16622
16623   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16624     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16625   } else {
16626   if(nrCastlingRights) {
16627      q = p;
16628      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16629        /* [HGM] write directly from rights */
16630            if(boards[move][CASTLING][2] != NoRights &&
16631               boards[move][CASTLING][0] != NoRights   )
16632                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16633            if(boards[move][CASTLING][2] != NoRights &&
16634               boards[move][CASTLING][1] != NoRights   )
16635                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16636            if(boards[move][CASTLING][5] != NoRights &&
16637               boards[move][CASTLING][3] != NoRights   )
16638                 *p++ = boards[move][CASTLING][3] + AAA;
16639            if(boards[move][CASTLING][5] != NoRights &&
16640               boards[move][CASTLING][4] != NoRights   )
16641                 *p++ = boards[move][CASTLING][4] + AAA;
16642      } else {
16643
16644         /* [HGM] write true castling rights */
16645         if( nrCastlingRights == 6 ) {
16646             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16647                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16648             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16649                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16650             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16651                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16652             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16653                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16654         }
16655      }
16656      if (q == p) *p++ = '-'; /* No castling rights */
16657      *p++ = ' ';
16658   }
16659
16660   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16661      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16662     /* En passant target square */
16663     if (move > backwardMostMove) {
16664         fromX = moveList[move - 1][0] - AAA;
16665         fromY = moveList[move - 1][1] - ONE;
16666         toX = moveList[move - 1][2] - AAA;
16667         toY = moveList[move - 1][3] - ONE;
16668         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16669             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16670             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16671             fromX == toX) {
16672             /* 2-square pawn move just happened */
16673             *p++ = toX + AAA;
16674             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16675         } else {
16676             *p++ = '-';
16677         }
16678     } else if(move == backwardMostMove) {
16679         // [HGM] perhaps we should always do it like this, and forget the above?
16680         if((signed char)boards[move][EP_STATUS] >= 0) {
16681             *p++ = boards[move][EP_STATUS] + AAA;
16682             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16683         } else {
16684             *p++ = '-';
16685         }
16686     } else {
16687         *p++ = '-';
16688     }
16689     *p++ = ' ';
16690   }
16691   }
16692
16693     /* [HGM] find reversible plies */
16694     {   int i = 0, j=move;
16695
16696         if (appData.debugMode) { int k;
16697             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16698             for(k=backwardMostMove; k<=forwardMostMove; k++)
16699                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16700
16701         }
16702
16703         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16704         if( j == backwardMostMove ) i += initialRulePlies;
16705         sprintf(p, "%d ", i);
16706         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16707     }
16708     /* Fullmove number */
16709     sprintf(p, "%d", (move / 2) + 1);
16710
16711     return StrSave(buf);
16712 }
16713
16714 Boolean
16715 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16716 {
16717     int i, j;
16718     char *p, c;
16719     int emptycount;
16720     ChessSquare piece;
16721
16722     p = fen;
16723
16724     /* [HGM] by default clear Crazyhouse holdings, if present */
16725     if(gameInfo.holdingsWidth) {
16726        for(i=0; i<BOARD_HEIGHT; i++) {
16727            board[i][0]             = EmptySquare; /* black holdings */
16728            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16729            board[i][1]             = (ChessSquare) 0; /* black counts */
16730            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16731        }
16732     }
16733
16734     /* Piece placement data */
16735     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16736         j = 0;
16737         for (;;) {
16738             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16739                 if (*p == '/') p++;
16740                 emptycount = gameInfo.boardWidth - j;
16741                 while (emptycount--)
16742                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16743                 break;
16744 #if(BOARD_FILES >= 10)
16745             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16746                 p++; emptycount=10;
16747                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16748                 while (emptycount--)
16749                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16750 #endif
16751             } else if (isdigit(*p)) {
16752                 emptycount = *p++ - '0';
16753                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16754                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16755                 while (emptycount--)
16756                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16757             } else if (*p == '+' || isalpha(*p)) {
16758                 if (j >= gameInfo.boardWidth) return FALSE;
16759                 if(*p=='+') {
16760                     piece = CharToPiece(*++p);
16761                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16762                     piece = (ChessSquare) (PROMOTED piece ); p++;
16763                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16764                 } else piece = CharToPiece(*p++);
16765
16766                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16767                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16768                     piece = (ChessSquare) (PROMOTED piece);
16769                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16770                     p++;
16771                 }
16772                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16773             } else {
16774                 return FALSE;
16775             }
16776         }
16777     }
16778     while (*p == '/' || *p == ' ') p++;
16779
16780     /* [HGM] look for Crazyhouse holdings here */
16781     while(*p==' ') p++;
16782     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16783         if(*p == '[') p++;
16784         if(*p == '-' ) p++; /* empty holdings */ else {
16785             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16786             /* if we would allow FEN reading to set board size, we would   */
16787             /* have to add holdings and shift the board read so far here   */
16788             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16789                 p++;
16790                 if((int) piece >= (int) BlackPawn ) {
16791                     i = (int)piece - (int)BlackPawn;
16792                     i = PieceToNumber((ChessSquare)i);
16793                     if( i >= gameInfo.holdingsSize ) return FALSE;
16794                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16795                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16796                 } else {
16797                     i = (int)piece - (int)WhitePawn;
16798                     i = PieceToNumber((ChessSquare)i);
16799                     if( i >= gameInfo.holdingsSize ) return FALSE;
16800                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16801                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16802                 }
16803             }
16804         }
16805         if(*p == ']') p++;
16806     }
16807
16808     while(*p == ' ') p++;
16809
16810     /* Active color */
16811     c = *p++;
16812     if(appData.colorNickNames) {
16813       if( c == appData.colorNickNames[0] ) c = 'w'; else
16814       if( c == appData.colorNickNames[1] ) c = 'b';
16815     }
16816     switch (c) {
16817       case 'w':
16818         *blackPlaysFirst = FALSE;
16819         break;
16820       case 'b':
16821         *blackPlaysFirst = TRUE;
16822         break;
16823       default:
16824         return FALSE;
16825     }
16826
16827     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16828     /* return the extra info in global variiables             */
16829
16830     /* set defaults in case FEN is incomplete */
16831     board[EP_STATUS] = EP_UNKNOWN;
16832     for(i=0; i<nrCastlingRights; i++ ) {
16833         board[CASTLING][i] =
16834             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16835     }   /* assume possible unless obviously impossible */
16836     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16837     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16838     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16839                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16840     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16841     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16842     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16843                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16844     FENrulePlies = 0;
16845
16846     while(*p==' ') p++;
16847     if(nrCastlingRights) {
16848       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16849           /* castling indicator present, so default becomes no castlings */
16850           for(i=0; i<nrCastlingRights; i++ ) {
16851                  board[CASTLING][i] = NoRights;
16852           }
16853       }
16854       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16855              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16856              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16857              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16858         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16859
16860         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16861             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16862             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16863         }
16864         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16865             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16866         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16867                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16868         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16869                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16870         switch(c) {
16871           case'K':
16872               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16873               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16874               board[CASTLING][2] = whiteKingFile;
16875               break;
16876           case'Q':
16877               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16878               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16879               board[CASTLING][2] = whiteKingFile;
16880               break;
16881           case'k':
16882               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16883               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16884               board[CASTLING][5] = blackKingFile;
16885               break;
16886           case'q':
16887               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16888               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16889               board[CASTLING][5] = blackKingFile;
16890           case '-':
16891               break;
16892           default: /* FRC castlings */
16893               if(c >= 'a') { /* black rights */
16894                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16895                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16896                   if(i == BOARD_RGHT) break;
16897                   board[CASTLING][5] = i;
16898                   c -= AAA;
16899                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16900                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16901                   if(c > i)
16902                       board[CASTLING][3] = c;
16903                   else
16904                       board[CASTLING][4] = c;
16905               } else { /* white rights */
16906                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16907                     if(board[0][i] == WhiteKing) break;
16908                   if(i == BOARD_RGHT) break;
16909                   board[CASTLING][2] = i;
16910                   c -= AAA - 'a' + 'A';
16911                   if(board[0][c] >= WhiteKing) break;
16912                   if(c > i)
16913                       board[CASTLING][0] = c;
16914                   else
16915                       board[CASTLING][1] = c;
16916               }
16917         }
16918       }
16919       for(i=0; i<nrCastlingRights; i++)
16920         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16921     if (appData.debugMode) {
16922         fprintf(debugFP, "FEN castling rights:");
16923         for(i=0; i<nrCastlingRights; i++)
16924         fprintf(debugFP, " %d", board[CASTLING][i]);
16925         fprintf(debugFP, "\n");
16926     }
16927
16928       while(*p==' ') p++;
16929     }
16930
16931     /* read e.p. field in games that know e.p. capture */
16932     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16933        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16934       if(*p=='-') {
16935         p++; board[EP_STATUS] = EP_NONE;
16936       } else {
16937          char c = *p++ - AAA;
16938
16939          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16940          if(*p >= '0' && *p <='9') p++;
16941          board[EP_STATUS] = c;
16942       }
16943     }
16944
16945
16946     if(sscanf(p, "%d", &i) == 1) {
16947         FENrulePlies = i; /* 50-move ply counter */
16948         /* (The move number is still ignored)    */
16949     }
16950
16951     return TRUE;
16952 }
16953
16954 void
16955 EditPositionPasteFEN (char *fen)
16956 {
16957   if (fen != NULL) {
16958     Board initial_position;
16959
16960     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16961       DisplayError(_("Bad FEN position in clipboard"), 0);
16962       return ;
16963     } else {
16964       int savedBlackPlaysFirst = blackPlaysFirst;
16965       EditPositionEvent();
16966       blackPlaysFirst = savedBlackPlaysFirst;
16967       CopyBoard(boards[0], initial_position);
16968       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16969       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16970       DisplayBothClocks();
16971       DrawPosition(FALSE, boards[currentMove]);
16972     }
16973   }
16974 }
16975
16976 static char cseq[12] = "\\   ";
16977
16978 Boolean
16979 set_cont_sequence (char *new_seq)
16980 {
16981     int len;
16982     Boolean ret;
16983
16984     // handle bad attempts to set the sequence
16985         if (!new_seq)
16986                 return 0; // acceptable error - no debug
16987
16988     len = strlen(new_seq);
16989     ret = (len > 0) && (len < sizeof(cseq));
16990     if (ret)
16991       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16992     else if (appData.debugMode)
16993       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16994     return ret;
16995 }
16996
16997 /*
16998     reformat a source message so words don't cross the width boundary.  internal
16999     newlines are not removed.  returns the wrapped size (no null character unless
17000     included in source message).  If dest is NULL, only calculate the size required
17001     for the dest buffer.  lp argument indicats line position upon entry, and it's
17002     passed back upon exit.
17003 */
17004 int
17005 wrap (char *dest, char *src, int count, int width, int *lp)
17006 {
17007     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17008
17009     cseq_len = strlen(cseq);
17010     old_line = line = *lp;
17011     ansi = len = clen = 0;
17012
17013     for (i=0; i < count; i++)
17014     {
17015         if (src[i] == '\033')
17016             ansi = 1;
17017
17018         // if we hit the width, back up
17019         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17020         {
17021             // store i & len in case the word is too long
17022             old_i = i, old_len = len;
17023
17024             // find the end of the last word
17025             while (i && src[i] != ' ' && src[i] != '\n')
17026             {
17027                 i--;
17028                 len--;
17029             }
17030
17031             // word too long?  restore i & len before splitting it
17032             if ((old_i-i+clen) >= width)
17033             {
17034                 i = old_i;
17035                 len = old_len;
17036             }
17037
17038             // extra space?
17039             if (i && src[i-1] == ' ')
17040                 len--;
17041
17042             if (src[i] != ' ' && src[i] != '\n')
17043             {
17044                 i--;
17045                 if (len)
17046                     len--;
17047             }
17048
17049             // now append the newline and continuation sequence
17050             if (dest)
17051                 dest[len] = '\n';
17052             len++;
17053             if (dest)
17054                 strncpy(dest+len, cseq, cseq_len);
17055             len += cseq_len;
17056             line = cseq_len;
17057             clen = cseq_len;
17058             continue;
17059         }
17060
17061         if (dest)
17062             dest[len] = src[i];
17063         len++;
17064         if (!ansi)
17065             line++;
17066         if (src[i] == '\n')
17067             line = 0;
17068         if (src[i] == 'm')
17069             ansi = 0;
17070     }
17071     if (dest && appData.debugMode)
17072     {
17073         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17074             count, width, line, len, *lp);
17075         show_bytes(debugFP, src, count);
17076         fprintf(debugFP, "\ndest: ");
17077         show_bytes(debugFP, dest, len);
17078         fprintf(debugFP, "\n");
17079     }
17080     *lp = dest ? line : old_line;
17081
17082     return len;
17083 }
17084
17085 // [HGM] vari: routines for shelving variations
17086 Boolean modeRestore = FALSE;
17087
17088 void
17089 PushInner (int firstMove, int lastMove)
17090 {
17091         int i, j, nrMoves = lastMove - firstMove;
17092
17093         // push current tail of game on stack
17094         savedResult[storedGames] = gameInfo.result;
17095         savedDetails[storedGames] = gameInfo.resultDetails;
17096         gameInfo.resultDetails = NULL;
17097         savedFirst[storedGames] = firstMove;
17098         savedLast [storedGames] = lastMove;
17099         savedFramePtr[storedGames] = framePtr;
17100         framePtr -= nrMoves; // reserve space for the boards
17101         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17102             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17103             for(j=0; j<MOVE_LEN; j++)
17104                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17105             for(j=0; j<2*MOVE_LEN; j++)
17106                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17107             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17108             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17109             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17110             pvInfoList[firstMove+i-1].depth = 0;
17111             commentList[framePtr+i] = commentList[firstMove+i];
17112             commentList[firstMove+i] = NULL;
17113         }
17114
17115         storedGames++;
17116         forwardMostMove = firstMove; // truncate game so we can start variation
17117 }
17118
17119 void
17120 PushTail (int firstMove, int lastMove)
17121 {
17122         if(appData.icsActive) { // only in local mode
17123                 forwardMostMove = currentMove; // mimic old ICS behavior
17124                 return;
17125         }
17126         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17127
17128         PushInner(firstMove, lastMove);
17129         if(storedGames == 1) GreyRevert(FALSE);
17130         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17131 }
17132
17133 void
17134 PopInner (Boolean annotate)
17135 {
17136         int i, j, nrMoves;
17137         char buf[8000], moveBuf[20];
17138
17139         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17140         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17141         nrMoves = savedLast[storedGames] - currentMove;
17142         if(annotate) {
17143                 int cnt = 10;
17144                 if(!WhiteOnMove(currentMove))
17145                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17146                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17147                 for(i=currentMove; i<forwardMostMove; i++) {
17148                         if(WhiteOnMove(i))
17149                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17150                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17151                         strcat(buf, moveBuf);
17152                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17153                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17154                 }
17155                 strcat(buf, ")");
17156         }
17157         for(i=1; i<=nrMoves; i++) { // copy last variation back
17158             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17159             for(j=0; j<MOVE_LEN; j++)
17160                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17161             for(j=0; j<2*MOVE_LEN; j++)
17162                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17163             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17164             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17165             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17166             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17167             commentList[currentMove+i] = commentList[framePtr+i];
17168             commentList[framePtr+i] = NULL;
17169         }
17170         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17171         framePtr = savedFramePtr[storedGames];
17172         gameInfo.result = savedResult[storedGames];
17173         if(gameInfo.resultDetails != NULL) {
17174             free(gameInfo.resultDetails);
17175       }
17176         gameInfo.resultDetails = savedDetails[storedGames];
17177         forwardMostMove = currentMove + nrMoves;
17178 }
17179
17180 Boolean
17181 PopTail (Boolean annotate)
17182 {
17183         if(appData.icsActive) return FALSE; // only in local mode
17184         if(!storedGames) return FALSE; // sanity
17185         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17186
17187         PopInner(annotate);
17188         if(currentMove < forwardMostMove) ForwardEvent(); else
17189         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17190
17191         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17192         return TRUE;
17193 }
17194
17195 void
17196 CleanupTail ()
17197 {       // remove all shelved variations
17198         int i;
17199         for(i=0; i<storedGames; i++) {
17200             if(savedDetails[i])
17201                 free(savedDetails[i]);
17202             savedDetails[i] = NULL;
17203         }
17204         for(i=framePtr; i<MAX_MOVES; i++) {
17205                 if(commentList[i]) free(commentList[i]);
17206                 commentList[i] = NULL;
17207         }
17208         framePtr = MAX_MOVES-1;
17209         storedGames = 0;
17210 }
17211
17212 void
17213 LoadVariation (int index, char *text)
17214 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17215         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17216         int level = 0, move;
17217
17218         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17219         // first find outermost bracketing variation
17220         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17221             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17222                 if(*p == '{') wait = '}'; else
17223                 if(*p == '[') wait = ']'; else
17224                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17225                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17226             }
17227             if(*p == wait) wait = NULLCHAR; // closing ]} found
17228             p++;
17229         }
17230         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17231         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17232         end[1] = NULLCHAR; // clip off comment beyond variation
17233         ToNrEvent(currentMove-1);
17234         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17235         // kludge: use ParsePV() to append variation to game
17236         move = currentMove;
17237         ParsePV(start, TRUE, TRUE);
17238         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17239         ClearPremoveHighlights();
17240         CommentPopDown();
17241         ToNrEvent(currentMove+1);
17242 }
17243