Implement automatic partner observe
[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      board[HOLDINGS_SET] = 0;
2450      gameInfo.boardWidth  = newWidth;
2451      gameInfo.boardHeight = newHeight;
2452      gameInfo.holdingsWidth = newHoldingsWidth;
2453      gameInfo.variant = newVariant;
2454      InitDrawingSizes(-2, 0);
2455    } else gameInfo.variant = newVariant;
2456    CopyBoard(oldBoard, board);   // remember correctly formatted board
2457      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2458    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2459 }
2460
2461 static int loggedOn = FALSE;
2462
2463 /*-- Game start info cache: --*/
2464 int gs_gamenum;
2465 char gs_kind[MSG_SIZ];
2466 static char player1Name[128] = "";
2467 static char player2Name[128] = "";
2468 static char cont_seq[] = "\n\\   ";
2469 static int player1Rating = -1;
2470 static int player2Rating = -1;
2471 /*----------------------------*/
2472
2473 ColorClass curColor = ColorNormal;
2474 int suppressKibitz = 0;
2475
2476 // [HGM] seekgraph
2477 Boolean soughtPending = FALSE;
2478 Boolean seekGraphUp;
2479 #define MAX_SEEK_ADS 200
2480 #define SQUARE 0x80
2481 char *seekAdList[MAX_SEEK_ADS];
2482 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2483 float tcList[MAX_SEEK_ADS];
2484 char colorList[MAX_SEEK_ADS];
2485 int nrOfSeekAds = 0;
2486 int minRating = 1010, maxRating = 2800;
2487 int hMargin = 10, vMargin = 20, h, w;
2488 extern int squareSize, lineGap;
2489
2490 void
2491 PlotSeekAd (int i)
2492 {
2493         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2494         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2495         if(r < minRating+100 && r >=0 ) r = minRating+100;
2496         if(r > maxRating) r = maxRating;
2497         if(tc < 1.) tc = 1.;
2498         if(tc > 95.) tc = 95.;
2499         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2500         y = ((double)r - minRating)/(maxRating - minRating)
2501             * (h-vMargin-squareSize/8-1) + vMargin;
2502         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2503         if(strstr(seekAdList[i], " u ")) color = 1;
2504         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2505            !strstr(seekAdList[i], "bullet") &&
2506            !strstr(seekAdList[i], "blitz") &&
2507            !strstr(seekAdList[i], "standard") ) color = 2;
2508         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2509         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2510 }
2511
2512 void
2513 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2514 {
2515         char buf[MSG_SIZ], *ext = "";
2516         VariantClass v = StringToVariant(type);
2517         if(strstr(type, "wild")) {
2518             ext = type + 4; // append wild number
2519             if(v == VariantFischeRandom) type = "chess960"; else
2520             if(v == VariantLoadable) type = "setup"; else
2521             type = VariantName(v);
2522         }
2523         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2524         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2525             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2526             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2527             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2528             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2529             seekNrList[nrOfSeekAds] = nr;
2530             zList[nrOfSeekAds] = 0;
2531             seekAdList[nrOfSeekAds++] = StrSave(buf);
2532             if(plot) PlotSeekAd(nrOfSeekAds-1);
2533         }
2534 }
2535
2536 void
2537 EraseSeekDot (int i)
2538 {
2539     int x = xList[i], y = yList[i], d=squareSize/4, k;
2540     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2541     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2542     // now replot every dot that overlapped
2543     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2544         int xx = xList[k], yy = yList[k];
2545         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2546             DrawSeekDot(xx, yy, colorList[k]);
2547     }
2548 }
2549
2550 void
2551 RemoveSeekAd (int nr)
2552 {
2553         int i;
2554         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2555             EraseSeekDot(i);
2556             if(seekAdList[i]) free(seekAdList[i]);
2557             seekAdList[i] = seekAdList[--nrOfSeekAds];
2558             seekNrList[i] = seekNrList[nrOfSeekAds];
2559             ratingList[i] = ratingList[nrOfSeekAds];
2560             colorList[i]  = colorList[nrOfSeekAds];
2561             tcList[i] = tcList[nrOfSeekAds];
2562             xList[i]  = xList[nrOfSeekAds];
2563             yList[i]  = yList[nrOfSeekAds];
2564             zList[i]  = zList[nrOfSeekAds];
2565             seekAdList[nrOfSeekAds] = NULL;
2566             break;
2567         }
2568 }
2569
2570 Boolean
2571 MatchSoughtLine (char *line)
2572 {
2573     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2574     int nr, base, inc, u=0; char dummy;
2575
2576     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2577        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2578        (u=1) &&
2579        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2580         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2581         // match: compact and save the line
2582         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2583         return TRUE;
2584     }
2585     return FALSE;
2586 }
2587
2588 int
2589 DrawSeekGraph ()
2590 {
2591     int i;
2592     if(!seekGraphUp) return FALSE;
2593     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2594     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2595
2596     DrawSeekBackground(0, 0, w, h);
2597     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2598     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2599     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2600         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2601         yy = h-1-yy;
2602         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2603         if(i%500 == 0) {
2604             char buf[MSG_SIZ];
2605             snprintf(buf, MSG_SIZ, "%d", i);
2606             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2607         }
2608     }
2609     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2610     for(i=1; i<100; i+=(i<10?1:5)) {
2611         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2612         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2613         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2614             char buf[MSG_SIZ];
2615             snprintf(buf, MSG_SIZ, "%d", i);
2616             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2617         }
2618     }
2619     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2620     return TRUE;
2621 }
2622
2623 int
2624 SeekGraphClick (ClickType click, int x, int y, int moving)
2625 {
2626     static int lastDown = 0, displayed = 0, lastSecond;
2627     if(y < 0) return FALSE;
2628     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2629         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2630         if(!seekGraphUp) return FALSE;
2631         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2632         DrawPosition(TRUE, NULL);
2633         return TRUE;
2634     }
2635     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2636         if(click == Release || moving) return FALSE;
2637         nrOfSeekAds = 0;
2638         soughtPending = TRUE;
2639         SendToICS(ics_prefix);
2640         SendToICS("sought\n"); // should this be "sought all"?
2641     } else { // issue challenge based on clicked ad
2642         int dist = 10000; int i, closest = 0, second = 0;
2643         for(i=0; i<nrOfSeekAds; i++) {
2644             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2645             if(d < dist) { dist = d; closest = i; }
2646             second += (d - zList[i] < 120); // count in-range ads
2647             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2648         }
2649         if(dist < 120) {
2650             char buf[MSG_SIZ];
2651             second = (second > 1);
2652             if(displayed != closest || second != lastSecond) {
2653                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2654                 lastSecond = second; displayed = closest;
2655             }
2656             if(click == Press) {
2657                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2658                 lastDown = closest;
2659                 return TRUE;
2660             } // on press 'hit', only show info
2661             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2662             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2663             SendToICS(ics_prefix);
2664             SendToICS(buf);
2665             return TRUE; // let incoming board of started game pop down the graph
2666         } else if(click == Release) { // release 'miss' is ignored
2667             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2668             if(moving == 2) { // right up-click
2669                 nrOfSeekAds = 0; // refresh graph
2670                 soughtPending = TRUE;
2671                 SendToICS(ics_prefix);
2672                 SendToICS("sought\n"); // should this be "sought all"?
2673             }
2674             return TRUE;
2675         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2676         // press miss or release hit 'pop down' seek graph
2677         seekGraphUp = FALSE;
2678         DrawPosition(TRUE, NULL);
2679     }
2680     return TRUE;
2681 }
2682
2683 void
2684 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2685 {
2686 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2687 #define STARTED_NONE 0
2688 #define STARTED_MOVES 1
2689 #define STARTED_BOARD 2
2690 #define STARTED_OBSERVE 3
2691 #define STARTED_HOLDINGS 4
2692 #define STARTED_CHATTER 5
2693 #define STARTED_COMMENT 6
2694 #define STARTED_MOVES_NOHIDE 7
2695
2696     static int started = STARTED_NONE;
2697     static char parse[20000];
2698     static int parse_pos = 0;
2699     static char buf[BUF_SIZE + 1];
2700     static int firstTime = TRUE, intfSet = FALSE;
2701     static ColorClass prevColor = ColorNormal;
2702     static int savingComment = FALSE;
2703     static int cmatch = 0; // continuation sequence match
2704     char *bp;
2705     char str[MSG_SIZ];
2706     int i, oldi;
2707     int buf_len;
2708     int next_out;
2709     int tkind;
2710     int backup;    /* [DM] For zippy color lines */
2711     char *p;
2712     char talker[MSG_SIZ]; // [HGM] chat
2713     int channel;
2714
2715     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2716
2717     if (appData.debugMode) {
2718       if (!error) {
2719         fprintf(debugFP, "<ICS: ");
2720         show_bytes(debugFP, data, count);
2721         fprintf(debugFP, "\n");
2722       }
2723     }
2724
2725     if (appData.debugMode) { int f = forwardMostMove;
2726         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2727                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2728                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2729     }
2730     if (count > 0) {
2731         /* If last read ended with a partial line that we couldn't parse,
2732            prepend it to the new read and try again. */
2733         if (leftover_len > 0) {
2734             for (i=0; i<leftover_len; i++)
2735               buf[i] = buf[leftover_start + i];
2736         }
2737
2738     /* copy new characters into the buffer */
2739     bp = buf + leftover_len;
2740     buf_len=leftover_len;
2741     for (i=0; i<count; i++)
2742     {
2743         // ignore these
2744         if (data[i] == '\r')
2745             continue;
2746
2747         // join lines split by ICS?
2748         if (!appData.noJoin)
2749         {
2750             /*
2751                 Joining just consists of finding matches against the
2752                 continuation sequence, and discarding that sequence
2753                 if found instead of copying it.  So, until a match
2754                 fails, there's nothing to do since it might be the
2755                 complete sequence, and thus, something we don't want
2756                 copied.
2757             */
2758             if (data[i] == cont_seq[cmatch])
2759             {
2760                 cmatch++;
2761                 if (cmatch == strlen(cont_seq))
2762                 {
2763                     cmatch = 0; // complete match.  just reset the counter
2764
2765                     /*
2766                         it's possible for the ICS to not include the space
2767                         at the end of the last word, making our [correct]
2768                         join operation fuse two separate words.  the server
2769                         does this when the space occurs at the width setting.
2770                     */
2771                     if (!buf_len || buf[buf_len-1] != ' ')
2772                     {
2773                         *bp++ = ' ';
2774                         buf_len++;
2775                     }
2776                 }
2777                 continue;
2778             }
2779             else if (cmatch)
2780             {
2781                 /*
2782                     match failed, so we have to copy what matched before
2783                     falling through and copying this character.  In reality,
2784                     this will only ever be just the newline character, but
2785                     it doesn't hurt to be precise.
2786                 */
2787                 strncpy(bp, cont_seq, cmatch);
2788                 bp += cmatch;
2789                 buf_len += cmatch;
2790                 cmatch = 0;
2791             }
2792         }
2793
2794         // copy this char
2795         *bp++ = data[i];
2796         buf_len++;
2797     }
2798
2799         buf[buf_len] = NULLCHAR;
2800 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2801         next_out = 0;
2802         leftover_start = 0;
2803
2804         i = 0;
2805         while (i < buf_len) {
2806             /* Deal with part of the TELNET option negotiation
2807                protocol.  We refuse to do anything beyond the
2808                defaults, except that we allow the WILL ECHO option,
2809                which ICS uses to turn off password echoing when we are
2810                directly connected to it.  We reject this option
2811                if localLineEditing mode is on (always on in xboard)
2812                and we are talking to port 23, which might be a real
2813                telnet server that will try to keep WILL ECHO on permanently.
2814              */
2815             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2816                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2817                 unsigned char option;
2818                 oldi = i;
2819                 switch ((unsigned char) buf[++i]) {
2820                   case TN_WILL:
2821                     if (appData.debugMode)
2822                       fprintf(debugFP, "\n<WILL ");
2823                     switch (option = (unsigned char) buf[++i]) {
2824                       case TN_ECHO:
2825                         if (appData.debugMode)
2826                           fprintf(debugFP, "ECHO ");
2827                         /* Reply only if this is a change, according
2828                            to the protocol rules. */
2829                         if (remoteEchoOption) break;
2830                         if (appData.localLineEditing &&
2831                             atoi(appData.icsPort) == TN_PORT) {
2832                             TelnetRequest(TN_DONT, TN_ECHO);
2833                         } else {
2834                             EchoOff();
2835                             TelnetRequest(TN_DO, TN_ECHO);
2836                             remoteEchoOption = TRUE;
2837                         }
2838                         break;
2839                       default:
2840                         if (appData.debugMode)
2841                           fprintf(debugFP, "%d ", option);
2842                         /* Whatever this is, we don't want it. */
2843                         TelnetRequest(TN_DONT, option);
2844                         break;
2845                     }
2846                     break;
2847                   case TN_WONT:
2848                     if (appData.debugMode)
2849                       fprintf(debugFP, "\n<WONT ");
2850                     switch (option = (unsigned char) buf[++i]) {
2851                       case TN_ECHO:
2852                         if (appData.debugMode)
2853                           fprintf(debugFP, "ECHO ");
2854                         /* Reply only if this is a change, according
2855                            to the protocol rules. */
2856                         if (!remoteEchoOption) break;
2857                         EchoOn();
2858                         TelnetRequest(TN_DONT, TN_ECHO);
2859                         remoteEchoOption = FALSE;
2860                         break;
2861                       default:
2862                         if (appData.debugMode)
2863                           fprintf(debugFP, "%d ", (unsigned char) option);
2864                         /* Whatever this is, it must already be turned
2865                            off, because we never agree to turn on
2866                            anything non-default, so according to the
2867                            protocol rules, we don't reply. */
2868                         break;
2869                     }
2870                     break;
2871                   case TN_DO:
2872                     if (appData.debugMode)
2873                       fprintf(debugFP, "\n<DO ");
2874                     switch (option = (unsigned char) buf[++i]) {
2875                       default:
2876                         /* Whatever this is, we refuse to do it. */
2877                         if (appData.debugMode)
2878                           fprintf(debugFP, "%d ", option);
2879                         TelnetRequest(TN_WONT, option);
2880                         break;
2881                     }
2882                     break;
2883                   case TN_DONT:
2884                     if (appData.debugMode)
2885                       fprintf(debugFP, "\n<DONT ");
2886                     switch (option = (unsigned char) buf[++i]) {
2887                       default:
2888                         if (appData.debugMode)
2889                           fprintf(debugFP, "%d ", option);
2890                         /* Whatever this is, we are already not doing
2891                            it, because we never agree to do anything
2892                            non-default, so according to the protocol
2893                            rules, we don't reply. */
2894                         break;
2895                     }
2896                     break;
2897                   case TN_IAC:
2898                     if (appData.debugMode)
2899                       fprintf(debugFP, "\n<IAC ");
2900                     /* Doubled IAC; pass it through */
2901                     i--;
2902                     break;
2903                   default:
2904                     if (appData.debugMode)
2905                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2906                     /* Drop all other telnet commands on the floor */
2907                     break;
2908                 }
2909                 if (oldi > next_out)
2910                   SendToPlayer(&buf[next_out], oldi - next_out);
2911                 if (++i > next_out)
2912                   next_out = i;
2913                 continue;
2914             }
2915
2916             /* OK, this at least will *usually* work */
2917             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2918                 loggedOn = TRUE;
2919             }
2920
2921             if (loggedOn && !intfSet) {
2922                 if (ics_type == ICS_ICC) {
2923                   snprintf(str, MSG_SIZ,
2924                           "/set-quietly interface %s\n/set-quietly style 12\n",
2925                           programVersion);
2926                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2927                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2928                 } else if (ics_type == ICS_CHESSNET) {
2929                   snprintf(str, MSG_SIZ, "/style 12\n");
2930                 } else {
2931                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2932                   strcat(str, programVersion);
2933                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2934                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2935                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2936 #ifdef WIN32
2937                   strcat(str, "$iset nohighlight 1\n");
2938 #endif
2939                   strcat(str, "$iset lock 1\n$style 12\n");
2940                 }
2941                 SendToICS(str);
2942                 NotifyFrontendLogin();
2943                 intfSet = TRUE;
2944             }
2945
2946             if (started == STARTED_COMMENT) {
2947                 /* Accumulate characters in comment */
2948                 parse[parse_pos++] = buf[i];
2949                 if (buf[i] == '\n') {
2950                     parse[parse_pos] = NULLCHAR;
2951                     if(chattingPartner>=0) {
2952                         char mess[MSG_SIZ];
2953                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2954                         OutputChatMessage(chattingPartner, mess);
2955                         chattingPartner = -1;
2956                         next_out = i+1; // [HGM] suppress printing in ICS window
2957                     } else
2958                     if(!suppressKibitz) // [HGM] kibitz
2959                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2960                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2961                         int nrDigit = 0, nrAlph = 0, j;
2962                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2963                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2964                         parse[parse_pos] = NULLCHAR;
2965                         // try to be smart: if it does not look like search info, it should go to
2966                         // ICS interaction window after all, not to engine-output window.
2967                         for(j=0; j<parse_pos; j++) { // count letters and digits
2968                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2969                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2970                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2971                         }
2972                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2973                             int depth=0; float score;
2974                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2975                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2976                                 pvInfoList[forwardMostMove-1].depth = depth;
2977                                 pvInfoList[forwardMostMove-1].score = 100*score;
2978                             }
2979                             OutputKibitz(suppressKibitz, parse);
2980                         } else {
2981                             char tmp[MSG_SIZ];
2982                             if(gameMode == IcsObserving) // restore original ICS messages
2983                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2984                             else
2985                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2986                             SendToPlayer(tmp, strlen(tmp));
2987                         }
2988                         next_out = i+1; // [HGM] suppress printing in ICS window
2989                     }
2990                     started = STARTED_NONE;
2991                 } else {
2992                     /* Don't match patterns against characters in comment */
2993                     i++;
2994                     continue;
2995                 }
2996             }
2997             if (started == STARTED_CHATTER) {
2998                 if (buf[i] != '\n') {
2999                     /* Don't match patterns against characters in chatter */
3000                     i++;
3001                     continue;
3002                 }
3003                 started = STARTED_NONE;
3004                 if(suppressKibitz) next_out = i+1;
3005             }
3006
3007             /* Kludge to deal with rcmd protocol */
3008             if (firstTime && looking_at(buf, &i, "\001*")) {
3009                 DisplayFatalError(&buf[1], 0, 1);
3010                 continue;
3011             } else {
3012                 firstTime = FALSE;
3013             }
3014
3015             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3016                 ics_type = ICS_ICC;
3017                 ics_prefix = "/";
3018                 if (appData.debugMode)
3019                   fprintf(debugFP, "ics_type %d\n", ics_type);
3020                 continue;
3021             }
3022             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3023                 ics_type = ICS_FICS;
3024                 ics_prefix = "$";
3025                 if (appData.debugMode)
3026                   fprintf(debugFP, "ics_type %d\n", ics_type);
3027                 continue;
3028             }
3029             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3030                 ics_type = ICS_CHESSNET;
3031                 ics_prefix = "/";
3032                 if (appData.debugMode)
3033                   fprintf(debugFP, "ics_type %d\n", ics_type);
3034                 continue;
3035             }
3036
3037             if (!loggedOn &&
3038                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3039                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3040                  looking_at(buf, &i, "will be \"*\""))) {
3041               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3042               continue;
3043             }
3044
3045             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3046               char buf[MSG_SIZ];
3047               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3048               DisplayIcsInteractionTitle(buf);
3049               have_set_title = TRUE;
3050             }
3051
3052             /* skip finger notes */
3053             if (started == STARTED_NONE &&
3054                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3055                  (buf[i] == '1' && buf[i+1] == '0')) &&
3056                 buf[i+2] == ':' && buf[i+3] == ' ') {
3057               started = STARTED_CHATTER;
3058               i += 3;
3059               continue;
3060             }
3061
3062             oldi = i;
3063             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3064             if(appData.seekGraph) {
3065                 if(soughtPending && MatchSoughtLine(buf+i)) {
3066                     i = strstr(buf+i, "rated") - buf;
3067                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3068                     next_out = leftover_start = i;
3069                     started = STARTED_CHATTER;
3070                     suppressKibitz = TRUE;
3071                     continue;
3072                 }
3073                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3074                         && looking_at(buf, &i, "* ads displayed")) {
3075                     soughtPending = FALSE;
3076                     seekGraphUp = TRUE;
3077                     DrawSeekGraph();
3078                     continue;
3079                 }
3080                 if(appData.autoRefresh) {
3081                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3082                         int s = (ics_type == ICS_ICC); // ICC format differs
3083                         if(seekGraphUp)
3084                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3085                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3086                         looking_at(buf, &i, "*% "); // eat prompt
3087                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3088                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3089                         next_out = i; // suppress
3090                         continue;
3091                     }
3092                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3093                         char *p = star_match[0];
3094                         while(*p) {
3095                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3096                             while(*p && *p++ != ' '); // next
3097                         }
3098                         looking_at(buf, &i, "*% "); // eat prompt
3099                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3100                         next_out = i;
3101                         continue;
3102                     }
3103                 }
3104             }
3105
3106             /* skip formula vars */
3107             if (started == STARTED_NONE &&
3108                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3109               started = STARTED_CHATTER;
3110               i += 3;
3111               continue;
3112             }
3113
3114             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3115             if (appData.autoKibitz && started == STARTED_NONE &&
3116                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3117                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3118                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3119                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3120                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3121                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3122                         suppressKibitz = TRUE;
3123                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3124                         next_out = i;
3125                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3126                                 && (gameMode == IcsPlayingWhite)) ||
3127                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3128                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3129                             started = STARTED_CHATTER; // own kibitz we simply discard
3130                         else {
3131                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3132                             parse_pos = 0; parse[0] = NULLCHAR;
3133                             savingComment = TRUE;
3134                             suppressKibitz = gameMode != IcsObserving ? 2 :
3135                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3136                         }
3137                         continue;
3138                 } else
3139                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3140                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3141                          && atoi(star_match[0])) {
3142                     // suppress the acknowledgements of our own autoKibitz
3143                     char *p;
3144                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3145                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3146                     SendToPlayer(star_match[0], strlen(star_match[0]));
3147                     if(looking_at(buf, &i, "*% ")) // eat prompt
3148                         suppressKibitz = FALSE;
3149                     next_out = i;
3150                     continue;
3151                 }
3152             } // [HGM] kibitz: end of patch
3153
3154             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3155
3156             // [HGM] chat: intercept tells by users for which we have an open chat window
3157             channel = -1;
3158             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3159                                            looking_at(buf, &i, "* whispers:") ||
3160                                            looking_at(buf, &i, "* kibitzes:") ||
3161                                            looking_at(buf, &i, "* shouts:") ||
3162                                            looking_at(buf, &i, "* c-shouts:") ||
3163                                            looking_at(buf, &i, "--> * ") ||
3164                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3165                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3166                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3167                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3168                 int p;
3169                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3170                 chattingPartner = -1;
3171
3172                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3173                 for(p=0; p<MAX_CHAT; p++) {
3174                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3175                     talker[0] = '['; strcat(talker, "] ");
3176                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3177                     chattingPartner = p; break;
3178                     }
3179                 } else
3180                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3181                 for(p=0; p<MAX_CHAT; p++) {
3182                     if(!strcmp("kibitzes", chatPartner[p])) {
3183                         talker[0] = '['; strcat(talker, "] ");
3184                         chattingPartner = p; break;
3185                     }
3186                 } else
3187                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3188                 for(p=0; p<MAX_CHAT; p++) {
3189                     if(!strcmp("whispers", chatPartner[p])) {
3190                         talker[0] = '['; strcat(talker, "] ");
3191                         chattingPartner = p; break;
3192                     }
3193                 } else
3194                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3195                   if(buf[i-8] == '-' && buf[i-3] == 't')
3196                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3197                     if(!strcmp("c-shouts", chatPartner[p])) {
3198                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3199                         chattingPartner = p; break;
3200                     }
3201                   }
3202                   if(chattingPartner < 0)
3203                   for(p=0; p<MAX_CHAT; p++) {
3204                     if(!strcmp("shouts", chatPartner[p])) {
3205                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3206                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3207                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3208                         chattingPartner = p; break;
3209                     }
3210                   }
3211                 }
3212                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3213                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3214                     talker[0] = 0; Colorize(ColorTell, FALSE);
3215                     chattingPartner = p; break;
3216                 }
3217                 if(chattingPartner<0) i = oldi; else {
3218                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3219                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3220                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3221                     started = STARTED_COMMENT;
3222                     parse_pos = 0; parse[0] = NULLCHAR;
3223                     savingComment = 3 + chattingPartner; // counts as TRUE
3224                     suppressKibitz = TRUE;
3225                     continue;
3226                 }
3227             } // [HGM] chat: end of patch
3228
3229           backup = i;
3230             if (appData.zippyTalk || appData.zippyPlay) {
3231                 /* [DM] Backup address for color zippy lines */
3232 #if ZIPPY
3233                if (loggedOn == TRUE)
3234                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3235                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3236 #endif
3237             } // [DM] 'else { ' deleted
3238                 if (
3239                     /* Regular tells and says */
3240                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3241                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3242                     looking_at(buf, &i, "* says: ") ||
3243                     /* Don't color "message" or "messages" output */
3244                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3245                     looking_at(buf, &i, "*. * at *:*: ") ||
3246                     looking_at(buf, &i, "--* (*:*): ") ||
3247                     /* Message notifications (same color as tells) */
3248                     looking_at(buf, &i, "* has left a message ") ||
3249                     looking_at(buf, &i, "* just sent you a message:\n") ||
3250                     /* Whispers and kibitzes */
3251                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3252                     looking_at(buf, &i, "* kibitzes: ") ||
3253                     /* Channel tells */
3254                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3255
3256                   if (tkind == 1 && strchr(star_match[0], ':')) {
3257                       /* Avoid "tells you:" spoofs in channels */
3258                      tkind = 3;
3259                   }
3260                   if (star_match[0][0] == NULLCHAR ||
3261                       strchr(star_match[0], ' ') ||
3262                       (tkind == 3 && strchr(star_match[1], ' '))) {
3263                     /* Reject bogus matches */
3264                     i = oldi;
3265                   } else {
3266                     if (appData.colorize) {
3267                       if (oldi > next_out) {
3268                         SendToPlayer(&buf[next_out], oldi - next_out);
3269                         next_out = oldi;
3270                       }
3271                       switch (tkind) {
3272                       case 1:
3273                         Colorize(ColorTell, FALSE);
3274                         curColor = ColorTell;
3275                         break;
3276                       case 2:
3277                         Colorize(ColorKibitz, FALSE);
3278                         curColor = ColorKibitz;
3279                         break;
3280                       case 3:
3281                         p = strrchr(star_match[1], '(');
3282                         if (p == NULL) {
3283                           p = star_match[1];
3284                         } else {
3285                           p++;
3286                         }
3287                         if (atoi(p) == 1) {
3288                           Colorize(ColorChannel1, FALSE);
3289                           curColor = ColorChannel1;
3290                         } else {
3291                           Colorize(ColorChannel, FALSE);
3292                           curColor = ColorChannel;
3293                         }
3294                         break;
3295                       case 5:
3296                         curColor = ColorNormal;
3297                         break;
3298                       }
3299                     }
3300                     if (started == STARTED_NONE && appData.autoComment &&
3301                         (gameMode == IcsObserving ||
3302                          gameMode == IcsPlayingWhite ||
3303                          gameMode == IcsPlayingBlack)) {
3304                       parse_pos = i - oldi;
3305                       memcpy(parse, &buf[oldi], parse_pos);
3306                       parse[parse_pos] = NULLCHAR;
3307                       started = STARTED_COMMENT;
3308                       savingComment = TRUE;
3309                     } else {
3310                       started = STARTED_CHATTER;
3311                       savingComment = FALSE;
3312                     }
3313                     loggedOn = TRUE;
3314                     continue;
3315                   }
3316                 }
3317
3318                 if (looking_at(buf, &i, "* s-shouts: ") ||
3319                     looking_at(buf, &i, "* c-shouts: ")) {
3320                     if (appData.colorize) {
3321                         if (oldi > next_out) {
3322                             SendToPlayer(&buf[next_out], oldi - next_out);
3323                             next_out = oldi;
3324                         }
3325                         Colorize(ColorSShout, FALSE);
3326                         curColor = ColorSShout;
3327                     }
3328                     loggedOn = TRUE;
3329                     started = STARTED_CHATTER;
3330                     continue;
3331                 }
3332
3333                 if (looking_at(buf, &i, "--->")) {
3334                     loggedOn = TRUE;
3335                     continue;
3336                 }
3337
3338                 if (looking_at(buf, &i, "* shouts: ") ||
3339                     looking_at(buf, &i, "--> ")) {
3340                     if (appData.colorize) {
3341                         if (oldi > next_out) {
3342                             SendToPlayer(&buf[next_out], oldi - next_out);
3343                             next_out = oldi;
3344                         }
3345                         Colorize(ColorShout, FALSE);
3346                         curColor = ColorShout;
3347                     }
3348                     loggedOn = TRUE;
3349                     started = STARTED_CHATTER;
3350                     continue;
3351                 }
3352
3353                 if (looking_at( buf, &i, "Challenge:")) {
3354                     if (appData.colorize) {
3355                         if (oldi > next_out) {
3356                             SendToPlayer(&buf[next_out], oldi - next_out);
3357                             next_out = oldi;
3358                         }
3359                         Colorize(ColorChallenge, FALSE);
3360                         curColor = ColorChallenge;
3361                     }
3362                     loggedOn = TRUE;
3363                     continue;
3364                 }
3365
3366                 if (looking_at(buf, &i, "* offers you") ||
3367                     looking_at(buf, &i, "* offers to be") ||
3368                     looking_at(buf, &i, "* would like to") ||
3369                     looking_at(buf, &i, "* requests to") ||
3370                     looking_at(buf, &i, "Your opponent offers") ||
3371                     looking_at(buf, &i, "Your opponent requests")) {
3372
3373                     if (appData.colorize) {
3374                         if (oldi > next_out) {
3375                             SendToPlayer(&buf[next_out], oldi - next_out);
3376                             next_out = oldi;
3377                         }
3378                         Colorize(ColorRequest, FALSE);
3379                         curColor = ColorRequest;
3380                     }
3381                     continue;
3382                 }
3383
3384                 if (looking_at(buf, &i, "* (*) seeking")) {
3385                     if (appData.colorize) {
3386                         if (oldi > next_out) {
3387                             SendToPlayer(&buf[next_out], oldi - next_out);
3388                             next_out = oldi;
3389                         }
3390                         Colorize(ColorSeek, FALSE);
3391                         curColor = ColorSeek;
3392                     }
3393                     continue;
3394             }
3395
3396           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3397
3398             if (looking_at(buf, &i, "\\   ")) {
3399                 if (prevColor != ColorNormal) {
3400                     if (oldi > next_out) {
3401                         SendToPlayer(&buf[next_out], oldi - next_out);
3402                         next_out = oldi;
3403                     }
3404                     Colorize(prevColor, TRUE);
3405                     curColor = prevColor;
3406                 }
3407                 if (savingComment) {
3408                     parse_pos = i - oldi;
3409                     memcpy(parse, &buf[oldi], parse_pos);
3410                     parse[parse_pos] = NULLCHAR;
3411                     started = STARTED_COMMENT;
3412                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3413                         chattingPartner = savingComment - 3; // kludge to remember the box
3414                 } else {
3415                     started = STARTED_CHATTER;
3416                 }
3417                 continue;
3418             }
3419
3420             if (looking_at(buf, &i, "Black Strength :") ||
3421                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3422                 looking_at(buf, &i, "<10>") ||
3423                 looking_at(buf, &i, "#@#")) {
3424                 /* Wrong board style */
3425                 loggedOn = TRUE;
3426                 SendToICS(ics_prefix);
3427                 SendToICS("set style 12\n");
3428                 SendToICS(ics_prefix);
3429                 SendToICS("refresh\n");
3430                 continue;
3431             }
3432
3433             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3434                 ICSInitScript();
3435                 have_sent_ICS_logon = 1;
3436                 continue;
3437             }
3438
3439             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3440                 (looking_at(buf, &i, "\n<12> ") ||
3441                  looking_at(buf, &i, "<12> "))) {
3442                 loggedOn = TRUE;
3443                 if (oldi > next_out) {
3444                     SendToPlayer(&buf[next_out], oldi - next_out);
3445                 }
3446                 next_out = i;
3447                 started = STARTED_BOARD;
3448                 parse_pos = 0;
3449                 continue;
3450             }
3451
3452             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3453                 looking_at(buf, &i, "<b1> ")) {
3454                 if (oldi > next_out) {
3455                     SendToPlayer(&buf[next_out], oldi - next_out);
3456                 }
3457                 next_out = i;
3458                 started = STARTED_HOLDINGS;
3459                 parse_pos = 0;
3460                 continue;
3461             }
3462
3463             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3464                 loggedOn = TRUE;
3465                 /* Header for a move list -- first line */
3466
3467                 switch (ics_getting_history) {
3468                   case H_FALSE:
3469                     switch (gameMode) {
3470                       case IcsIdle:
3471                       case BeginningOfGame:
3472                         /* User typed "moves" or "oldmoves" while we
3473                            were idle.  Pretend we asked for these
3474                            moves and soak them up so user can step
3475                            through them and/or save them.
3476                            */
3477                         Reset(FALSE, TRUE);
3478                         gameMode = IcsObserving;
3479                         ModeHighlight();
3480                         ics_gamenum = -1;
3481                         ics_getting_history = H_GOT_UNREQ_HEADER;
3482                         break;
3483                       case EditGame: /*?*/
3484                       case EditPosition: /*?*/
3485                         /* Should above feature work in these modes too? */
3486                         /* For now it doesn't */
3487                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3488                         break;
3489                       default:
3490                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3491                         break;
3492                     }
3493                     break;
3494                   case H_REQUESTED:
3495                     /* Is this the right one? */
3496                     if (gameInfo.white && gameInfo.black &&
3497                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3498                         strcmp(gameInfo.black, star_match[2]) == 0) {
3499                         /* All is well */
3500                         ics_getting_history = H_GOT_REQ_HEADER;
3501                     }
3502                     break;
3503                   case H_GOT_REQ_HEADER:
3504                   case H_GOT_UNREQ_HEADER:
3505                   case H_GOT_UNWANTED_HEADER:
3506                   case H_GETTING_MOVES:
3507                     /* Should not happen */
3508                     DisplayError(_("Error gathering move list: two headers"), 0);
3509                     ics_getting_history = H_FALSE;
3510                     break;
3511                 }
3512
3513                 /* Save player ratings into gameInfo if needed */
3514                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3515                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3516                     (gameInfo.whiteRating == -1 ||
3517                      gameInfo.blackRating == -1)) {
3518
3519                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3520                     gameInfo.blackRating = string_to_rating(star_match[3]);
3521                     if (appData.debugMode)
3522                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3523                               gameInfo.whiteRating, gameInfo.blackRating);
3524                 }
3525                 continue;
3526             }
3527
3528             if (looking_at(buf, &i,
3529               "* * match, initial time: * minute*, increment: * second")) {
3530                 /* Header for a move list -- second line */
3531                 /* Initial board will follow if this is a wild game */
3532                 if (gameInfo.event != NULL) free(gameInfo.event);
3533                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3534                 gameInfo.event = StrSave(str);
3535                 /* [HGM] we switched variant. Translate boards if needed. */
3536                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3537                 continue;
3538             }
3539
3540             if (looking_at(buf, &i, "Move  ")) {
3541                 /* Beginning of a move list */
3542                 switch (ics_getting_history) {
3543                   case H_FALSE:
3544                     /* Normally should not happen */
3545                     /* Maybe user hit reset while we were parsing */
3546                     break;
3547                   case H_REQUESTED:
3548                     /* Happens if we are ignoring a move list that is not
3549                      * the one we just requested.  Common if the user
3550                      * tries to observe two games without turning off
3551                      * getMoveList */
3552                     break;
3553                   case H_GETTING_MOVES:
3554                     /* Should not happen */
3555                     DisplayError(_("Error gathering move list: nested"), 0);
3556                     ics_getting_history = H_FALSE;
3557                     break;
3558                   case H_GOT_REQ_HEADER:
3559                     ics_getting_history = H_GETTING_MOVES;
3560                     started = STARTED_MOVES;
3561                     parse_pos = 0;
3562                     if (oldi > next_out) {
3563                         SendToPlayer(&buf[next_out], oldi - next_out);
3564                     }
3565                     break;
3566                   case H_GOT_UNREQ_HEADER:
3567                     ics_getting_history = H_GETTING_MOVES;
3568                     started = STARTED_MOVES_NOHIDE;
3569                     parse_pos = 0;
3570                     break;
3571                   case H_GOT_UNWANTED_HEADER:
3572                     ics_getting_history = H_FALSE;
3573                     break;
3574                 }
3575                 continue;
3576             }
3577
3578             if (looking_at(buf, &i, "% ") ||
3579                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3580                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3581                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3582                     soughtPending = FALSE;
3583                     seekGraphUp = TRUE;
3584                     DrawSeekGraph();
3585                 }
3586                 if(suppressKibitz) next_out = i;
3587                 savingComment = FALSE;
3588                 suppressKibitz = 0;
3589                 switch (started) {
3590                   case STARTED_MOVES:
3591                   case STARTED_MOVES_NOHIDE:
3592                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3593                     parse[parse_pos + i - oldi] = NULLCHAR;
3594                     ParseGameHistory(parse);
3595 #if ZIPPY
3596                     if (appData.zippyPlay && first.initDone) {
3597                         FeedMovesToProgram(&first, forwardMostMove);
3598                         if (gameMode == IcsPlayingWhite) {
3599                             if (WhiteOnMove(forwardMostMove)) {
3600                                 if (first.sendTime) {
3601                                   if (first.useColors) {
3602                                     SendToProgram("black\n", &first);
3603                                   }
3604                                   SendTimeRemaining(&first, TRUE);
3605                                 }
3606                                 if (first.useColors) {
3607                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3608                                 }
3609                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3610                                 first.maybeThinking = TRUE;
3611                             } else {
3612                                 if (first.usePlayother) {
3613                                   if (first.sendTime) {
3614                                     SendTimeRemaining(&first, TRUE);
3615                                   }
3616                                   SendToProgram("playother\n", &first);
3617                                   firstMove = FALSE;
3618                                 } else {
3619                                   firstMove = TRUE;
3620                                 }
3621                             }
3622                         } else if (gameMode == IcsPlayingBlack) {
3623                             if (!WhiteOnMove(forwardMostMove)) {
3624                                 if (first.sendTime) {
3625                                   if (first.useColors) {
3626                                     SendToProgram("white\n", &first);
3627                                   }
3628                                   SendTimeRemaining(&first, FALSE);
3629                                 }
3630                                 if (first.useColors) {
3631                                   SendToProgram("black\n", &first);
3632                                 }
3633                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3634                                 first.maybeThinking = TRUE;
3635                             } else {
3636                                 if (first.usePlayother) {
3637                                   if (first.sendTime) {
3638                                     SendTimeRemaining(&first, FALSE);
3639                                   }
3640                                   SendToProgram("playother\n", &first);
3641                                   firstMove = FALSE;
3642                                 } else {
3643                                   firstMove = TRUE;
3644                                 }
3645                             }
3646                         }
3647                     }
3648 #endif
3649                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3650                         /* Moves came from oldmoves or moves command
3651                            while we weren't doing anything else.
3652                            */
3653                         currentMove = forwardMostMove;
3654                         ClearHighlights();/*!!could figure this out*/
3655                         flipView = appData.flipView;
3656                         DrawPosition(TRUE, boards[currentMove]);
3657                         DisplayBothClocks();
3658                         snprintf(str, MSG_SIZ, "%s %s %s",
3659                                 gameInfo.white, _("vs."),  gameInfo.black);
3660                         DisplayTitle(str);
3661                         gameMode = IcsIdle;
3662                     } else {
3663                         /* Moves were history of an active game */
3664                         if (gameInfo.resultDetails != NULL) {
3665                             free(gameInfo.resultDetails);
3666                             gameInfo.resultDetails = NULL;
3667                         }
3668                     }
3669                     HistorySet(parseList, backwardMostMove,
3670                                forwardMostMove, currentMove-1);
3671                     DisplayMove(currentMove - 1);
3672                     if (started == STARTED_MOVES) next_out = i;
3673                     started = STARTED_NONE;
3674                     ics_getting_history = H_FALSE;
3675                     break;
3676
3677                   case STARTED_OBSERVE:
3678                     started = STARTED_NONE;
3679                     SendToICS(ics_prefix);
3680                     SendToICS("refresh\n");
3681                     break;
3682
3683                   default:
3684                     break;
3685                 }
3686                 if(bookHit) { // [HGM] book: simulate book reply
3687                     static char bookMove[MSG_SIZ]; // a bit generous?
3688
3689                     programStats.nodes = programStats.depth = programStats.time =
3690                     programStats.score = programStats.got_only_move = 0;
3691                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3692
3693                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3694                     strcat(bookMove, bookHit);
3695                     HandleMachineMove(bookMove, &first);
3696                 }
3697                 continue;
3698             }
3699
3700             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3701                  started == STARTED_HOLDINGS ||
3702                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3703                 /* Accumulate characters in move list or board */
3704                 parse[parse_pos++] = buf[i];
3705             }
3706
3707             /* Start of game messages.  Mostly we detect start of game
3708                when the first board image arrives.  On some versions
3709                of the ICS, though, we need to do a "refresh" after starting
3710                to observe in order to get the current board right away. */
3711             if (looking_at(buf, &i, "Adding game * to observation list")) {
3712                 started = STARTED_OBSERVE;
3713                 continue;
3714             }
3715
3716             /* Handle auto-observe */
3717             if (appData.autoObserve &&
3718                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3719                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3720                 char *player;
3721                 /* Choose the player that was highlighted, if any. */
3722                 if (star_match[0][0] == '\033' ||
3723                     star_match[1][0] != '\033') {
3724                     player = star_match[0];
3725                 } else {
3726                     player = star_match[2];
3727                 }
3728                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3729                         ics_prefix, StripHighlightAndTitle(player));
3730                 SendToICS(str);
3731
3732                 /* Save ratings from notify string */
3733                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3734                 player1Rating = string_to_rating(star_match[1]);
3735                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3736                 player2Rating = string_to_rating(star_match[3]);
3737
3738                 if (appData.debugMode)
3739                   fprintf(debugFP,
3740                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3741                           player1Name, player1Rating,
3742                           player2Name, player2Rating);
3743
3744                 continue;
3745             }
3746
3747             /* Deal with automatic examine mode after a game,
3748                and with IcsObserving -> IcsExamining transition */
3749             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3750                 looking_at(buf, &i, "has made you an examiner of game *")) {
3751
3752                 int gamenum = atoi(star_match[0]);
3753                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3754                     gamenum == ics_gamenum) {
3755                     /* We were already playing or observing this game;
3756                        no need to refetch history */
3757                     gameMode = IcsExamining;
3758                     if (pausing) {
3759                         pauseExamForwardMostMove = forwardMostMove;
3760                     } else if (currentMove < forwardMostMove) {
3761                         ForwardInner(forwardMostMove);
3762                     }
3763                 } else {
3764                     /* I don't think this case really can happen */
3765                     SendToICS(ics_prefix);
3766                     SendToICS("refresh\n");
3767                 }
3768                 continue;
3769             }
3770
3771             /* Error messages */
3772 //          if (ics_user_moved) {
3773             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3774                 if (looking_at(buf, &i, "Illegal move") ||
3775                     looking_at(buf, &i, "Not a legal move") ||
3776                     looking_at(buf, &i, "Your king is in check") ||
3777                     looking_at(buf, &i, "It isn't your turn") ||
3778                     looking_at(buf, &i, "It is not your move")) {
3779                     /* Illegal move */
3780                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3781                         currentMove = forwardMostMove-1;
3782                         DisplayMove(currentMove - 1); /* before DMError */
3783                         DrawPosition(FALSE, boards[currentMove]);
3784                         SwitchClocks(forwardMostMove-1); // [HGM] race
3785                         DisplayBothClocks();
3786                     }
3787                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3788                     ics_user_moved = 0;
3789                     continue;
3790                 }
3791             }
3792
3793             if (looking_at(buf, &i, "still have time") ||
3794                 looking_at(buf, &i, "not out of time") ||
3795                 looking_at(buf, &i, "either player is out of time") ||
3796                 looking_at(buf, &i, "has timeseal; checking")) {
3797                 /* We must have called his flag a little too soon */
3798                 whiteFlag = blackFlag = FALSE;
3799                 continue;
3800             }
3801
3802             if (looking_at(buf, &i, "added * seconds to") ||
3803                 looking_at(buf, &i, "seconds were added to")) {
3804                 /* Update the clocks */
3805                 SendToICS(ics_prefix);
3806                 SendToICS("refresh\n");
3807                 continue;
3808             }
3809
3810             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3811                 ics_clock_paused = TRUE;
3812                 StopClocks();
3813                 continue;
3814             }
3815
3816             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3817                 ics_clock_paused = FALSE;
3818                 StartClocks();
3819                 continue;
3820             }
3821
3822             /* Grab player ratings from the Creating: message.
3823                Note we have to check for the special case when
3824                the ICS inserts things like [white] or [black]. */
3825             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3826                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3827                 /* star_matches:
3828                    0    player 1 name (not necessarily white)
3829                    1    player 1 rating
3830                    2    empty, white, or black (IGNORED)
3831                    3    player 2 name (not necessarily black)
3832                    4    player 2 rating
3833
3834                    The names/ratings are sorted out when the game
3835                    actually starts (below).
3836                 */
3837                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3838                 player1Rating = string_to_rating(star_match[1]);
3839                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3840                 player2Rating = string_to_rating(star_match[4]);
3841
3842                 if (appData.debugMode)
3843                   fprintf(debugFP,
3844                           "Ratings from 'Creating:' %s %d, %s %d\n",
3845                           player1Name, player1Rating,
3846                           player2Name, player2Rating);
3847
3848                 continue;
3849             }
3850
3851             /* Improved generic start/end-of-game messages */
3852             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3853                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3854                 /* If tkind == 0: */
3855                 /* star_match[0] is the game number */
3856                 /*           [1] is the white player's name */
3857                 /*           [2] is the black player's name */
3858                 /* For end-of-game: */
3859                 /*           [3] is the reason for the game end */
3860                 /*           [4] is a PGN end game-token, preceded by " " */
3861                 /* For start-of-game: */
3862                 /*           [3] begins with "Creating" or "Continuing" */
3863                 /*           [4] is " *" or empty (don't care). */
3864                 int gamenum = atoi(star_match[0]);
3865                 char *whitename, *blackname, *why, *endtoken;
3866                 ChessMove endtype = EndOfFile;
3867
3868                 if (tkind == 0) {
3869                   whitename = star_match[1];
3870                   blackname = star_match[2];
3871                   why = star_match[3];
3872                   endtoken = star_match[4];
3873                 } else {
3874                   whitename = star_match[1];
3875                   blackname = star_match[3];
3876                   why = star_match[5];
3877                   endtoken = star_match[6];
3878                 }
3879
3880                 /* Game start messages */
3881                 if (strncmp(why, "Creating ", 9) == 0 ||
3882                     strncmp(why, "Continuing ", 11) == 0) {
3883                     gs_gamenum = gamenum;
3884                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3885                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3886                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3887 #if ZIPPY
3888                     if (appData.zippyPlay) {
3889                         ZippyGameStart(whitename, blackname);
3890                     }
3891 #endif /*ZIPPY*/
3892                     partnerBoardValid = FALSE; // [HGM] bughouse
3893                     continue;
3894                 }
3895
3896                 /* Game end messages */
3897                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3898                     ics_gamenum != gamenum) {
3899                     continue;
3900                 }
3901                 while (endtoken[0] == ' ') endtoken++;
3902                 switch (endtoken[0]) {
3903                   case '*':
3904                   default:
3905                     endtype = GameUnfinished;
3906                     break;
3907                   case '0':
3908                     endtype = BlackWins;
3909                     break;
3910                   case '1':
3911                     if (endtoken[1] == '/')
3912                       endtype = GameIsDrawn;
3913                     else
3914                       endtype = WhiteWins;
3915                     break;
3916                 }
3917                 GameEnds(endtype, why, GE_ICS);
3918 #if ZIPPY
3919                 if (appData.zippyPlay && first.initDone) {
3920                     ZippyGameEnd(endtype, why);
3921                     if (first.pr == NoProc) {
3922                       /* Start the next process early so that we'll
3923                          be ready for the next challenge */
3924                       StartChessProgram(&first);
3925                     }
3926                     /* Send "new" early, in case this command takes
3927                        a long time to finish, so that we'll be ready
3928                        for the next challenge. */
3929                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3930                     Reset(TRUE, TRUE);
3931                 }
3932 #endif /*ZIPPY*/
3933                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3934                 continue;
3935             }
3936
3937             if (looking_at(buf, &i, "Removing game * from observation") ||
3938                 looking_at(buf, &i, "no longer observing game *") ||
3939                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3940                 if (gameMode == IcsObserving &&
3941                     atoi(star_match[0]) == ics_gamenum)
3942                   {
3943                       /* icsEngineAnalyze */
3944                       if (appData.icsEngineAnalyze) {
3945                             ExitAnalyzeMode();
3946                             ModeHighlight();
3947                       }
3948                       StopClocks();
3949                       gameMode = IcsIdle;
3950                       ics_gamenum = -1;
3951                       ics_user_moved = FALSE;
3952                   }
3953                 continue;
3954             }
3955
3956             if (looking_at(buf, &i, "no longer examining game *")) {
3957                 if (gameMode == IcsExamining &&
3958                     atoi(star_match[0]) == ics_gamenum)
3959                   {
3960                       gameMode = IcsIdle;
3961                       ics_gamenum = -1;
3962                       ics_user_moved = FALSE;
3963                   }
3964                 continue;
3965             }
3966
3967             /* Advance leftover_start past any newlines we find,
3968                so only partial lines can get reparsed */
3969             if (looking_at(buf, &i, "\n")) {
3970                 prevColor = curColor;
3971                 if (curColor != ColorNormal) {
3972                     if (oldi > next_out) {
3973                         SendToPlayer(&buf[next_out], oldi - next_out);
3974                         next_out = oldi;
3975                     }
3976                     Colorize(ColorNormal, FALSE);
3977                     curColor = ColorNormal;
3978                 }
3979                 if (started == STARTED_BOARD) {
3980                     started = STARTED_NONE;
3981                     parse[parse_pos] = NULLCHAR;
3982                     ParseBoard12(parse);
3983                     ics_user_moved = 0;
3984
3985                     /* Send premove here */
3986                     if (appData.premove) {
3987                       char str[MSG_SIZ];
3988                       if (currentMove == 0 &&
3989                           gameMode == IcsPlayingWhite &&
3990                           appData.premoveWhite) {
3991                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3992                         if (appData.debugMode)
3993                           fprintf(debugFP, "Sending premove:\n");
3994                         SendToICS(str);
3995                       } else if (currentMove == 1 &&
3996                                  gameMode == IcsPlayingBlack &&
3997                                  appData.premoveBlack) {
3998                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3999                         if (appData.debugMode)
4000                           fprintf(debugFP, "Sending premove:\n");
4001                         SendToICS(str);
4002                       } else if (gotPremove) {
4003                         gotPremove = 0;
4004                         ClearPremoveHighlights();
4005                         if (appData.debugMode)
4006                           fprintf(debugFP, "Sending premove:\n");
4007                           UserMoveEvent(premoveFromX, premoveFromY,
4008                                         premoveToX, premoveToY,
4009                                         premovePromoChar);
4010                       }
4011                     }
4012
4013                     /* Usually suppress following prompt */
4014                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4015                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4016                         if (looking_at(buf, &i, "*% ")) {
4017                             savingComment = FALSE;
4018                             suppressKibitz = 0;
4019                         }
4020                     }
4021                     next_out = i;
4022                 } else if (started == STARTED_HOLDINGS) {
4023                     int gamenum;
4024                     char new_piece[MSG_SIZ];
4025                     started = STARTED_NONE;
4026                     parse[parse_pos] = NULLCHAR;
4027                     if (appData.debugMode)
4028                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4029                                                         parse, currentMove);
4030                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4031                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4032                         if (gameInfo.variant == VariantNormal) {
4033                           /* [HGM] We seem to switch variant during a game!
4034                            * Presumably no holdings were displayed, so we have
4035                            * to move the position two files to the right to
4036                            * create room for them!
4037                            */
4038                           VariantClass newVariant;
4039                           switch(gameInfo.boardWidth) { // base guess on board width
4040                                 case 9:  newVariant = VariantShogi; break;
4041                                 case 10: newVariant = VariantGreat; break;
4042                                 default: newVariant = VariantCrazyhouse; break;
4043                           }
4044                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4045                           /* Get a move list just to see the header, which
4046                              will tell us whether this is really bug or zh */
4047                           if (ics_getting_history == H_FALSE) {
4048                             ics_getting_history = H_REQUESTED;
4049                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4050                             SendToICS(str);
4051                           }
4052                         }
4053                         new_piece[0] = NULLCHAR;
4054                         sscanf(parse, "game %d white [%s black [%s <- %s",
4055                                &gamenum, white_holding, black_holding,
4056                                new_piece);
4057                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4058                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4059                         /* [HGM] copy holdings to board holdings area */
4060                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4061                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4062                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4063 #if ZIPPY
4064                         if (appData.zippyPlay && first.initDone) {
4065                             ZippyHoldings(white_holding, black_holding,
4066                                           new_piece);
4067                         }
4068 #endif /*ZIPPY*/
4069                         if (tinyLayout || smallLayout) {
4070                             char wh[16], bh[16];
4071                             PackHolding(wh, white_holding);
4072                             PackHolding(bh, black_holding);
4073                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4074                                     gameInfo.white, gameInfo.black);
4075                         } else {
4076                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4077                                     gameInfo.white, white_holding, _("vs."),
4078                                     gameInfo.black, black_holding);
4079                         }
4080                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4081                         DrawPosition(FALSE, boards[currentMove]);
4082                         DisplayTitle(str);
4083                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4084                         sscanf(parse, "game %d white [%s black [%s <- %s",
4085                                &gamenum, white_holding, black_holding,
4086                                new_piece);
4087                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4088                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4089                         /* [HGM] copy holdings to partner-board holdings area */
4090                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4091                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4092                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4093                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4094                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4095                       }
4096                     }
4097                     /* Suppress following prompt */
4098                     if (looking_at(buf, &i, "*% ")) {
4099                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4100                         savingComment = FALSE;
4101                         suppressKibitz = 0;
4102                     }
4103                     next_out = i;
4104                 }
4105                 continue;
4106             }
4107
4108             i++;                /* skip unparsed character and loop back */
4109         }
4110
4111         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4112 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4113 //          SendToPlayer(&buf[next_out], i - next_out);
4114             started != STARTED_HOLDINGS && leftover_start > next_out) {
4115             SendToPlayer(&buf[next_out], leftover_start - next_out);
4116             next_out = i;
4117         }
4118
4119         leftover_len = buf_len - leftover_start;
4120         /* if buffer ends with something we couldn't parse,
4121            reparse it after appending the next read */
4122
4123     } else if (count == 0) {
4124         RemoveInputSource(isr);
4125         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4126     } else {
4127         DisplayFatalError(_("Error reading from ICS"), error, 1);
4128     }
4129 }
4130
4131
4132 /* Board style 12 looks like this:
4133
4134    <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
4135
4136  * The "<12> " is stripped before it gets to this routine.  The two
4137  * trailing 0's (flip state and clock ticking) are later addition, and
4138  * some chess servers may not have them, or may have only the first.
4139  * Additional trailing fields may be added in the future.
4140  */
4141
4142 #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"
4143
4144 #define RELATION_OBSERVING_PLAYED    0
4145 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4146 #define RELATION_PLAYING_MYMOVE      1
4147 #define RELATION_PLAYING_NOTMYMOVE  -1
4148 #define RELATION_EXAMINING           2
4149 #define RELATION_ISOLATED_BOARD     -3
4150 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4151
4152 void
4153 ParseBoard12 (char *string)
4154 {
4155     GameMode newGameMode;
4156     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4157     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4158     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4159     char to_play, board_chars[200];
4160     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4161     char black[32], white[32];
4162     Board board;
4163     int prevMove = currentMove;
4164     int ticking = 2;
4165     ChessMove moveType;
4166     int fromX, fromY, toX, toY;
4167     char promoChar;
4168     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4169     char *bookHit = NULL; // [HGM] book
4170     Boolean weird = FALSE, reqFlag = FALSE;
4171
4172     fromX = fromY = toX = toY = -1;
4173
4174     newGame = FALSE;
4175
4176     if (appData.debugMode)
4177       fprintf(debugFP, _("Parsing board: %s\n"), string);
4178
4179     move_str[0] = NULLCHAR;
4180     elapsed_time[0] = NULLCHAR;
4181     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4182         int  i = 0, j;
4183         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4184             if(string[i] == ' ') { ranks++; files = 0; }
4185             else files++;
4186             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4187             i++;
4188         }
4189         for(j = 0; j <i; j++) board_chars[j] = string[j];
4190         board_chars[i] = '\0';
4191         string += i + 1;
4192     }
4193     n = sscanf(string, PATTERN, &to_play, &double_push,
4194                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4195                &gamenum, white, black, &relation, &basetime, &increment,
4196                &white_stren, &black_stren, &white_time, &black_time,
4197                &moveNum, str, elapsed_time, move_str, &ics_flip,
4198                &ticking);
4199
4200     if (n < 21) {
4201         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4202         DisplayError(str, 0);
4203         return;
4204     }
4205
4206     /* Convert the move number to internal form */
4207     moveNum = (moveNum - 1) * 2;
4208     if (to_play == 'B') moveNum++;
4209     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4210       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4211                         0, 1);
4212       return;
4213     }
4214
4215     switch (relation) {
4216       case RELATION_OBSERVING_PLAYED:
4217       case RELATION_OBSERVING_STATIC:
4218         if (gamenum == -1) {
4219             /* Old ICC buglet */
4220             relation = RELATION_OBSERVING_STATIC;
4221         }
4222         newGameMode = IcsObserving;
4223         break;
4224       case RELATION_PLAYING_MYMOVE:
4225       case RELATION_PLAYING_NOTMYMOVE:
4226         newGameMode =
4227           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4228             IcsPlayingWhite : IcsPlayingBlack;
4229         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4230         break;
4231       case RELATION_EXAMINING:
4232         newGameMode = IcsExamining;
4233         break;
4234       case RELATION_ISOLATED_BOARD:
4235       default:
4236         /* Just display this board.  If user was doing something else,
4237            we will forget about it until the next board comes. */
4238         newGameMode = IcsIdle;
4239         break;
4240       case RELATION_STARTING_POSITION:
4241         newGameMode = gameMode;
4242         break;
4243     }
4244
4245     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4246         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4247          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4248       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4249       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4250       static int lastBgGame = -1;
4251       char *toSqr;
4252       for (k = 0; k < ranks; k++) {
4253         for (j = 0; j < files; j++)
4254           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4255         if(gameInfo.holdingsWidth > 1) {
4256              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4257              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4258         }
4259       }
4260       CopyBoard(partnerBoard, board);
4261       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4262         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4263         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4264       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4265       if(toSqr = strchr(str, '-')) {
4266         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4267         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4268       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4269       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4270       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4271       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4272       if(twoBoards) {
4273           DisplayWhiteClock(white_time*fac, to_play == 'W');
4274           DisplayBlackClock(black_time*fac, to_play != 'W');
4275           activePartner = to_play;
4276           if(gamenum != lastBgGame) {
4277               char buf[MSG_SIZ];
4278               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4279               DisplayTitle(buf);
4280           }
4281           lastBgGame = gamenum;
4282           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4283                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4284       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4285                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4286       DisplayMessage(partnerStatus, "");
4287         partnerBoardValid = TRUE;
4288       return;
4289     }
4290
4291     if(appData.dualBoard && appData.bgObserve) {
4292         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4293             SendToICS(ics_prefix), SendToICS("pobserve\n");
4294         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4295             char buf[MSG_SIZ];
4296             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4297             SendToICS(buf);
4298         }
4299     }
4300
4301     /* Modify behavior for initial board display on move listing
4302        of wild games.
4303        */
4304     switch (ics_getting_history) {
4305       case H_FALSE:
4306       case H_REQUESTED:
4307         break;
4308       case H_GOT_REQ_HEADER:
4309       case H_GOT_UNREQ_HEADER:
4310         /* This is the initial position of the current game */
4311         gamenum = ics_gamenum;
4312         moveNum = 0;            /* old ICS bug workaround */
4313         if (to_play == 'B') {
4314           startedFromSetupPosition = TRUE;
4315           blackPlaysFirst = TRUE;
4316           moveNum = 1;
4317           if (forwardMostMove == 0) forwardMostMove = 1;
4318           if (backwardMostMove == 0) backwardMostMove = 1;
4319           if (currentMove == 0) currentMove = 1;
4320         }
4321         newGameMode = gameMode;
4322         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4323         break;
4324       case H_GOT_UNWANTED_HEADER:
4325         /* This is an initial board that we don't want */
4326         return;
4327       case H_GETTING_MOVES:
4328         /* Should not happen */
4329         DisplayError(_("Error gathering move list: extra board"), 0);
4330         ics_getting_history = H_FALSE;
4331         return;
4332     }
4333
4334    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4335                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4336                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4337      /* [HGM] We seem to have switched variant unexpectedly
4338       * Try to guess new variant from board size
4339       */
4340           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4341           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4342           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4343           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4344           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4345           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4346           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4347           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4348           /* Get a move list just to see the header, which
4349              will tell us whether this is really bug or zh */
4350           if (ics_getting_history == H_FALSE) {
4351             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4352             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4353             SendToICS(str);
4354           }
4355     }
4356
4357     /* Take action if this is the first board of a new game, or of a
4358        different game than is currently being displayed.  */
4359     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4360         relation == RELATION_ISOLATED_BOARD) {
4361
4362         /* Forget the old game and get the history (if any) of the new one */
4363         if (gameMode != BeginningOfGame) {
4364           Reset(TRUE, TRUE);
4365         }
4366         newGame = TRUE;
4367         if (appData.autoRaiseBoard) BoardToTop();
4368         prevMove = -3;
4369         if (gamenum == -1) {
4370             newGameMode = IcsIdle;
4371         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4372                    appData.getMoveList && !reqFlag) {
4373             /* Need to get game history */
4374             ics_getting_history = H_REQUESTED;
4375             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4376             SendToICS(str);
4377         }
4378
4379         /* Initially flip the board to have black on the bottom if playing
4380            black or if the ICS flip flag is set, but let the user change
4381            it with the Flip View button. */
4382         flipView = appData.autoFlipView ?
4383           (newGameMode == IcsPlayingBlack) || ics_flip :
4384           appData.flipView;
4385
4386         /* Done with values from previous mode; copy in new ones */
4387         gameMode = newGameMode;
4388         ModeHighlight();
4389         ics_gamenum = gamenum;
4390         if (gamenum == gs_gamenum) {
4391             int klen = strlen(gs_kind);
4392             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4393             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4394             gameInfo.event = StrSave(str);
4395         } else {
4396             gameInfo.event = StrSave("ICS game");
4397         }
4398         gameInfo.site = StrSave(appData.icsHost);
4399         gameInfo.date = PGNDate();
4400         gameInfo.round = StrSave("-");
4401         gameInfo.white = StrSave(white);
4402         gameInfo.black = StrSave(black);
4403         timeControl = basetime * 60 * 1000;
4404         timeControl_2 = 0;
4405         timeIncrement = increment * 1000;
4406         movesPerSession = 0;
4407         gameInfo.timeControl = TimeControlTagValue();
4408         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4409   if (appData.debugMode) {
4410     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4411     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4412     setbuf(debugFP, NULL);
4413   }
4414
4415         gameInfo.outOfBook = NULL;
4416
4417         /* Do we have the ratings? */
4418         if (strcmp(player1Name, white) == 0 &&
4419             strcmp(player2Name, black) == 0) {
4420             if (appData.debugMode)
4421               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4422                       player1Rating, player2Rating);
4423             gameInfo.whiteRating = player1Rating;
4424             gameInfo.blackRating = player2Rating;
4425         } else if (strcmp(player2Name, white) == 0 &&
4426                    strcmp(player1Name, black) == 0) {
4427             if (appData.debugMode)
4428               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4429                       player2Rating, player1Rating);
4430             gameInfo.whiteRating = player2Rating;
4431             gameInfo.blackRating = player1Rating;
4432         }
4433         player1Name[0] = player2Name[0] = NULLCHAR;
4434
4435         /* Silence shouts if requested */
4436         if (appData.quietPlay &&
4437             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4438             SendToICS(ics_prefix);
4439             SendToICS("set shout 0\n");
4440         }
4441     }
4442
4443     /* Deal with midgame name changes */
4444     if (!newGame) {
4445         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4446             if (gameInfo.white) free(gameInfo.white);
4447             gameInfo.white = StrSave(white);
4448         }
4449         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4450             if (gameInfo.black) free(gameInfo.black);
4451             gameInfo.black = StrSave(black);
4452         }
4453     }
4454
4455     /* Throw away game result if anything actually changes in examine mode */
4456     if (gameMode == IcsExamining && !newGame) {
4457         gameInfo.result = GameUnfinished;
4458         if (gameInfo.resultDetails != NULL) {
4459             free(gameInfo.resultDetails);
4460             gameInfo.resultDetails = NULL;
4461         }
4462     }
4463
4464     /* In pausing && IcsExamining mode, we ignore boards coming
4465        in if they are in a different variation than we are. */
4466     if (pauseExamInvalid) return;
4467     if (pausing && gameMode == IcsExamining) {
4468         if (moveNum <= pauseExamForwardMostMove) {
4469             pauseExamInvalid = TRUE;
4470             forwardMostMove = pauseExamForwardMostMove;
4471             return;
4472         }
4473     }
4474
4475   if (appData.debugMode) {
4476     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4477   }
4478     /* Parse the board */
4479     for (k = 0; k < ranks; k++) {
4480       for (j = 0; j < files; j++)
4481         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4482       if(gameInfo.holdingsWidth > 1) {
4483            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4484            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4485       }
4486     }
4487     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4488       board[5][BOARD_RGHT+1] = WhiteAngel;
4489       board[6][BOARD_RGHT+1] = WhiteMarshall;
4490       board[1][0] = BlackMarshall;
4491       board[2][0] = BlackAngel;
4492       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4493     }
4494     CopyBoard(boards[moveNum], board);
4495     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4496     if (moveNum == 0) {
4497         startedFromSetupPosition =
4498           !CompareBoards(board, initialPosition);
4499         if(startedFromSetupPosition)
4500             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4501     }
4502
4503     /* [HGM] Set castling rights. Take the outermost Rooks,
4504        to make it also work for FRC opening positions. Note that board12
4505        is really defective for later FRC positions, as it has no way to
4506        indicate which Rook can castle if they are on the same side of King.
4507        For the initial position we grant rights to the outermost Rooks,
4508        and remember thos rights, and we then copy them on positions
4509        later in an FRC game. This means WB might not recognize castlings with
4510        Rooks that have moved back to their original position as illegal,
4511        but in ICS mode that is not its job anyway.
4512     */
4513     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4514     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4515
4516         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4517             if(board[0][i] == WhiteRook) j = i;
4518         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4519         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4520             if(board[0][i] == WhiteRook) j = i;
4521         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4522         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4523             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4524         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4525         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4526             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4527         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4528
4529         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4530         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4531         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4532             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4533         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4534             if(board[BOARD_HEIGHT-1][k] == bKing)
4535                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4536         if(gameInfo.variant == VariantTwoKings) {
4537             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4538             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4539             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4540         }
4541     } else { int r;
4542         r = boards[moveNum][CASTLING][0] = initialRights[0];
4543         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4544         r = boards[moveNum][CASTLING][1] = initialRights[1];
4545         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4546         r = boards[moveNum][CASTLING][3] = initialRights[3];
4547         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4548         r = boards[moveNum][CASTLING][4] = initialRights[4];
4549         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4550         /* wildcastle kludge: always assume King has rights */
4551         r = boards[moveNum][CASTLING][2] = initialRights[2];
4552         r = boards[moveNum][CASTLING][5] = initialRights[5];
4553     }
4554     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4555     boards[moveNum][EP_STATUS] = EP_NONE;
4556     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4557     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4558     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4559
4560
4561     if (ics_getting_history == H_GOT_REQ_HEADER ||
4562         ics_getting_history == H_GOT_UNREQ_HEADER) {
4563         /* This was an initial position from a move list, not
4564            the current position */
4565         return;
4566     }
4567
4568     /* Update currentMove and known move number limits */
4569     newMove = newGame || moveNum > forwardMostMove;
4570
4571     if (newGame) {
4572         forwardMostMove = backwardMostMove = currentMove = moveNum;
4573         if (gameMode == IcsExamining && moveNum == 0) {
4574           /* Workaround for ICS limitation: we are not told the wild
4575              type when starting to examine a game.  But if we ask for
4576              the move list, the move list header will tell us */
4577             ics_getting_history = H_REQUESTED;
4578             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4579             SendToICS(str);
4580         }
4581     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4582                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4583 #if ZIPPY
4584         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4585         /* [HGM] applied this also to an engine that is silently watching        */
4586         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4587             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4588             gameInfo.variant == currentlyInitializedVariant) {
4589           takeback = forwardMostMove - moveNum;
4590           for (i = 0; i < takeback; i++) {
4591             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4592             SendToProgram("undo\n", &first);
4593           }
4594         }
4595 #endif
4596
4597         forwardMostMove = moveNum;
4598         if (!pausing || currentMove > forwardMostMove)
4599           currentMove = forwardMostMove;
4600     } else {
4601         /* New part of history that is not contiguous with old part */
4602         if (pausing && gameMode == IcsExamining) {
4603             pauseExamInvalid = TRUE;
4604             forwardMostMove = pauseExamForwardMostMove;
4605             return;
4606         }
4607         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4608 #if ZIPPY
4609             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4610                 // [HGM] when we will receive the move list we now request, it will be
4611                 // fed to the engine from the first move on. So if the engine is not
4612                 // in the initial position now, bring it there.
4613                 InitChessProgram(&first, 0);
4614             }
4615 #endif
4616             ics_getting_history = H_REQUESTED;
4617             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4618             SendToICS(str);
4619         }
4620         forwardMostMove = backwardMostMove = currentMove = moveNum;
4621     }
4622
4623     /* Update the clocks */
4624     if (strchr(elapsed_time, '.')) {
4625       /* Time is in ms */
4626       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4627       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4628     } else {
4629       /* Time is in seconds */
4630       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4631       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4632     }
4633
4634
4635 #if ZIPPY
4636     if (appData.zippyPlay && newGame &&
4637         gameMode != IcsObserving && gameMode != IcsIdle &&
4638         gameMode != IcsExamining)
4639       ZippyFirstBoard(moveNum, basetime, increment);
4640 #endif
4641
4642     /* Put the move on the move list, first converting
4643        to canonical algebraic form. */
4644     if (moveNum > 0) {
4645   if (appData.debugMode) {
4646     if (appData.debugMode) { int f = forwardMostMove;
4647         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4648                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4649                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4650     }
4651     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4652     fprintf(debugFP, "moveNum = %d\n", moveNum);
4653     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4654     setbuf(debugFP, NULL);
4655   }
4656         if (moveNum <= backwardMostMove) {
4657             /* We don't know what the board looked like before
4658                this move.  Punt. */
4659           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4660             strcat(parseList[moveNum - 1], " ");
4661             strcat(parseList[moveNum - 1], elapsed_time);
4662             moveList[moveNum - 1][0] = NULLCHAR;
4663         } else if (strcmp(move_str, "none") == 0) {
4664             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4665             /* Again, we don't know what the board looked like;
4666                this is really the start of the game. */
4667             parseList[moveNum - 1][0] = NULLCHAR;
4668             moveList[moveNum - 1][0] = NULLCHAR;
4669             backwardMostMove = moveNum;
4670             startedFromSetupPosition = TRUE;
4671             fromX = fromY = toX = toY = -1;
4672         } else {
4673           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4674           //                 So we parse the long-algebraic move string in stead of the SAN move
4675           int valid; char buf[MSG_SIZ], *prom;
4676
4677           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4678                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4679           // str looks something like "Q/a1-a2"; kill the slash
4680           if(str[1] == '/')
4681             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4682           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4683           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4684                 strcat(buf, prom); // long move lacks promo specification!
4685           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4686                 if(appData.debugMode)
4687                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4688                 safeStrCpy(move_str, buf, MSG_SIZ);
4689           }
4690           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4691                                 &fromX, &fromY, &toX, &toY, &promoChar)
4692                || ParseOneMove(buf, moveNum - 1, &moveType,
4693                                 &fromX, &fromY, &toX, &toY, &promoChar);
4694           // end of long SAN patch
4695           if (valid) {
4696             (void) CoordsToAlgebraic(boards[moveNum - 1],
4697                                      PosFlags(moveNum - 1),
4698                                      fromY, fromX, toY, toX, promoChar,
4699                                      parseList[moveNum-1]);
4700             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4701               case MT_NONE:
4702               case MT_STALEMATE:
4703               default:
4704                 break;
4705               case MT_CHECK:
4706                 if(gameInfo.variant != VariantShogi)
4707                     strcat(parseList[moveNum - 1], "+");
4708                 break;
4709               case MT_CHECKMATE:
4710               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4711                 strcat(parseList[moveNum - 1], "#");
4712                 break;
4713             }
4714             strcat(parseList[moveNum - 1], " ");
4715             strcat(parseList[moveNum - 1], elapsed_time);
4716             /* currentMoveString is set as a side-effect of ParseOneMove */
4717             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4718             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4719             strcat(moveList[moveNum - 1], "\n");
4720
4721             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4722                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4723               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4724                 ChessSquare old, new = boards[moveNum][k][j];
4725                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4726                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4727                   if(old == new) continue;
4728                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4729                   else if(new == WhiteWazir || new == BlackWazir) {
4730                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4731                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4732                       else boards[moveNum][k][j] = old; // preserve type of Gold
4733                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4734                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4735               }
4736           } else {
4737             /* Move from ICS was illegal!?  Punt. */
4738             if (appData.debugMode) {
4739               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4740               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4741             }
4742             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4743             strcat(parseList[moveNum - 1], " ");
4744             strcat(parseList[moveNum - 1], elapsed_time);
4745             moveList[moveNum - 1][0] = NULLCHAR;
4746             fromX = fromY = toX = toY = -1;
4747           }
4748         }
4749   if (appData.debugMode) {
4750     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4751     setbuf(debugFP, NULL);
4752   }
4753
4754 #if ZIPPY
4755         /* Send move to chess program (BEFORE animating it). */
4756         if (appData.zippyPlay && !newGame && newMove &&
4757            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4758
4759             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4760                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4761                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4762                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4763                             move_str);
4764                     DisplayError(str, 0);
4765                 } else {
4766                     if (first.sendTime) {
4767                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4768                     }
4769                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4770                     if (firstMove && !bookHit) {
4771                         firstMove = FALSE;
4772                         if (first.useColors) {
4773                           SendToProgram(gameMode == IcsPlayingWhite ?
4774                                         "white\ngo\n" :
4775                                         "black\ngo\n", &first);
4776                         } else {
4777                           SendToProgram("go\n", &first);
4778                         }
4779                         first.maybeThinking = TRUE;
4780                     }
4781                 }
4782             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4783               if (moveList[moveNum - 1][0] == NULLCHAR) {
4784                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4785                 DisplayError(str, 0);
4786               } else {
4787                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4788                 SendMoveToProgram(moveNum - 1, &first);
4789               }
4790             }
4791         }
4792 #endif
4793     }
4794
4795     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4796         /* If move comes from a remote source, animate it.  If it
4797            isn't remote, it will have already been animated. */
4798         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4799             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4800         }
4801         if (!pausing && appData.highlightLastMove) {
4802             SetHighlights(fromX, fromY, toX, toY);
4803         }
4804     }
4805
4806     /* Start the clocks */
4807     whiteFlag = blackFlag = FALSE;
4808     appData.clockMode = !(basetime == 0 && increment == 0);
4809     if (ticking == 0) {
4810       ics_clock_paused = TRUE;
4811       StopClocks();
4812     } else if (ticking == 1) {
4813       ics_clock_paused = FALSE;
4814     }
4815     if (gameMode == IcsIdle ||
4816         relation == RELATION_OBSERVING_STATIC ||
4817         relation == RELATION_EXAMINING ||
4818         ics_clock_paused)
4819       DisplayBothClocks();
4820     else
4821       StartClocks();
4822
4823     /* Display opponents and material strengths */
4824     if (gameInfo.variant != VariantBughouse &&
4825         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4826         if (tinyLayout || smallLayout) {
4827             if(gameInfo.variant == VariantNormal)
4828               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4829                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4830                     basetime, increment);
4831             else
4832               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4833                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4834                     basetime, increment, (int) gameInfo.variant);
4835         } else {
4836             if(gameInfo.variant == VariantNormal)
4837               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4838                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4839                     basetime, increment);
4840             else
4841               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4842                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4843                     basetime, increment, VariantName(gameInfo.variant));
4844         }
4845         DisplayTitle(str);
4846   if (appData.debugMode) {
4847     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4848   }
4849     }
4850
4851
4852     /* Display the board */
4853     if (!pausing && !appData.noGUI) {
4854
4855       if (appData.premove)
4856           if (!gotPremove ||
4857              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4858              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4859               ClearPremoveHighlights();
4860
4861       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4862         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4863       DrawPosition(j, boards[currentMove]);
4864
4865       DisplayMove(moveNum - 1);
4866       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4867             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4868               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4869         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4870       }
4871     }
4872
4873     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4874 #if ZIPPY
4875     if(bookHit) { // [HGM] book: simulate book reply
4876         static char bookMove[MSG_SIZ]; // a bit generous?
4877
4878         programStats.nodes = programStats.depth = programStats.time =
4879         programStats.score = programStats.got_only_move = 0;
4880         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4881
4882         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4883         strcat(bookMove, bookHit);
4884         HandleMachineMove(bookMove, &first);
4885     }
4886 #endif
4887 }
4888
4889 void
4890 GetMoveListEvent ()
4891 {
4892     char buf[MSG_SIZ];
4893     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4894         ics_getting_history = H_REQUESTED;
4895         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4896         SendToICS(buf);
4897     }
4898 }
4899
4900 void
4901 AnalysisPeriodicEvent (int force)
4902 {
4903     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4904          && !force) || !appData.periodicUpdates)
4905       return;
4906
4907     /* Send . command to Crafty to collect stats */
4908     SendToProgram(".\n", &first);
4909
4910     /* Don't send another until we get a response (this makes
4911        us stop sending to old Crafty's which don't understand
4912        the "." command (sending illegal cmds resets node count & time,
4913        which looks bad)) */
4914     programStats.ok_to_send = 0;
4915 }
4916
4917 void
4918 ics_update_width (int new_width)
4919 {
4920         ics_printf("set width %d\n", new_width);
4921 }
4922
4923 void
4924 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4925 {
4926     char buf[MSG_SIZ];
4927
4928     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4929         // null move in variant where engine does not understand it (for analysis purposes)
4930         SendBoard(cps, moveNum + 1); // send position after move in stead.
4931         return;
4932     }
4933     if (cps->useUsermove) {
4934       SendToProgram("usermove ", cps);
4935     }
4936     if (cps->useSAN) {
4937       char *space;
4938       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4939         int len = space - parseList[moveNum];
4940         memcpy(buf, parseList[moveNum], len);
4941         buf[len++] = '\n';
4942         buf[len] = NULLCHAR;
4943       } else {
4944         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4945       }
4946       SendToProgram(buf, cps);
4947     } else {
4948       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4949         AlphaRank(moveList[moveNum], 4);
4950         SendToProgram(moveList[moveNum], cps);
4951         AlphaRank(moveList[moveNum], 4); // and back
4952       } else
4953       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4954        * the engine. It would be nice to have a better way to identify castle
4955        * moves here. */
4956       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4957                                                                          && cps->useOOCastle) {
4958         int fromX = moveList[moveNum][0] - AAA;
4959         int fromY = moveList[moveNum][1] - ONE;
4960         int toX = moveList[moveNum][2] - AAA;
4961         int toY = moveList[moveNum][3] - ONE;
4962         if((boards[moveNum][fromY][fromX] == WhiteKing
4963             && boards[moveNum][toY][toX] == WhiteRook)
4964            || (boards[moveNum][fromY][fromX] == BlackKing
4965                && boards[moveNum][toY][toX] == BlackRook)) {
4966           if(toX > fromX) SendToProgram("O-O\n", cps);
4967           else SendToProgram("O-O-O\n", cps);
4968         }
4969         else SendToProgram(moveList[moveNum], cps);
4970       } else
4971       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4972         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4973           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4974           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4975                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4976         } else
4977           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4978                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4979         SendToProgram(buf, cps);
4980       }
4981       else SendToProgram(moveList[moveNum], cps);
4982       /* End of additions by Tord */
4983     }
4984
4985     /* [HGM] setting up the opening has brought engine in force mode! */
4986     /*       Send 'go' if we are in a mode where machine should play. */
4987     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4988         (gameMode == TwoMachinesPlay   ||
4989 #if ZIPPY
4990          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4991 #endif
4992          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4993         SendToProgram("go\n", cps);
4994   if (appData.debugMode) {
4995     fprintf(debugFP, "(extra)\n");
4996   }
4997     }
4998     setboardSpoiledMachineBlack = 0;
4999 }
5000
5001 void
5002 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5003 {
5004     char user_move[MSG_SIZ];
5005     char suffix[4];
5006
5007     if(gameInfo.variant == VariantSChess && promoChar) {
5008         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5009         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5010     } else suffix[0] = NULLCHAR;
5011
5012     switch (moveType) {
5013       default:
5014         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5015                 (int)moveType, fromX, fromY, toX, toY);
5016         DisplayError(user_move + strlen("say "), 0);
5017         break;
5018       case WhiteKingSideCastle:
5019       case BlackKingSideCastle:
5020       case WhiteQueenSideCastleWild:
5021       case BlackQueenSideCastleWild:
5022       /* PUSH Fabien */
5023       case WhiteHSideCastleFR:
5024       case BlackHSideCastleFR:
5025       /* POP Fabien */
5026         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5027         break;
5028       case WhiteQueenSideCastle:
5029       case BlackQueenSideCastle:
5030       case WhiteKingSideCastleWild:
5031       case BlackKingSideCastleWild:
5032       /* PUSH Fabien */
5033       case WhiteASideCastleFR:
5034       case BlackASideCastleFR:
5035       /* POP Fabien */
5036         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5037         break;
5038       case WhiteNonPromotion:
5039       case BlackNonPromotion:
5040         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5041         break;
5042       case WhitePromotion:
5043       case BlackPromotion:
5044         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5045           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5046                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5047                 PieceToChar(WhiteFerz));
5048         else if(gameInfo.variant == VariantGreat)
5049           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5050                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5051                 PieceToChar(WhiteMan));
5052         else
5053           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5054                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5055                 promoChar);
5056         break;
5057       case WhiteDrop:
5058       case BlackDrop:
5059       drop:
5060         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5061                  ToUpper(PieceToChar((ChessSquare) fromX)),
5062                  AAA + toX, ONE + toY);
5063         break;
5064       case IllegalMove:  /* could be a variant we don't quite understand */
5065         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5066       case NormalMove:
5067       case WhiteCapturesEnPassant:
5068       case BlackCapturesEnPassant:
5069         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5070                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5071         break;
5072     }
5073     SendToICS(user_move);
5074     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5075         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5076 }
5077
5078 void
5079 UploadGameEvent ()
5080 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5081     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5082     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5083     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5084       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5085       return;
5086     }
5087     if(gameMode != IcsExamining) { // is this ever not the case?
5088         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5089
5090         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5091           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5092         } else { // on FICS we must first go to general examine mode
5093           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5094         }
5095         if(gameInfo.variant != VariantNormal) {
5096             // try figure out wild number, as xboard names are not always valid on ICS
5097             for(i=1; i<=36; i++) {
5098               snprintf(buf, MSG_SIZ, "wild/%d", i);
5099                 if(StringToVariant(buf) == gameInfo.variant) break;
5100             }
5101             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5102             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5103             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5104         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5105         SendToICS(ics_prefix);
5106         SendToICS(buf);
5107         if(startedFromSetupPosition || backwardMostMove != 0) {
5108           fen = PositionToFEN(backwardMostMove, NULL);
5109           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5110             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5111             SendToICS(buf);
5112           } else { // FICS: everything has to set by separate bsetup commands
5113             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5114             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5115             SendToICS(buf);
5116             if(!WhiteOnMove(backwardMostMove)) {
5117                 SendToICS("bsetup tomove black\n");
5118             }
5119             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5120             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5121             SendToICS(buf);
5122             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5123             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5124             SendToICS(buf);
5125             i = boards[backwardMostMove][EP_STATUS];
5126             if(i >= 0) { // set e.p.
5127               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5128                 SendToICS(buf);
5129             }
5130             bsetup++;
5131           }
5132         }
5133       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5134             SendToICS("bsetup done\n"); // switch to normal examining.
5135     }
5136     for(i = backwardMostMove; i<last; i++) {
5137         char buf[20];
5138         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5139         SendToICS(buf);
5140     }
5141     SendToICS(ics_prefix);
5142     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5143 }
5144
5145 void
5146 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5147 {
5148     if (rf == DROP_RANK) {
5149       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5150       sprintf(move, "%c@%c%c\n",
5151                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5152     } else {
5153         if (promoChar == 'x' || promoChar == NULLCHAR) {
5154           sprintf(move, "%c%c%c%c\n",
5155                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5156         } else {
5157             sprintf(move, "%c%c%c%c%c\n",
5158                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5159         }
5160     }
5161 }
5162
5163 void
5164 ProcessICSInitScript (FILE *f)
5165 {
5166     char buf[MSG_SIZ];
5167
5168     while (fgets(buf, MSG_SIZ, f)) {
5169         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5170     }
5171
5172     fclose(f);
5173 }
5174
5175
5176 static int lastX, lastY, selectFlag, dragging;
5177
5178 void
5179 Sweep (int step)
5180 {
5181     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5182     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5183     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5184     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5185     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5186     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5187     do {
5188         promoSweep -= step;
5189         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5190         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5191         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5192         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5193         if(!step) step = -1;
5194     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5195             appData.testLegality && (promoSweep == king ||
5196             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5197     if(toX >= 0) {
5198         int victim = boards[currentMove][toY][toX];
5199         boards[currentMove][toY][toX] = promoSweep;
5200         DrawPosition(FALSE, boards[currentMove]);
5201         boards[currentMove][toY][toX] = victim;
5202     } else
5203     ChangeDragPiece(promoSweep);
5204 }
5205
5206 int
5207 PromoScroll (int x, int y)
5208 {
5209   int step = 0;
5210
5211   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5212   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5213   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5214   if(!step) return FALSE;
5215   lastX = x; lastY = y;
5216   if((promoSweep < BlackPawn) == flipView) step = -step;
5217   if(step > 0) selectFlag = 1;
5218   if(!selectFlag) Sweep(step);
5219   return FALSE;
5220 }
5221
5222 void
5223 NextPiece (int step)
5224 {
5225     ChessSquare piece = boards[currentMove][toY][toX];
5226     do {
5227         pieceSweep -= step;
5228         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5229         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5230         if(!step) step = -1;
5231     } while(PieceToChar(pieceSweep) == '.');
5232     boards[currentMove][toY][toX] = pieceSweep;
5233     DrawPosition(FALSE, boards[currentMove]);
5234     boards[currentMove][toY][toX] = piece;
5235 }
5236 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5237 void
5238 AlphaRank (char *move, int n)
5239 {
5240 //    char *p = move, c; int x, y;
5241
5242     if (appData.debugMode) {
5243         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5244     }
5245
5246     if(move[1]=='*' &&
5247        move[2]>='0' && move[2]<='9' &&
5248        move[3]>='a' && move[3]<='x'    ) {
5249         move[1] = '@';
5250         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5251         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5252     } else
5253     if(move[0]>='0' && move[0]<='9' &&
5254        move[1]>='a' && move[1]<='x' &&
5255        move[2]>='0' && move[2]<='9' &&
5256        move[3]>='a' && move[3]<='x'    ) {
5257         /* input move, Shogi -> normal */
5258         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5259         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5260         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5261         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5262     } else
5263     if(move[1]=='@' &&
5264        move[3]>='0' && move[3]<='9' &&
5265        move[2]>='a' && move[2]<='x'    ) {
5266         move[1] = '*';
5267         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5268         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5269     } else
5270     if(
5271        move[0]>='a' && move[0]<='x' &&
5272        move[3]>='0' && move[3]<='9' &&
5273        move[2]>='a' && move[2]<='x'    ) {
5274          /* output move, normal -> Shogi */
5275         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5276         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5277         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5278         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5279         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5280     }
5281     if (appData.debugMode) {
5282         fprintf(debugFP, "   out = '%s'\n", move);
5283     }
5284 }
5285
5286 char yy_textstr[8000];
5287
5288 /* Parser for moves from gnuchess, ICS, or user typein box */
5289 Boolean
5290 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5291 {
5292     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5293
5294     switch (*moveType) {
5295       case WhitePromotion:
5296       case BlackPromotion:
5297       case WhiteNonPromotion:
5298       case BlackNonPromotion:
5299       case NormalMove:
5300       case WhiteCapturesEnPassant:
5301       case BlackCapturesEnPassant:
5302       case WhiteKingSideCastle:
5303       case WhiteQueenSideCastle:
5304       case BlackKingSideCastle:
5305       case BlackQueenSideCastle:
5306       case WhiteKingSideCastleWild:
5307       case WhiteQueenSideCastleWild:
5308       case BlackKingSideCastleWild:
5309       case BlackQueenSideCastleWild:
5310       /* Code added by Tord: */
5311       case WhiteHSideCastleFR:
5312       case WhiteASideCastleFR:
5313       case BlackHSideCastleFR:
5314       case BlackASideCastleFR:
5315       /* End of code added by Tord */
5316       case IllegalMove:         /* bug or odd chess variant */
5317         *fromX = currentMoveString[0] - AAA;
5318         *fromY = currentMoveString[1] - ONE;
5319         *toX = currentMoveString[2] - AAA;
5320         *toY = currentMoveString[3] - ONE;
5321         *promoChar = currentMoveString[4];
5322         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5323             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5324     if (appData.debugMode) {
5325         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5326     }
5327             *fromX = *fromY = *toX = *toY = 0;
5328             return FALSE;
5329         }
5330         if (appData.testLegality) {
5331           return (*moveType != IllegalMove);
5332         } else {
5333           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5334                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5335         }
5336
5337       case WhiteDrop:
5338       case BlackDrop:
5339         *fromX = *moveType == WhiteDrop ?
5340           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5341           (int) CharToPiece(ToLower(currentMoveString[0]));
5342         *fromY = DROP_RANK;
5343         *toX = currentMoveString[2] - AAA;
5344         *toY = currentMoveString[3] - ONE;
5345         *promoChar = NULLCHAR;
5346         return TRUE;
5347
5348       case AmbiguousMove:
5349       case ImpossibleMove:
5350       case EndOfFile:
5351       case ElapsedTime:
5352       case Comment:
5353       case PGNTag:
5354       case NAG:
5355       case WhiteWins:
5356       case BlackWins:
5357       case GameIsDrawn:
5358       default:
5359     if (appData.debugMode) {
5360         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5361     }
5362         /* bug? */
5363         *fromX = *fromY = *toX = *toY = 0;
5364         *promoChar = NULLCHAR;
5365         return FALSE;
5366     }
5367 }
5368
5369 Boolean pushed = FALSE;
5370 char *lastParseAttempt;
5371
5372 void
5373 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5374 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5375   int fromX, fromY, toX, toY; char promoChar;
5376   ChessMove moveType;
5377   Boolean valid;
5378   int nr = 0;
5379
5380   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5381     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5382     pushed = TRUE;
5383   }
5384   endPV = forwardMostMove;
5385   do {
5386     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5387     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5388     lastParseAttempt = pv;
5389     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5390     if(!valid && nr == 0 &&
5391        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5392         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5393         // Hande case where played move is different from leading PV move
5394         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5395         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5396         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5397         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5398           endPV += 2; // if position different, keep this
5399           moveList[endPV-1][0] = fromX + AAA;
5400           moveList[endPV-1][1] = fromY + ONE;
5401           moveList[endPV-1][2] = toX + AAA;
5402           moveList[endPV-1][3] = toY + ONE;
5403           parseList[endPV-1][0] = NULLCHAR;
5404           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5405         }
5406       }
5407     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5408     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5409     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5410     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5411         valid++; // allow comments in PV
5412         continue;
5413     }
5414     nr++;
5415     if(endPV+1 > framePtr) break; // no space, truncate
5416     if(!valid) break;
5417     endPV++;
5418     CopyBoard(boards[endPV], boards[endPV-1]);
5419     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5420     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5421     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5422     CoordsToAlgebraic(boards[endPV - 1],
5423                              PosFlags(endPV - 1),
5424                              fromY, fromX, toY, toX, promoChar,
5425                              parseList[endPV - 1]);
5426   } while(valid);
5427   if(atEnd == 2) return; // used hidden, for PV conversion
5428   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5429   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5430   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5431                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5432   DrawPosition(TRUE, boards[currentMove]);
5433 }
5434
5435 int
5436 MultiPV (ChessProgramState *cps)
5437 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5438         int i;
5439         for(i=0; i<cps->nrOptions; i++)
5440             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5441                 return i;
5442         return -1;
5443 }
5444
5445 Boolean
5446 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5447 {
5448         int startPV, multi, lineStart, origIndex = index;
5449         char *p, buf2[MSG_SIZ];
5450
5451         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5452         lastX = x; lastY = y;
5453         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5454         lineStart = startPV = index;
5455         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5456         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5457         index = startPV;
5458         do{ while(buf[index] && buf[index] != '\n') index++;
5459         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5460         buf[index] = 0;
5461         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5462                 int n = first.option[multi].value;
5463                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5464                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5465                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5466                 first.option[multi].value = n;
5467                 *start = *end = 0;
5468                 return FALSE;
5469         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5470                 ExcludeClick(origIndex - lineStart);
5471                 return FALSE;
5472         }
5473         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5474         *start = startPV; *end = index-1;
5475         return TRUE;
5476 }
5477
5478 char *
5479 PvToSAN (char *pv)
5480 {
5481         static char buf[10*MSG_SIZ];
5482         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5483         *buf = NULLCHAR;
5484         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5485         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5486         for(i = forwardMostMove; i<endPV; i++){
5487             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5488             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5489             k += strlen(buf+k);
5490         }
5491         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5492         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5493         endPV = savedEnd;
5494         return buf;
5495 }
5496
5497 Boolean
5498 LoadPV (int x, int y)
5499 { // called on right mouse click to load PV
5500   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5501   lastX = x; lastY = y;
5502   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5503   return TRUE;
5504 }
5505
5506 void
5507 UnLoadPV ()
5508 {
5509   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5510   if(endPV < 0) return;
5511   if(appData.autoCopyPV) CopyFENToClipboard();
5512   endPV = -1;
5513   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5514         Boolean saveAnimate = appData.animate;
5515         if(pushed) {
5516             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5517                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5518             } else storedGames--; // abandon shelved tail of original game
5519         }
5520         pushed = FALSE;
5521         forwardMostMove = currentMove;
5522         currentMove = oldFMM;
5523         appData.animate = FALSE;
5524         ToNrEvent(forwardMostMove);
5525         appData.animate = saveAnimate;
5526   }
5527   currentMove = forwardMostMove;
5528   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5529   ClearPremoveHighlights();
5530   DrawPosition(TRUE, boards[currentMove]);
5531 }
5532
5533 void
5534 MovePV (int x, int y, int h)
5535 { // step through PV based on mouse coordinates (called on mouse move)
5536   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5537
5538   // we must somehow check if right button is still down (might be released off board!)
5539   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5540   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5541   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5542   if(!step) return;
5543   lastX = x; lastY = y;
5544
5545   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5546   if(endPV < 0) return;
5547   if(y < margin) step = 1; else
5548   if(y > h - margin) step = -1;
5549   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5550   currentMove += step;
5551   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5552   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5553                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5554   DrawPosition(FALSE, boards[currentMove]);
5555 }
5556
5557
5558 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5559 // All positions will have equal probability, but the current method will not provide a unique
5560 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5561 #define DARK 1
5562 #define LITE 2
5563 #define ANY 3
5564
5565 int squaresLeft[4];
5566 int piecesLeft[(int)BlackPawn];
5567 int seed, nrOfShuffles;
5568
5569 void
5570 GetPositionNumber ()
5571 {       // sets global variable seed
5572         int i;
5573
5574         seed = appData.defaultFrcPosition;
5575         if(seed < 0) { // randomize based on time for negative FRC position numbers
5576                 for(i=0; i<50; i++) seed += random();
5577                 seed = random() ^ random() >> 8 ^ random() << 8;
5578                 if(seed<0) seed = -seed;
5579         }
5580 }
5581
5582 int
5583 put (Board board, int pieceType, int rank, int n, int shade)
5584 // put the piece on the (n-1)-th empty squares of the given shade
5585 {
5586         int i;
5587
5588         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5589                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5590                         board[rank][i] = (ChessSquare) pieceType;
5591                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5592                         squaresLeft[ANY]--;
5593                         piecesLeft[pieceType]--;
5594                         return i;
5595                 }
5596         }
5597         return -1;
5598 }
5599
5600
5601 void
5602 AddOnePiece (Board board, int pieceType, int rank, int shade)
5603 // calculate where the next piece goes, (any empty square), and put it there
5604 {
5605         int i;
5606
5607         i = seed % squaresLeft[shade];
5608         nrOfShuffles *= squaresLeft[shade];
5609         seed /= squaresLeft[shade];
5610         put(board, pieceType, rank, i, shade);
5611 }
5612
5613 void
5614 AddTwoPieces (Board board, int pieceType, int rank)
5615 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5616 {
5617         int i, n=squaresLeft[ANY], j=n-1, k;
5618
5619         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5620         i = seed % k;  // pick one
5621         nrOfShuffles *= k;
5622         seed /= k;
5623         while(i >= j) i -= j--;
5624         j = n - 1 - j; i += j;
5625         put(board, pieceType, rank, j, ANY);
5626         put(board, pieceType, rank, i, ANY);
5627 }
5628
5629 void
5630 SetUpShuffle (Board board, int number)
5631 {
5632         int i, p, first=1;
5633
5634         GetPositionNumber(); nrOfShuffles = 1;
5635
5636         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5637         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5638         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5639
5640         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5641
5642         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5643             p = (int) board[0][i];
5644             if(p < (int) BlackPawn) piecesLeft[p] ++;
5645             board[0][i] = EmptySquare;
5646         }
5647
5648         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5649             // shuffles restricted to allow normal castling put KRR first
5650             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5651                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5652             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5653                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5654             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5655                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5656             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5657                 put(board, WhiteRook, 0, 0, ANY);
5658             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5659         }
5660
5661         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5662             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5663             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5664                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5665                 while(piecesLeft[p] >= 2) {
5666                     AddOnePiece(board, p, 0, LITE);
5667                     AddOnePiece(board, p, 0, DARK);
5668                 }
5669                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5670             }
5671
5672         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5673             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5674             // but we leave King and Rooks for last, to possibly obey FRC restriction
5675             if(p == (int)WhiteRook) continue;
5676             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5677             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5678         }
5679
5680         // now everything is placed, except perhaps King (Unicorn) and Rooks
5681
5682         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5683             // Last King gets castling rights
5684             while(piecesLeft[(int)WhiteUnicorn]) {
5685                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5686                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5687             }
5688
5689             while(piecesLeft[(int)WhiteKing]) {
5690                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5691                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5692             }
5693
5694
5695         } else {
5696             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5697             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5698         }
5699
5700         // Only Rooks can be left; simply place them all
5701         while(piecesLeft[(int)WhiteRook]) {
5702                 i = put(board, WhiteRook, 0, 0, ANY);
5703                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5704                         if(first) {
5705                                 first=0;
5706                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5707                         }
5708                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5709                 }
5710         }
5711         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5712             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5713         }
5714
5715         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5716 }
5717
5718 int
5719 SetCharTable (char *table, const char * map)
5720 /* [HGM] moved here from winboard.c because of its general usefulness */
5721 /*       Basically a safe strcpy that uses the last character as King */
5722 {
5723     int result = FALSE; int NrPieces;
5724
5725     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5726                     && NrPieces >= 12 && !(NrPieces&1)) {
5727         int i; /* [HGM] Accept even length from 12 to 34 */
5728
5729         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5730         for( i=0; i<NrPieces/2-1; i++ ) {
5731             table[i] = map[i];
5732             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5733         }
5734         table[(int) WhiteKing]  = map[NrPieces/2-1];
5735         table[(int) BlackKing]  = map[NrPieces-1];
5736
5737         result = TRUE;
5738     }
5739
5740     return result;
5741 }
5742
5743 void
5744 Prelude (Board board)
5745 {       // [HGM] superchess: random selection of exo-pieces
5746         int i, j, k; ChessSquare p;
5747         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5748
5749         GetPositionNumber(); // use FRC position number
5750
5751         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5752             SetCharTable(pieceToChar, appData.pieceToCharTable);
5753             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5754                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5755         }
5756
5757         j = seed%4;                 seed /= 4;
5758         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5759         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5760         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5761         j = seed%3 + (seed%3 >= j); seed /= 3;
5762         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5763         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5764         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5765         j = seed%3;                 seed /= 3;
5766         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5767         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5768         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5769         j = seed%2 + (seed%2 >= j); seed /= 2;
5770         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5771         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5772         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5773         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5774         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5775         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5776         put(board, exoPieces[0],    0, 0, ANY);
5777         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5778 }
5779
5780 void
5781 InitPosition (int redraw)
5782 {
5783     ChessSquare (* pieces)[BOARD_FILES];
5784     int i, j, pawnRow, overrule,
5785     oldx = gameInfo.boardWidth,
5786     oldy = gameInfo.boardHeight,
5787     oldh = gameInfo.holdingsWidth;
5788     static int oldv;
5789
5790     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5791
5792     /* [AS] Initialize pv info list [HGM] and game status */
5793     {
5794         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5795             pvInfoList[i].depth = 0;
5796             boards[i][EP_STATUS] = EP_NONE;
5797             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5798         }
5799
5800         initialRulePlies = 0; /* 50-move counter start */
5801
5802         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5803         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5804     }
5805
5806
5807     /* [HGM] logic here is completely changed. In stead of full positions */
5808     /* the initialized data only consist of the two backranks. The switch */
5809     /* selects which one we will use, which is than copied to the Board   */
5810     /* initialPosition, which for the rest is initialized by Pawns and    */
5811     /* empty squares. This initial position is then copied to boards[0],  */
5812     /* possibly after shuffling, so that it remains available.            */
5813
5814     gameInfo.holdingsWidth = 0; /* default board sizes */
5815     gameInfo.boardWidth    = 8;
5816     gameInfo.boardHeight   = 8;
5817     gameInfo.holdingsSize  = 0;
5818     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5819     for(i=0; i<BOARD_FILES-2; i++)
5820       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5821     initialPosition[EP_STATUS] = EP_NONE;
5822     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5823     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5824          SetCharTable(pieceNickName, appData.pieceNickNames);
5825     else SetCharTable(pieceNickName, "............");
5826     pieces = FIDEArray;
5827
5828     switch (gameInfo.variant) {
5829     case VariantFischeRandom:
5830       shuffleOpenings = TRUE;
5831     default:
5832       break;
5833     case VariantShatranj:
5834       pieces = ShatranjArray;
5835       nrCastlingRights = 0;
5836       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5837       break;
5838     case VariantMakruk:
5839       pieces = makrukArray;
5840       nrCastlingRights = 0;
5841       startedFromSetupPosition = TRUE;
5842       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5843       break;
5844     case VariantTwoKings:
5845       pieces = twoKingsArray;
5846       break;
5847     case VariantGrand:
5848       pieces = GrandArray;
5849       nrCastlingRights = 0;
5850       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5851       gameInfo.boardWidth = 10;
5852       gameInfo.boardHeight = 10;
5853       gameInfo.holdingsSize = 7;
5854       break;
5855     case VariantCapaRandom:
5856       shuffleOpenings = TRUE;
5857     case VariantCapablanca:
5858       pieces = CapablancaArray;
5859       gameInfo.boardWidth = 10;
5860       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5861       break;
5862     case VariantGothic:
5863       pieces = GothicArray;
5864       gameInfo.boardWidth = 10;
5865       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5866       break;
5867     case VariantSChess:
5868       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5869       gameInfo.holdingsSize = 7;
5870       break;
5871     case VariantJanus:
5872       pieces = JanusArray;
5873       gameInfo.boardWidth = 10;
5874       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5875       nrCastlingRights = 6;
5876         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5877         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5878         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5879         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5880         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5881         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5882       break;
5883     case VariantFalcon:
5884       pieces = FalconArray;
5885       gameInfo.boardWidth = 10;
5886       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5887       break;
5888     case VariantXiangqi:
5889       pieces = XiangqiArray;
5890       gameInfo.boardWidth  = 9;
5891       gameInfo.boardHeight = 10;
5892       nrCastlingRights = 0;
5893       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5894       break;
5895     case VariantShogi:
5896       pieces = ShogiArray;
5897       gameInfo.boardWidth  = 9;
5898       gameInfo.boardHeight = 9;
5899       gameInfo.holdingsSize = 7;
5900       nrCastlingRights = 0;
5901       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5902       break;
5903     case VariantCourier:
5904       pieces = CourierArray;
5905       gameInfo.boardWidth  = 12;
5906       nrCastlingRights = 0;
5907       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5908       break;
5909     case VariantKnightmate:
5910       pieces = KnightmateArray;
5911       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5912       break;
5913     case VariantSpartan:
5914       pieces = SpartanArray;
5915       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5916       break;
5917     case VariantFairy:
5918       pieces = fairyArray;
5919       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5920       break;
5921     case VariantGreat:
5922       pieces = GreatArray;
5923       gameInfo.boardWidth = 10;
5924       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5925       gameInfo.holdingsSize = 8;
5926       break;
5927     case VariantSuper:
5928       pieces = FIDEArray;
5929       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5930       gameInfo.holdingsSize = 8;
5931       startedFromSetupPosition = TRUE;
5932       break;
5933     case VariantCrazyhouse:
5934     case VariantBughouse:
5935       pieces = FIDEArray;
5936       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5937       gameInfo.holdingsSize = 5;
5938       break;
5939     case VariantWildCastle:
5940       pieces = FIDEArray;
5941       /* !!?shuffle with kings guaranteed to be on d or e file */
5942       shuffleOpenings = 1;
5943       break;
5944     case VariantNoCastle:
5945       pieces = FIDEArray;
5946       nrCastlingRights = 0;
5947       /* !!?unconstrained back-rank shuffle */
5948       shuffleOpenings = 1;
5949       break;
5950     }
5951
5952     overrule = 0;
5953     if(appData.NrFiles >= 0) {
5954         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5955         gameInfo.boardWidth = appData.NrFiles;
5956     }
5957     if(appData.NrRanks >= 0) {
5958         gameInfo.boardHeight = appData.NrRanks;
5959     }
5960     if(appData.holdingsSize >= 0) {
5961         i = appData.holdingsSize;
5962         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5963         gameInfo.holdingsSize = i;
5964     }
5965     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5966     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5967         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5968
5969     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5970     if(pawnRow < 1) pawnRow = 1;
5971     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5972
5973     /* User pieceToChar list overrules defaults */
5974     if(appData.pieceToCharTable != NULL)
5975         SetCharTable(pieceToChar, appData.pieceToCharTable);
5976
5977     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5978
5979         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5980             s = (ChessSquare) 0; /* account holding counts in guard band */
5981         for( i=0; i<BOARD_HEIGHT; i++ )
5982             initialPosition[i][j] = s;
5983
5984         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5985         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5986         initialPosition[pawnRow][j] = WhitePawn;
5987         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5988         if(gameInfo.variant == VariantXiangqi) {
5989             if(j&1) {
5990                 initialPosition[pawnRow][j] =
5991                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5992                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5993                    initialPosition[2][j] = WhiteCannon;
5994                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5995                 }
5996             }
5997         }
5998         if(gameInfo.variant == VariantGrand) {
5999             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6000                initialPosition[0][j] = WhiteRook;
6001                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6002             }
6003         }
6004         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6005     }
6006     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6007
6008             j=BOARD_LEFT+1;
6009             initialPosition[1][j] = WhiteBishop;
6010             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6011             j=BOARD_RGHT-2;
6012             initialPosition[1][j] = WhiteRook;
6013             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6014     }
6015
6016     if( nrCastlingRights == -1) {
6017         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6018         /*       This sets default castling rights from none to normal corners   */
6019         /* Variants with other castling rights must set them themselves above    */
6020         nrCastlingRights = 6;
6021
6022         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6023         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6024         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6025         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6026         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6027         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6028      }
6029
6030      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6031      if(gameInfo.variant == VariantGreat) { // promotion commoners
6032         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6033         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6034         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6035         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6036      }
6037      if( gameInfo.variant == VariantSChess ) {
6038       initialPosition[1][0] = BlackMarshall;
6039       initialPosition[2][0] = BlackAngel;
6040       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6041       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6042       initialPosition[1][1] = initialPosition[2][1] = 
6043       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6044      }
6045   if (appData.debugMode) {
6046     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6047   }
6048     if(shuffleOpenings) {
6049         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6050         startedFromSetupPosition = TRUE;
6051     }
6052     if(startedFromPositionFile) {
6053       /* [HGM] loadPos: use PositionFile for every new game */
6054       CopyBoard(initialPosition, filePosition);
6055       for(i=0; i<nrCastlingRights; i++)
6056           initialRights[i] = filePosition[CASTLING][i];
6057       startedFromSetupPosition = TRUE;
6058     }
6059
6060     CopyBoard(boards[0], initialPosition);
6061
6062     if(oldx != gameInfo.boardWidth ||
6063        oldy != gameInfo.boardHeight ||
6064        oldv != gameInfo.variant ||
6065        oldh != gameInfo.holdingsWidth
6066                                          )
6067             InitDrawingSizes(-2 ,0);
6068
6069     oldv = gameInfo.variant;
6070     if (redraw)
6071       DrawPosition(TRUE, boards[currentMove]);
6072 }
6073
6074 void
6075 SendBoard (ChessProgramState *cps, int moveNum)
6076 {
6077     char message[MSG_SIZ];
6078
6079     if (cps->useSetboard) {
6080       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6081       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6082       SendToProgram(message, cps);
6083       free(fen);
6084
6085     } else {
6086       ChessSquare *bp;
6087       int i, j, left=0, right=BOARD_WIDTH;
6088       /* Kludge to set black to move, avoiding the troublesome and now
6089        * deprecated "black" command.
6090        */
6091       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6092         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6093
6094       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6095
6096       SendToProgram("edit\n", cps);
6097       SendToProgram("#\n", cps);
6098       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6099         bp = &boards[moveNum][i][left];
6100         for (j = left; j < right; j++, bp++) {
6101           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6102           if ((int) *bp < (int) BlackPawn) {
6103             if(j == BOARD_RGHT+1)
6104                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6105             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6106             if(message[0] == '+' || message[0] == '~') {
6107               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6108                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6109                         AAA + j, ONE + i);
6110             }
6111             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6112                 message[1] = BOARD_RGHT   - 1 - j + '1';
6113                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6114             }
6115             SendToProgram(message, cps);
6116           }
6117         }
6118       }
6119
6120       SendToProgram("c\n", cps);
6121       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6122         bp = &boards[moveNum][i][left];
6123         for (j = left; j < right; j++, bp++) {
6124           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6125           if (((int) *bp != (int) EmptySquare)
6126               && ((int) *bp >= (int) BlackPawn)) {
6127             if(j == BOARD_LEFT-2)
6128                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6129             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6130                     AAA + j, ONE + i);
6131             if(message[0] == '+' || message[0] == '~') {
6132               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6133                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6134                         AAA + j, ONE + i);
6135             }
6136             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6137                 message[1] = BOARD_RGHT   - 1 - j + '1';
6138                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6139             }
6140             SendToProgram(message, cps);
6141           }
6142         }
6143       }
6144
6145       SendToProgram(".\n", cps);
6146     }
6147     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6148 }
6149
6150 char exclusionHeader[MSG_SIZ];
6151 int exCnt, excludePtr;
6152 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6153 static Exclusion excluTab[200];
6154 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6155
6156 static void
6157 WriteMap (int s)
6158 {
6159     int j;
6160     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6161     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6162 }
6163
6164 static void
6165 ClearMap ()
6166 {
6167     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6168     excludePtr = 24; exCnt = 0;
6169     WriteMap(0);
6170 }
6171
6172 static void
6173 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6174 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6175     char buf[2*MOVE_LEN], *p;
6176     Exclusion *e = excluTab;
6177     int i;
6178     for(i=0; i<exCnt; i++)
6179         if(e[i].ff == fromX && e[i].fr == fromY &&
6180            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6181     if(i == exCnt) { // was not in exclude list; add it
6182         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6183         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6184             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6185             return; // abort
6186         }
6187         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6188         excludePtr++; e[i].mark = excludePtr++;
6189         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6190         exCnt++;
6191     }
6192     exclusionHeader[e[i].mark] = state;
6193 }
6194
6195 static int
6196 ExcludeOneMove (int fromY, int fromX, int toY, int toX, signed char promoChar, char state)
6197 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6198     char buf[MSG_SIZ];
6199     int j, k;
6200     ChessMove moveType;
6201     if(promoChar == -1) { // kludge to indicate best move
6202         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6203             return 1; // if unparsable, abort
6204     }
6205     // update exclusion map (resolving toggle by consulting existing state)
6206     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6207     j = k%8; k >>= 3;
6208     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6209     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6210          excludeMap[k] |=   1<<j;
6211     else excludeMap[k] &= ~(1<<j);
6212     // update header
6213     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6214     // inform engine
6215     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6216     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6217     SendToProgram(buf, &first);
6218     return (state == '+');
6219 }
6220
6221 static void
6222 ExcludeClick (int index)
6223 {
6224     int i, j;
6225     Exclusion *e = excluTab;
6226     if(index < 25) { // none, best or tail clicked
6227         if(index < 13) { // none: include all
6228             WriteMap(0); // clear map
6229             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6230             SendToProgram("include all\n", &first); // and inform engine
6231         } else if(index > 18) { // tail
6232             if(exclusionHeader[19] == '-') { // tail was excluded
6233                 SendToProgram("include all\n", &first);
6234                 WriteMap(0); // clear map completely
6235                 // now re-exclude selected moves
6236                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6237                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6238             } else { // tail was included or in mixed state
6239                 SendToProgram("exclude all\n", &first);
6240                 WriteMap(0xFF); // fill map completely
6241                 // now re-include selected moves
6242                 j = 0; // count them
6243                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6244                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6245                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6246             }
6247         } else { // best
6248             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6249         }
6250     } else {
6251         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6252             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6253             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6254             break;
6255         }
6256     }
6257 }
6258
6259 ChessSquare
6260 DefaultPromoChoice (int white)
6261 {
6262     ChessSquare result;
6263     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6264         result = WhiteFerz; // no choice
6265     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6266         result= WhiteKing; // in Suicide Q is the last thing we want
6267     else if(gameInfo.variant == VariantSpartan)
6268         result = white ? WhiteQueen : WhiteAngel;
6269     else result = WhiteQueen;
6270     if(!white) result = WHITE_TO_BLACK result;
6271     return result;
6272 }
6273
6274 static int autoQueen; // [HGM] oneclick
6275
6276 int
6277 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6278 {
6279     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6280     /* [HGM] add Shogi promotions */
6281     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6282     ChessSquare piece;
6283     ChessMove moveType;
6284     Boolean premove;
6285
6286     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6287     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6288
6289     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6290       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6291         return FALSE;
6292
6293     piece = boards[currentMove][fromY][fromX];
6294     if(gameInfo.variant == VariantShogi) {
6295         promotionZoneSize = BOARD_HEIGHT/3;
6296         highestPromotingPiece = (int)WhiteFerz;
6297     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6298         promotionZoneSize = 3;
6299     }
6300
6301     // Treat Lance as Pawn when it is not representing Amazon
6302     if(gameInfo.variant != VariantSuper) {
6303         if(piece == WhiteLance) piece = WhitePawn; else
6304         if(piece == BlackLance) piece = BlackPawn;
6305     }
6306
6307     // next weed out all moves that do not touch the promotion zone at all
6308     if((int)piece >= BlackPawn) {
6309         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6310              return FALSE;
6311         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6312     } else {
6313         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6314            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6315     }
6316
6317     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6318
6319     // weed out mandatory Shogi promotions
6320     if(gameInfo.variant == VariantShogi) {
6321         if(piece >= BlackPawn) {
6322             if(toY == 0 && piece == BlackPawn ||
6323                toY == 0 && piece == BlackQueen ||
6324                toY <= 1 && piece == BlackKnight) {
6325                 *promoChoice = '+';
6326                 return FALSE;
6327             }
6328         } else {
6329             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6330                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6331                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6332                 *promoChoice = '+';
6333                 return FALSE;
6334             }
6335         }
6336     }
6337
6338     // weed out obviously illegal Pawn moves
6339     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6340         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6341         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6342         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6343         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6344         // note we are not allowed to test for valid (non-)capture, due to premove
6345     }
6346
6347     // we either have a choice what to promote to, or (in Shogi) whether to promote
6348     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6349         *promoChoice = PieceToChar(BlackFerz);  // no choice
6350         return FALSE;
6351     }
6352     // no sense asking what we must promote to if it is going to explode...
6353     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6354         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6355         return FALSE;
6356     }
6357     // give caller the default choice even if we will not make it
6358     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6359     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6360     if(        sweepSelect && gameInfo.variant != VariantGreat
6361                            && gameInfo.variant != VariantGrand
6362                            && gameInfo.variant != VariantSuper) return FALSE;
6363     if(autoQueen) return FALSE; // predetermined
6364
6365     // suppress promotion popup on illegal moves that are not premoves
6366     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6367               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6368     if(appData.testLegality && !premove) {
6369         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6370                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6371         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6372             return FALSE;
6373     }
6374
6375     return TRUE;
6376 }
6377
6378 int
6379 InPalace (int row, int column)
6380 {   /* [HGM] for Xiangqi */
6381     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6382          column < (BOARD_WIDTH + 4)/2 &&
6383          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6384     return FALSE;
6385 }
6386
6387 int
6388 PieceForSquare (int x, int y)
6389 {
6390   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6391      return -1;
6392   else
6393      return boards[currentMove][y][x];
6394 }
6395
6396 int
6397 OKToStartUserMove (int x, int y)
6398 {
6399     ChessSquare from_piece;
6400     int white_piece;
6401
6402     if (matchMode) return FALSE;
6403     if (gameMode == EditPosition) return TRUE;
6404
6405     if (x >= 0 && y >= 0)
6406       from_piece = boards[currentMove][y][x];
6407     else
6408       from_piece = EmptySquare;
6409
6410     if (from_piece == EmptySquare) return FALSE;
6411
6412     white_piece = (int)from_piece >= (int)WhitePawn &&
6413       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6414
6415     switch (gameMode) {
6416       case AnalyzeFile:
6417       case TwoMachinesPlay:
6418       case EndOfGame:
6419         return FALSE;
6420
6421       case IcsObserving:
6422       case IcsIdle:
6423         return FALSE;
6424
6425       case MachinePlaysWhite:
6426       case IcsPlayingBlack:
6427         if (appData.zippyPlay) return FALSE;
6428         if (white_piece) {
6429             DisplayMoveError(_("You are playing Black"));
6430             return FALSE;
6431         }
6432         break;
6433
6434       case MachinePlaysBlack:
6435       case IcsPlayingWhite:
6436         if (appData.zippyPlay) return FALSE;
6437         if (!white_piece) {
6438             DisplayMoveError(_("You are playing White"));
6439             return FALSE;
6440         }
6441         break;
6442
6443       case PlayFromGameFile:
6444             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6445       case EditGame:
6446         if (!white_piece && WhiteOnMove(currentMove)) {
6447             DisplayMoveError(_("It is White's turn"));
6448             return FALSE;
6449         }
6450         if (white_piece && !WhiteOnMove(currentMove)) {
6451             DisplayMoveError(_("It is Black's turn"));
6452             return FALSE;
6453         }
6454         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6455             /* Editing correspondence game history */
6456             /* Could disallow this or prompt for confirmation */
6457             cmailOldMove = -1;
6458         }
6459         break;
6460
6461       case BeginningOfGame:
6462         if (appData.icsActive) return FALSE;
6463         if (!appData.noChessProgram) {
6464             if (!white_piece) {
6465                 DisplayMoveError(_("You are playing White"));
6466                 return FALSE;
6467             }
6468         }
6469         break;
6470
6471       case Training:
6472         if (!white_piece && WhiteOnMove(currentMove)) {
6473             DisplayMoveError(_("It is White's turn"));
6474             return FALSE;
6475         }
6476         if (white_piece && !WhiteOnMove(currentMove)) {
6477             DisplayMoveError(_("It is Black's turn"));
6478             return FALSE;
6479         }
6480         break;
6481
6482       default:
6483       case IcsExamining:
6484         break;
6485     }
6486     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6487         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6488         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6489         && gameMode != AnalyzeFile && gameMode != Training) {
6490         DisplayMoveError(_("Displayed position is not current"));
6491         return FALSE;
6492     }
6493     return TRUE;
6494 }
6495
6496 Boolean
6497 OnlyMove (int *x, int *y, Boolean captures) 
6498 {
6499     DisambiguateClosure cl;
6500     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6501     switch(gameMode) {
6502       case MachinePlaysBlack:
6503       case IcsPlayingWhite:
6504       case BeginningOfGame:
6505         if(!WhiteOnMove(currentMove)) return FALSE;
6506         break;
6507       case MachinePlaysWhite:
6508       case IcsPlayingBlack:
6509         if(WhiteOnMove(currentMove)) return FALSE;
6510         break;
6511       case EditGame:
6512         break;
6513       default:
6514         return FALSE;
6515     }
6516     cl.pieceIn = EmptySquare;
6517     cl.rfIn = *y;
6518     cl.ffIn = *x;
6519     cl.rtIn = -1;
6520     cl.ftIn = -1;
6521     cl.promoCharIn = NULLCHAR;
6522     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6523     if( cl.kind == NormalMove ||
6524         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6525         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6526         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6527       fromX = cl.ff;
6528       fromY = cl.rf;
6529       *x = cl.ft;
6530       *y = cl.rt;
6531       return TRUE;
6532     }
6533     if(cl.kind != ImpossibleMove) return FALSE;
6534     cl.pieceIn = EmptySquare;
6535     cl.rfIn = -1;
6536     cl.ffIn = -1;
6537     cl.rtIn = *y;
6538     cl.ftIn = *x;
6539     cl.promoCharIn = NULLCHAR;
6540     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6541     if( cl.kind == NormalMove ||
6542         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6543         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6544         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6545       fromX = cl.ff;
6546       fromY = cl.rf;
6547       *x = cl.ft;
6548       *y = cl.rt;
6549       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6550       return TRUE;
6551     }
6552     return FALSE;
6553 }
6554
6555 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6556 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6557 int lastLoadGameUseList = FALSE;
6558 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6559 ChessMove lastLoadGameStart = EndOfFile;
6560 int doubleClick;
6561
6562 void
6563 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6564 {
6565     ChessMove moveType;
6566     ChessSquare pdown, pup;
6567     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6568
6569
6570     /* Check if the user is playing in turn.  This is complicated because we
6571        let the user "pick up" a piece before it is his turn.  So the piece he
6572        tried to pick up may have been captured by the time he puts it down!
6573        Therefore we use the color the user is supposed to be playing in this
6574        test, not the color of the piece that is currently on the starting
6575        square---except in EditGame mode, where the user is playing both
6576        sides; fortunately there the capture race can't happen.  (It can
6577        now happen in IcsExamining mode, but that's just too bad.  The user
6578        will get a somewhat confusing message in that case.)
6579        */
6580
6581     switch (gameMode) {
6582       case AnalyzeFile:
6583       case TwoMachinesPlay:
6584       case EndOfGame:
6585       case IcsObserving:
6586       case IcsIdle:
6587         /* We switched into a game mode where moves are not accepted,
6588            perhaps while the mouse button was down. */
6589         return;
6590
6591       case MachinePlaysWhite:
6592         /* User is moving for Black */
6593         if (WhiteOnMove(currentMove)) {
6594             DisplayMoveError(_("It is White's turn"));
6595             return;
6596         }
6597         break;
6598
6599       case MachinePlaysBlack:
6600         /* User is moving for White */
6601         if (!WhiteOnMove(currentMove)) {
6602             DisplayMoveError(_("It is Black's turn"));
6603             return;
6604         }
6605         break;
6606
6607       case PlayFromGameFile:
6608             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6609       case EditGame:
6610       case IcsExamining:
6611       case BeginningOfGame:
6612       case AnalyzeMode:
6613       case Training:
6614         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6615         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6616             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6617             /* User is moving for Black */
6618             if (WhiteOnMove(currentMove)) {
6619                 DisplayMoveError(_("It is White's turn"));
6620                 return;
6621             }
6622         } else {
6623             /* User is moving for White */
6624             if (!WhiteOnMove(currentMove)) {
6625                 DisplayMoveError(_("It is Black's turn"));
6626                 return;
6627             }
6628         }
6629         break;
6630
6631       case IcsPlayingBlack:
6632         /* User is moving for Black */
6633         if (WhiteOnMove(currentMove)) {
6634             if (!appData.premove) {
6635                 DisplayMoveError(_("It is White'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       case IcsPlayingWhite:
6653         /* User is moving for White */
6654         if (!WhiteOnMove(currentMove)) {
6655             if (!appData.premove) {
6656                 DisplayMoveError(_("It is Black's turn"));
6657             } else if (toX >= 0 && toY >= 0) {
6658                 premoveToX = toX;
6659                 premoveToY = toY;
6660                 premoveFromX = fromX;
6661                 premoveFromY = fromY;
6662                 premovePromoChar = promoChar;
6663                 gotPremove = 1;
6664                 if (appData.debugMode)
6665                     fprintf(debugFP, "Got premove: fromX %d,"
6666                             "fromY %d, toX %d, toY %d\n",
6667                             fromX, fromY, toX, toY);
6668             }
6669             return;
6670         }
6671         break;
6672
6673       default:
6674         break;
6675
6676       case EditPosition:
6677         /* EditPosition, empty square, or different color piece;
6678            click-click move is possible */
6679         if (toX == -2 || toY == -2) {
6680             boards[0][fromY][fromX] = EmptySquare;
6681             DrawPosition(FALSE, boards[currentMove]);
6682             return;
6683         } else if (toX >= 0 && toY >= 0) {
6684             boards[0][toY][toX] = boards[0][fromY][fromX];
6685             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6686                 if(boards[0][fromY][0] != EmptySquare) {
6687                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6688                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6689                 }
6690             } else
6691             if(fromX == BOARD_RGHT+1) {
6692                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6693                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6694                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6695                 }
6696             } else
6697             boards[0][fromY][fromX] = gatingPiece;
6698             DrawPosition(FALSE, boards[currentMove]);
6699             return;
6700         }
6701         return;
6702     }
6703
6704     if(toX < 0 || toY < 0) return;
6705     pdown = boards[currentMove][fromY][fromX];
6706     pup = boards[currentMove][toY][toX];
6707
6708     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6709     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6710          if( pup != EmptySquare ) return;
6711          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6712            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6713                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6714            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6715            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6716            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6717            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6718          fromY = DROP_RANK;
6719     }
6720
6721     /* [HGM] always test for legality, to get promotion info */
6722     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6723                                          fromY, fromX, toY, toX, promoChar);
6724
6725     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6726
6727     /* [HGM] but possibly ignore an IllegalMove result */
6728     if (appData.testLegality) {
6729         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6730             DisplayMoveError(_("Illegal move"));
6731             return;
6732         }
6733     }
6734
6735     if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6736         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6737              ClearPremoveHighlights(); // was included
6738         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6739         return;
6740     }
6741
6742     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6743 }
6744
6745 /* Common tail of UserMoveEvent and DropMenuEvent */
6746 int
6747 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6748 {
6749     char *bookHit = 0;
6750
6751     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6752         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6753         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6754         if(WhiteOnMove(currentMove)) {
6755             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6756         } else {
6757             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6758         }
6759     }
6760
6761     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6762        move type in caller when we know the move is a legal promotion */
6763     if(moveType == NormalMove && promoChar)
6764         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6765
6766     /* [HGM] <popupFix> The following if has been moved here from
6767        UserMoveEvent(). Because it seemed to belong here (why not allow
6768        piece drops in training games?), and because it can only be
6769        performed after it is known to what we promote. */
6770     if (gameMode == Training) {
6771       /* compare the move played on the board to the next move in the
6772        * game. If they match, display the move and the opponent's response.
6773        * If they don't match, display an error message.
6774        */
6775       int saveAnimate;
6776       Board testBoard;
6777       CopyBoard(testBoard, boards[currentMove]);
6778       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6779
6780       if (CompareBoards(testBoard, boards[currentMove+1])) {
6781         ForwardInner(currentMove+1);
6782
6783         /* Autoplay the opponent's response.
6784          * if appData.animate was TRUE when Training mode was entered,
6785          * the response will be animated.
6786          */
6787         saveAnimate = appData.animate;
6788         appData.animate = animateTraining;
6789         ForwardInner(currentMove+1);
6790         appData.animate = saveAnimate;
6791
6792         /* check for the end of the game */
6793         if (currentMove >= forwardMostMove) {
6794           gameMode = PlayFromGameFile;
6795           ModeHighlight();
6796           SetTrainingModeOff();
6797           DisplayInformation(_("End of game"));
6798         }
6799       } else {
6800         DisplayError(_("Incorrect move"), 0);
6801       }
6802       return 1;
6803     }
6804
6805   /* Ok, now we know that the move is good, so we can kill
6806      the previous line in Analysis Mode */
6807   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6808                                 && currentMove < forwardMostMove) {
6809     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6810     else forwardMostMove = currentMove;
6811   }
6812
6813   ClearMap();
6814
6815   /* If we need the chess program but it's dead, restart it */
6816   ResurrectChessProgram();
6817
6818   /* A user move restarts a paused game*/
6819   if (pausing)
6820     PauseEvent();
6821
6822   thinkOutput[0] = NULLCHAR;
6823
6824   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6825
6826   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6827     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6828     return 1;
6829   }
6830
6831   if (gameMode == BeginningOfGame) {
6832     if (appData.noChessProgram) {
6833       gameMode = EditGame;
6834       SetGameInfo();
6835     } else {
6836       char buf[MSG_SIZ];
6837       gameMode = MachinePlaysBlack;
6838       StartClocks();
6839       SetGameInfo();
6840       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6841       DisplayTitle(buf);
6842       if (first.sendName) {
6843         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6844         SendToProgram(buf, &first);
6845       }
6846       StartClocks();
6847     }
6848     ModeHighlight();
6849   }
6850
6851   /* Relay move to ICS or chess engine */
6852   if (appData.icsActive) {
6853     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6854         gameMode == IcsExamining) {
6855       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6856         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6857         SendToICS("draw ");
6858         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6859       }
6860       // also send plain move, in case ICS does not understand atomic claims
6861       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6862       ics_user_moved = 1;
6863     }
6864   } else {
6865     if (first.sendTime && (gameMode == BeginningOfGame ||
6866                            gameMode == MachinePlaysWhite ||
6867                            gameMode == MachinePlaysBlack)) {
6868       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6869     }
6870     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6871          // [HGM] book: if program might be playing, let it use book
6872         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6873         first.maybeThinking = TRUE;
6874     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6875         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6876         SendBoard(&first, currentMove+1);
6877     } else SendMoveToProgram(forwardMostMove-1, &first);
6878     if (currentMove == cmailOldMove + 1) {
6879       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6880     }
6881   }
6882
6883   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6884
6885   switch (gameMode) {
6886   case EditGame:
6887     if(appData.testLegality)
6888     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6889     case MT_NONE:
6890     case MT_CHECK:
6891       break;
6892     case MT_CHECKMATE:
6893     case MT_STAINMATE:
6894       if (WhiteOnMove(currentMove)) {
6895         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6896       } else {
6897         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6898       }
6899       break;
6900     case MT_STALEMATE:
6901       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6902       break;
6903     }
6904     break;
6905
6906   case MachinePlaysBlack:
6907   case MachinePlaysWhite:
6908     /* disable certain menu options while machine is thinking */
6909     SetMachineThinkingEnables();
6910     break;
6911
6912   default:
6913     break;
6914   }
6915
6916   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6917   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6918
6919   if(bookHit) { // [HGM] book: simulate book reply
6920         static char bookMove[MSG_SIZ]; // a bit generous?
6921
6922         programStats.nodes = programStats.depth = programStats.time =
6923         programStats.score = programStats.got_only_move = 0;
6924         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6925
6926         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6927         strcat(bookMove, bookHit);
6928         HandleMachineMove(bookMove, &first);
6929   }
6930   return 1;
6931 }
6932
6933 void
6934 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6935 {
6936     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6937     Markers *m = (Markers *) closure;
6938     if(rf == fromY && ff == fromX)
6939         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6940                          || kind == WhiteCapturesEnPassant
6941                          || kind == BlackCapturesEnPassant);
6942     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6943 }
6944
6945 void
6946 MarkTargetSquares (int clear)
6947 {
6948   int x, y;
6949   if(clear) // no reason to ever suppress clearing
6950     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6951   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6952      !appData.testLegality || gameMode == EditPosition) return;
6953   if(!clear) {
6954     int capt = 0;
6955     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6956     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6957       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6958       if(capt)
6959       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6960     }
6961   }
6962   DrawPosition(FALSE, NULL);
6963 }
6964
6965 int
6966 Explode (Board board, int fromX, int fromY, int toX, int toY)
6967 {
6968     if(gameInfo.variant == VariantAtomic &&
6969        (board[toY][toX] != EmptySquare ||                     // capture?
6970         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6971                          board[fromY][fromX] == BlackPawn   )
6972       )) {
6973         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6974         return TRUE;
6975     }
6976     return FALSE;
6977 }
6978
6979 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6980
6981 int
6982 CanPromote (ChessSquare piece, int y)
6983 {
6984         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6985         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6986         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6987            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6988            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6989                                                   gameInfo.variant == VariantMakruk) return FALSE;
6990         return (piece == BlackPawn && y == 1 ||
6991                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6992                 piece == BlackLance && y == 1 ||
6993                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6994 }
6995
6996 void
6997 LeftClick (ClickType clickType, int xPix, int yPix)
6998 {
6999     int x, y;
7000     Boolean saveAnimate;
7001     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7002     char promoChoice = NULLCHAR;
7003     ChessSquare piece;
7004     static TimeMark lastClickTime, prevClickTime;
7005
7006     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7007
7008     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7009
7010     if (clickType == Press) ErrorPopDown();
7011
7012     x = EventToSquare(xPix, BOARD_WIDTH);
7013     y = EventToSquare(yPix, BOARD_HEIGHT);
7014     if (!flipView && y >= 0) {
7015         y = BOARD_HEIGHT - 1 - y;
7016     }
7017     if (flipView && x >= 0) {
7018         x = BOARD_WIDTH - 1 - x;
7019     }
7020
7021     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7022         defaultPromoChoice = promoSweep;
7023         promoSweep = EmptySquare;   // terminate sweep
7024         promoDefaultAltered = TRUE;
7025         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7026     }
7027
7028     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7029         if(clickType == Release) return; // ignore upclick of click-click destination
7030         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7031         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7032         if(gameInfo.holdingsWidth &&
7033                 (WhiteOnMove(currentMove)
7034                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7035                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7036             // click in right holdings, for determining promotion piece
7037             ChessSquare p = boards[currentMove][y][x];
7038             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7039             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7040             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7041                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7042                 fromX = fromY = -1;
7043                 return;
7044             }
7045         }
7046         DrawPosition(FALSE, boards[currentMove]);
7047         return;
7048     }
7049
7050     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7051     if(clickType == Press
7052             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7053               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7054               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7055         return;
7056
7057     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7058         // could be static click on premove from-square: abort premove
7059         gotPremove = 0;
7060         ClearPremoveHighlights();
7061     }
7062
7063     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7064         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7065
7066     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7067         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7068                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7069         defaultPromoChoice = DefaultPromoChoice(side);
7070     }
7071
7072     autoQueen = appData.alwaysPromoteToQueen;
7073
7074     if (fromX == -1) {
7075       int originalY = y;
7076       gatingPiece = EmptySquare;
7077       if (clickType != Press) {
7078         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7079             DragPieceEnd(xPix, yPix); dragging = 0;
7080             DrawPosition(FALSE, NULL);
7081         }
7082         return;
7083       }
7084       doubleClick = FALSE;
7085       fromX = x; fromY = y; toX = toY = -1;
7086       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7087          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7088          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7089             /* First square */
7090             if (OKToStartUserMove(fromX, fromY)) {
7091                 second = 0;
7092                 MarkTargetSquares(0);
7093                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7094                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7095                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7096                     promoSweep = defaultPromoChoice;
7097                     selectFlag = 0; lastX = xPix; lastY = yPix;
7098                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7099                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7100                 }
7101                 if (appData.highlightDragging) {
7102                     SetHighlights(fromX, fromY, -1, -1);
7103                 } else {
7104                     ClearHighlights();
7105                 }
7106             } else fromX = fromY = -1;
7107             return;
7108         }
7109     }
7110
7111     /* fromX != -1 */
7112     if (clickType == Press && gameMode != EditPosition) {
7113         ChessSquare fromP;
7114         ChessSquare toP;
7115         int frc;
7116
7117         // ignore off-board to clicks
7118         if(y < 0 || x < 0) return;
7119
7120         /* Check if clicking again on the same color piece */
7121         fromP = boards[currentMove][fromY][fromX];
7122         toP = boards[currentMove][y][x];
7123         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7124         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7125              WhitePawn <= toP && toP <= WhiteKing &&
7126              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7127              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7128             (BlackPawn <= fromP && fromP <= BlackKing &&
7129              BlackPawn <= toP && toP <= BlackKing &&
7130              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7131              !(fromP == BlackKing && toP == BlackRook && frc))) {
7132             /* Clicked again on same color piece -- changed his mind */
7133             second = (x == fromX && y == fromY);
7134             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7135                 second = FALSE; // first double-click rather than scond click
7136                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7137             }
7138             promoDefaultAltered = FALSE;
7139             MarkTargetSquares(1);
7140            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7141             if (appData.highlightDragging) {
7142                 SetHighlights(x, y, -1, -1);
7143             } else {
7144                 ClearHighlights();
7145             }
7146             if (OKToStartUserMove(x, y)) {
7147                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7148                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7149                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7150                  gatingPiece = boards[currentMove][fromY][fromX];
7151                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7152                 fromX = x;
7153                 fromY = y; dragging = 1;
7154                 MarkTargetSquares(0);
7155                 DragPieceBegin(xPix, yPix, FALSE);
7156                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7157                     promoSweep = defaultPromoChoice;
7158                     selectFlag = 0; lastX = xPix; lastY = yPix;
7159                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7160                 }
7161             }
7162            }
7163            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7164            second = FALSE; 
7165         }
7166         // ignore clicks on holdings
7167         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7168     }
7169
7170     if (clickType == Release && x == fromX && y == fromY) {
7171         DragPieceEnd(xPix, yPix); dragging = 0;
7172         if(clearFlag) {
7173             // a deferred attempt to click-click move an empty square on top of a piece
7174             boards[currentMove][y][x] = EmptySquare;
7175             ClearHighlights();
7176             DrawPosition(FALSE, boards[currentMove]);
7177             fromX = fromY = -1; clearFlag = 0;
7178             return;
7179         }
7180         if (appData.animateDragging) {
7181             /* Undo animation damage if any */
7182             DrawPosition(FALSE, NULL);
7183         }
7184         if (second || sweepSelecting) {
7185             /* Second up/down in same square; just abort move */
7186             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7187             second = sweepSelecting = 0;
7188             fromX = fromY = -1;
7189             gatingPiece = EmptySquare;
7190             ClearHighlights();
7191             gotPremove = 0;
7192             ClearPremoveHighlights();
7193         } else {
7194             /* First upclick in same square; start click-click mode */
7195             SetHighlights(x, y, -1, -1);
7196         }
7197         return;
7198     }
7199
7200     clearFlag = 0;
7201
7202     /* we now have a different from- and (possibly off-board) to-square */
7203     /* Completed move */
7204     if(!sweepSelecting) {
7205         toX = x;
7206         toY = y;
7207         saveAnimate = appData.animate;
7208     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7209
7210     if (clickType == Press) {
7211         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7212             // must be Edit Position mode with empty-square selected
7213             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7214             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7215             return;
7216         }
7217         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7218           if(appData.sweepSelect) {
7219             ChessSquare piece = boards[currentMove][fromY][fromX];
7220             promoSweep = defaultPromoChoice;
7221             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7222             selectFlag = 0; lastX = xPix; lastY = yPix;
7223             Sweep(0); // Pawn that is going to promote: preview promotion piece
7224             sweepSelecting = 1;
7225             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7226             MarkTargetSquares(1);
7227           }
7228           return; // promo popup appears on up-click
7229         }
7230         /* Finish clickclick move */
7231         if (appData.animate || appData.highlightLastMove) {
7232             SetHighlights(fromX, fromY, toX, toY);
7233         } else {
7234             ClearHighlights();
7235         }
7236     } else {
7237         /* Finish drag move */
7238         if (appData.highlightLastMove) {
7239             SetHighlights(fromX, fromY, toX, toY);
7240         } else {
7241             ClearHighlights();
7242         }
7243         DragPieceEnd(xPix, yPix); dragging = 0;
7244         /* Don't animate move and drag both */
7245         appData.animate = FALSE;
7246     }
7247     MarkTargetSquares(1);
7248
7249     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7250     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7251         ChessSquare piece = boards[currentMove][fromY][fromX];
7252         if(gameMode == EditPosition && piece != EmptySquare &&
7253            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7254             int n;
7255
7256             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7257                 n = PieceToNumber(piece - (int)BlackPawn);
7258                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7259                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7260                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7261             } else
7262             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7263                 n = PieceToNumber(piece);
7264                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7265                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7266                 boards[currentMove][n][BOARD_WIDTH-2]++;
7267             }
7268             boards[currentMove][fromY][fromX] = EmptySquare;
7269         }
7270         ClearHighlights();
7271         fromX = fromY = -1;
7272         DrawPosition(TRUE, boards[currentMove]);
7273         return;
7274     }
7275
7276     // off-board moves should not be highlighted
7277     if(x < 0 || y < 0) ClearHighlights();
7278
7279     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7280
7281     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7282         SetHighlights(fromX, fromY, toX, toY);
7283         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7284             // [HGM] super: promotion to captured piece selected from holdings
7285             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7286             promotionChoice = TRUE;
7287             // kludge follows to temporarily execute move on display, without promoting yet
7288             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7289             boards[currentMove][toY][toX] = p;
7290             DrawPosition(FALSE, boards[currentMove]);
7291             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7292             boards[currentMove][toY][toX] = q;
7293             DisplayMessage("Click in holdings to choose piece", "");
7294             return;
7295         }
7296         PromotionPopUp();
7297     } else {
7298         int oldMove = currentMove;
7299         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7300         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7301         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7302         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7303            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7304             DrawPosition(TRUE, boards[currentMove]);
7305         fromX = fromY = -1;
7306     }
7307     appData.animate = saveAnimate;
7308     if (appData.animate || appData.animateDragging) {
7309         /* Undo animation damage if needed */
7310         DrawPosition(FALSE, NULL);
7311     }
7312 }
7313
7314 int
7315 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7316 {   // front-end-free part taken out of PieceMenuPopup
7317     int whichMenu; int xSqr, ySqr;
7318
7319     if(seekGraphUp) { // [HGM] seekgraph
7320         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7321         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7322         return -2;
7323     }
7324
7325     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7326          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7327         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7328         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7329         if(action == Press)   {
7330             originalFlip = flipView;
7331             flipView = !flipView; // temporarily flip board to see game from partners perspective
7332             DrawPosition(TRUE, partnerBoard);
7333             DisplayMessage(partnerStatus, "");
7334             partnerUp = TRUE;
7335         } else if(action == Release) {
7336             flipView = originalFlip;
7337             DrawPosition(TRUE, boards[currentMove]);
7338             partnerUp = FALSE;
7339         }
7340         return -2;
7341     }
7342
7343     xSqr = EventToSquare(x, BOARD_WIDTH);
7344     ySqr = EventToSquare(y, BOARD_HEIGHT);
7345     if (action == Release) {
7346         if(pieceSweep != EmptySquare) {
7347             EditPositionMenuEvent(pieceSweep, toX, toY);
7348             pieceSweep = EmptySquare;
7349         } else UnLoadPV(); // [HGM] pv
7350     }
7351     if (action != Press) return -2; // return code to be ignored
7352     switch (gameMode) {
7353       case IcsExamining:
7354         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7355       case EditPosition:
7356         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7357         if (xSqr < 0 || ySqr < 0) return -1;
7358         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7359         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7360         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7361         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7362         NextPiece(0);
7363         return 2; // grab
7364       case IcsObserving:
7365         if(!appData.icsEngineAnalyze) return -1;
7366       case IcsPlayingWhite:
7367       case IcsPlayingBlack:
7368         if(!appData.zippyPlay) goto noZip;
7369       case AnalyzeMode:
7370       case AnalyzeFile:
7371       case MachinePlaysWhite:
7372       case MachinePlaysBlack:
7373       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7374         if (!appData.dropMenu) {
7375           LoadPV(x, y);
7376           return 2; // flag front-end to grab mouse events
7377         }
7378         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7379            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7380       case EditGame:
7381       noZip:
7382         if (xSqr < 0 || ySqr < 0) return -1;
7383         if (!appData.dropMenu || appData.testLegality &&
7384             gameInfo.variant != VariantBughouse &&
7385             gameInfo.variant != VariantCrazyhouse) return -1;
7386         whichMenu = 1; // drop menu
7387         break;
7388       default:
7389         return -1;
7390     }
7391
7392     if (((*fromX = xSqr) < 0) ||
7393         ((*fromY = ySqr) < 0)) {
7394         *fromX = *fromY = -1;
7395         return -1;
7396     }
7397     if (flipView)
7398       *fromX = BOARD_WIDTH - 1 - *fromX;
7399     else
7400       *fromY = BOARD_HEIGHT - 1 - *fromY;
7401
7402     return whichMenu;
7403 }
7404
7405 void
7406 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7407 {
7408 //    char * hint = lastHint;
7409     FrontEndProgramStats stats;
7410
7411     stats.which = cps == &first ? 0 : 1;
7412     stats.depth = cpstats->depth;
7413     stats.nodes = cpstats->nodes;
7414     stats.score = cpstats->score;
7415     stats.time = cpstats->time;
7416     stats.pv = cpstats->movelist;
7417     stats.hint = lastHint;
7418     stats.an_move_index = 0;
7419     stats.an_move_count = 0;
7420
7421     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7422         stats.hint = cpstats->move_name;
7423         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7424         stats.an_move_count = cpstats->nr_moves;
7425     }
7426
7427     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
7428
7429     SetProgramStats( &stats );
7430 }
7431
7432 void
7433 ClearEngineOutputPane (int which)
7434 {
7435     static FrontEndProgramStats dummyStats;
7436     dummyStats.which = which;
7437     dummyStats.pv = "#";
7438     SetProgramStats( &dummyStats );
7439 }
7440
7441 #define MAXPLAYERS 500
7442
7443 char *
7444 TourneyStandings (int display)
7445 {
7446     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7447     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7448     char result, *p, *names[MAXPLAYERS];
7449
7450     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7451         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7452     names[0] = p = strdup(appData.participants);
7453     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7454
7455     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7456
7457     while(result = appData.results[nr]) {
7458         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7459         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7460         wScore = bScore = 0;
7461         switch(result) {
7462           case '+': wScore = 2; break;
7463           case '-': bScore = 2; break;
7464           case '=': wScore = bScore = 1; break;
7465           case ' ':
7466           case '*': return strdup("busy"); // tourney not finished
7467         }
7468         score[w] += wScore;
7469         score[b] += bScore;
7470         games[w]++;
7471         games[b]++;
7472         nr++;
7473     }
7474     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7475     for(w=0; w<nPlayers; w++) {
7476         bScore = -1;
7477         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7478         ranking[w] = b; points[w] = bScore; score[b] = -2;
7479     }
7480     p = malloc(nPlayers*34+1);
7481     for(w=0; w<nPlayers && w<display; w++)
7482         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7483     free(names[0]);
7484     return p;
7485 }
7486
7487 void
7488 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7489 {       // count all piece types
7490         int p, f, r;
7491         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7492         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7493         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7494                 p = board[r][f];
7495                 pCnt[p]++;
7496                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7497                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7498                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7499                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7500                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7501                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7502         }
7503 }
7504
7505 int
7506 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7507 {
7508         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7509         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7510
7511         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7512         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7513         if(myPawns == 2 && nMine == 3) // KPP
7514             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7515         if(myPawns == 1 && nMine == 2) // KP
7516             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7517         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7518             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7519         if(myPawns) return FALSE;
7520         if(pCnt[WhiteRook+side])
7521             return pCnt[BlackRook-side] ||
7522                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7523                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7524                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7525         if(pCnt[WhiteCannon+side]) {
7526             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7527             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7528         }
7529         if(pCnt[WhiteKnight+side])
7530             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7531         return FALSE;
7532 }
7533
7534 int
7535 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7536 {
7537         VariantClass v = gameInfo.variant;
7538
7539         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7540         if(v == VariantShatranj) return TRUE; // always winnable through baring
7541         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7542         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7543
7544         if(v == VariantXiangqi) {
7545                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7546
7547                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7548                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7549                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7550                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7551                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7552                 if(stale) // we have at least one last-rank P plus perhaps C
7553                     return majors // KPKX
7554                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7555                 else // KCA*E*
7556                     return pCnt[WhiteFerz+side] // KCAK
7557                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7558                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7559                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7560
7561         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7562                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7563
7564                 if(nMine == 1) return FALSE; // bare King
7565                 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
7566                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7567                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7568                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7569                 if(pCnt[WhiteKnight+side])
7570                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7571                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7572                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7573                 if(nBishops)
7574                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7575                 if(pCnt[WhiteAlfil+side])
7576                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7577                 if(pCnt[WhiteWazir+side])
7578                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7579         }
7580
7581         return TRUE;
7582 }
7583
7584 int
7585 CompareWithRights (Board b1, Board b2)
7586 {
7587     int rights = 0;
7588     if(!CompareBoards(b1, b2)) return FALSE;
7589     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7590     /* compare castling rights */
7591     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7592            rights++; /* King lost rights, while rook still had them */
7593     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7594         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7595            rights++; /* but at least one rook lost them */
7596     }
7597     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7598            rights++;
7599     if( b1[CASTLING][5] != NoRights ) {
7600         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7601            rights++;
7602     }
7603     return rights == 0;
7604 }
7605
7606 int
7607 Adjudicate (ChessProgramState *cps)
7608 {       // [HGM] some adjudications useful with buggy engines
7609         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7610         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7611         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7612         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7613         int k, count = 0; static int bare = 1;
7614         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7615         Boolean canAdjudicate = !appData.icsActive;
7616
7617         // most tests only when we understand the game, i.e. legality-checking on
7618             if( appData.testLegality )
7619             {   /* [HGM] Some more adjudications for obstinate engines */
7620                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7621                 static int moveCount = 6;
7622                 ChessMove result;
7623                 char *reason = NULL;
7624
7625                 /* Count what is on board. */
7626                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7627
7628                 /* Some material-based adjudications that have to be made before stalemate test */
7629                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7630                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7631                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7632                      if(canAdjudicate && appData.checkMates) {
7633                          if(engineOpponent)
7634                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7635                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7636                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7637                          return 1;
7638                      }
7639                 }
7640
7641                 /* Bare King in Shatranj (loses) or Losers (wins) */
7642                 if( nrW == 1 || nrB == 1) {
7643                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7644                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7645                      if(canAdjudicate && appData.checkMates) {
7646                          if(engineOpponent)
7647                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7648                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7649                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7650                          return 1;
7651                      }
7652                   } else
7653                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7654                   {    /* bare King */
7655                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7656                         if(canAdjudicate && appData.checkMates) {
7657                             /* but only adjudicate if adjudication enabled */
7658                             if(engineOpponent)
7659                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7660                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7661                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7662                             return 1;
7663                         }
7664                   }
7665                 } else bare = 1;
7666
7667
7668             // don't wait for engine to announce game end if we can judge ourselves
7669             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7670               case MT_CHECK:
7671                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7672                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7673                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7674                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7675                             checkCnt++;
7676                         if(checkCnt >= 2) {
7677                             reason = "Xboard adjudication: 3rd check";
7678                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7679                             break;
7680                         }
7681                     }
7682                 }
7683               case MT_NONE:
7684               default:
7685                 break;
7686               case MT_STALEMATE:
7687               case MT_STAINMATE:
7688                 reason = "Xboard adjudication: Stalemate";
7689                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7690                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7691                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7692                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7693                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7694                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7695                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7696                                                                         EP_CHECKMATE : EP_WINS);
7697                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7698                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7699                 }
7700                 break;
7701               case MT_CHECKMATE:
7702                 reason = "Xboard adjudication: Checkmate";
7703                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7704                 break;
7705             }
7706
7707                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7708                     case EP_STALEMATE:
7709                         result = GameIsDrawn; break;
7710                     case EP_CHECKMATE:
7711                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7712                     case EP_WINS:
7713                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7714                     default:
7715                         result = EndOfFile;
7716                 }
7717                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7718                     if(engineOpponent)
7719                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7720                     GameEnds( result, reason, GE_XBOARD );
7721                     return 1;
7722                 }
7723
7724                 /* Next absolutely insufficient mating material. */
7725                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7726                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7727                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7728
7729                      /* always flag draws, for judging claims */
7730                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7731
7732                      if(canAdjudicate && appData.materialDraws) {
7733                          /* but only adjudicate them if adjudication enabled */
7734                          if(engineOpponent) {
7735                            SendToProgram("force\n", engineOpponent); // suppress reply
7736                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7737                          }
7738                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7739                          return 1;
7740                      }
7741                 }
7742
7743                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7744                 if(gameInfo.variant == VariantXiangqi ?
7745                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7746                  : nrW + nrB == 4 &&
7747                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7748                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7749                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7750                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7751                    ) ) {
7752                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7753                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7754                           if(engineOpponent) {
7755                             SendToProgram("force\n", engineOpponent); // suppress reply
7756                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7757                           }
7758                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7759                           return 1;
7760                      }
7761                 } else moveCount = 6;
7762             }
7763
7764         // Repetition draws and 50-move rule can be applied independently of legality testing
7765
7766                 /* Check for rep-draws */
7767                 count = 0;
7768                 for(k = forwardMostMove-2;
7769                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7770                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7771                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7772                     k-=2)
7773                 {   int rights=0;
7774                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7775                         /* compare castling rights */
7776                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7777                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7778                                 rights++; /* King lost rights, while rook still had them */
7779                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7780                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7781                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7782                                    rights++; /* but at least one rook lost them */
7783                         }
7784                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7785                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7786                                 rights++;
7787                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7788                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7789                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7790                                    rights++;
7791                         }
7792                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7793                             && appData.drawRepeats > 1) {
7794                              /* adjudicate after user-specified nr of repeats */
7795                              int result = GameIsDrawn;
7796                              char *details = "XBoard adjudication: repetition draw";
7797                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7798                                 // [HGM] xiangqi: check for forbidden perpetuals
7799                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7800                                 for(m=forwardMostMove; m>k; m-=2) {
7801                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7802                                         ourPerpetual = 0; // the current mover did not always check
7803                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7804                                         hisPerpetual = 0; // the opponent did not always check
7805                                 }
7806                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7807                                                                         ourPerpetual, hisPerpetual);
7808                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7809                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7810                                     details = "Xboard adjudication: perpetual checking";
7811                                 } else
7812                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7813                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7814                                 } else
7815                                 // Now check for perpetual chases
7816                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7817                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7818                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7819                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7820                                         static char resdet[MSG_SIZ];
7821                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7822                                         details = resdet;
7823                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7824                                     } else
7825                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7826                                         break; // Abort repetition-checking loop.
7827                                 }
7828                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7829                              }
7830                              if(engineOpponent) {
7831                                SendToProgram("force\n", engineOpponent); // suppress reply
7832                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7833                              }
7834                              GameEnds( result, details, GE_XBOARD );
7835                              return 1;
7836                         }
7837                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7838                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7839                     }
7840                 }
7841
7842                 /* Now we test for 50-move draws. Determine ply count */
7843                 count = forwardMostMove;
7844                 /* look for last irreversble move */
7845                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7846                     count--;
7847                 /* if we hit starting position, add initial plies */
7848                 if( count == backwardMostMove )
7849                     count -= initialRulePlies;
7850                 count = forwardMostMove - count;
7851                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7852                         // adjust reversible move counter for checks in Xiangqi
7853                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7854                         if(i < backwardMostMove) i = backwardMostMove;
7855                         while(i <= forwardMostMove) {
7856                                 lastCheck = inCheck; // check evasion does not count
7857                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7858                                 if(inCheck || lastCheck) count--; // check does not count
7859                                 i++;
7860                         }
7861                 }
7862                 if( count >= 100)
7863                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7864                          /* this is used to judge if draw claims are legal */
7865                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7866                          if(engineOpponent) {
7867                            SendToProgram("force\n", engineOpponent); // suppress reply
7868                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7869                          }
7870                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7871                          return 1;
7872                 }
7873
7874                 /* if draw offer is pending, treat it as a draw claim
7875                  * when draw condition present, to allow engines a way to
7876                  * claim draws before making their move to avoid a race
7877                  * condition occurring after their move
7878                  */
7879                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7880                          char *p = NULL;
7881                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7882                              p = "Draw claim: 50-move rule";
7883                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7884                              p = "Draw claim: 3-fold repetition";
7885                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7886                              p = "Draw claim: insufficient mating material";
7887                          if( p != NULL && canAdjudicate) {
7888                              if(engineOpponent) {
7889                                SendToProgram("force\n", engineOpponent); // suppress reply
7890                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7891                              }
7892                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7893                              return 1;
7894                          }
7895                 }
7896
7897                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7898                     if(engineOpponent) {
7899                       SendToProgram("force\n", engineOpponent); // suppress reply
7900                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7901                     }
7902                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7903                     return 1;
7904                 }
7905         return 0;
7906 }
7907
7908 char *
7909 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7910 {   // [HGM] book: this routine intercepts moves to simulate book replies
7911     char *bookHit = NULL;
7912
7913     //first determine if the incoming move brings opponent into his book
7914     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7915         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7916     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7917     if(bookHit != NULL && !cps->bookSuspend) {
7918         // make sure opponent is not going to reply after receiving move to book position
7919         SendToProgram("force\n", cps);
7920         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7921     }
7922     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7923     // now arrange restart after book miss
7924     if(bookHit) {
7925         // after a book hit we never send 'go', and the code after the call to this routine
7926         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7927         char buf[MSG_SIZ], *move = bookHit;
7928         if(cps->useSAN) {
7929             int fromX, fromY, toX, toY;
7930             char promoChar;
7931             ChessMove moveType;
7932             move = buf + 30;
7933             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7934                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7935                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7936                                     PosFlags(forwardMostMove),
7937                                     fromY, fromX, toY, toX, promoChar, move);
7938             } else {
7939                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7940                 bookHit = NULL;
7941             }
7942         }
7943         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7944         SendToProgram(buf, cps);
7945         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7946     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7947         SendToProgram("go\n", cps);
7948         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7949     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7950         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7951             SendToProgram("go\n", cps);
7952         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7953     }
7954     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7955 }
7956
7957 int
7958 LoadError (char *errmess, ChessProgramState *cps)
7959 {   // unloads engine and switches back to -ncp mode if it was first
7960     if(cps->initDone) return FALSE;
7961     cps->isr = NULL; // this should suppress further error popups from breaking pipes
7962     DestroyChildProcess(cps->pr, 9 ); // just to be sure
7963     cps->pr = NoProc; 
7964     if(cps == &first) {
7965         appData.noChessProgram = TRUE;
7966         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
7967         gameMode = BeginningOfGame; ModeHighlight();
7968         SetNCPMode();
7969     }
7970     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
7971     DisplayMessage("", ""); // erase waiting message
7972     if(errmess) DisplayError(errmess, 0); // announce reason, if given
7973     return TRUE;
7974 }
7975
7976 char *savedMessage;
7977 ChessProgramState *savedState;
7978 void
7979 DeferredBookMove (void)
7980 {
7981         if(savedState->lastPing != savedState->lastPong)
7982                     ScheduleDelayedEvent(DeferredBookMove, 10);
7983         else
7984         HandleMachineMove(savedMessage, savedState);
7985 }
7986
7987 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7988
7989 void
7990 HandleMachineMove (char *message, ChessProgramState *cps)
7991 {
7992     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7993     char realname[MSG_SIZ];
7994     int fromX, fromY, toX, toY;
7995     ChessMove moveType;
7996     char promoChar;
7997     char *p, *pv=buf1;
7998     int machineWhite, oldError;
7999     char *bookHit;
8000
8001     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8002         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8003         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8004             DisplayError(_("Invalid pairing from pairing engine"), 0);
8005             return;
8006         }
8007         pairingReceived = 1;
8008         NextMatchGame();
8009         return; // Skim the pairing messages here.
8010     }
8011
8012     oldError = cps->userError; cps->userError = 0;
8013
8014 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8015     /*
8016      * Kludge to ignore BEL characters
8017      */
8018     while (*message == '\007') message++;
8019
8020     /*
8021      * [HGM] engine debug message: ignore lines starting with '#' character
8022      */
8023     if(cps->debug && *message == '#') return;
8024
8025     /*
8026      * Look for book output
8027      */
8028     if (cps == &first && bookRequested) {
8029         if (message[0] == '\t' || message[0] == ' ') {
8030             /* Part of the book output is here; append it */
8031             strcat(bookOutput, message);
8032             strcat(bookOutput, "  \n");
8033             return;
8034         } else if (bookOutput[0] != NULLCHAR) {
8035             /* All of book output has arrived; display it */
8036             char *p = bookOutput;
8037             while (*p != NULLCHAR) {
8038                 if (*p == '\t') *p = ' ';
8039                 p++;
8040             }
8041             DisplayInformation(bookOutput);
8042             bookRequested = FALSE;
8043             /* Fall through to parse the current output */
8044         }
8045     }
8046
8047     /*
8048      * Look for machine move.
8049      */
8050     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8051         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8052     {
8053         /* This method is only useful on engines that support ping */
8054         if (cps->lastPing != cps->lastPong) {
8055           if (gameMode == BeginningOfGame) {
8056             /* Extra move from before last new; ignore */
8057             if (appData.debugMode) {
8058                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8059             }
8060           } else {
8061             if (appData.debugMode) {
8062                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8063                         cps->which, gameMode);
8064             }
8065
8066             SendToProgram("undo\n", cps);
8067           }
8068           return;
8069         }
8070
8071         switch (gameMode) {
8072           case BeginningOfGame:
8073             /* Extra move from before last reset; ignore */
8074             if (appData.debugMode) {
8075                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8076             }
8077             return;
8078
8079           case EndOfGame:
8080           case IcsIdle:
8081           default:
8082             /* Extra move after we tried to stop.  The mode test is
8083                not a reliable way of detecting this problem, but it's
8084                the best we can do on engines that don't support ping.
8085             */
8086             if (appData.debugMode) {
8087                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8088                         cps->which, gameMode);
8089             }
8090             SendToProgram("undo\n", cps);
8091             return;
8092
8093           case MachinePlaysWhite:
8094           case IcsPlayingWhite:
8095             machineWhite = TRUE;
8096             break;
8097
8098           case MachinePlaysBlack:
8099           case IcsPlayingBlack:
8100             machineWhite = FALSE;
8101             break;
8102
8103           case TwoMachinesPlay:
8104             machineWhite = (cps->twoMachinesColor[0] == 'w');
8105             break;
8106         }
8107         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8108             if (appData.debugMode) {
8109                 fprintf(debugFP,
8110                         "Ignoring move out of turn by %s, gameMode %d"
8111                         ", forwardMost %d\n",
8112                         cps->which, gameMode, forwardMostMove);
8113             }
8114             return;
8115         }
8116
8117         if(cps->alphaRank) AlphaRank(machineMove, 4);
8118         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8119                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8120             /* Machine move could not be parsed; ignore it. */
8121           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8122                     machineMove, _(cps->which));
8123             DisplayError(buf1, 0);
8124             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8125                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8126             if (gameMode == TwoMachinesPlay) {
8127               GameEnds(machineWhite ? BlackWins : WhiteWins,
8128                        buf1, GE_XBOARD);
8129             }
8130             return;
8131         }
8132
8133         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8134         /* So we have to redo legality test with true e.p. status here,  */
8135         /* to make sure an illegal e.p. capture does not slip through,   */
8136         /* to cause a forfeit on a justified illegal-move complaint      */
8137         /* of the opponent.                                              */
8138         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8139            ChessMove moveType;
8140            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8141                              fromY, fromX, toY, toX, promoChar);
8142             if(moveType == IllegalMove) {
8143               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8144                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8145                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8146                            buf1, GE_XBOARD);
8147                 return;
8148            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8149            /* [HGM] Kludge to handle engines that send FRC-style castling
8150               when they shouldn't (like TSCP-Gothic) */
8151            switch(moveType) {
8152              case WhiteASideCastleFR:
8153              case BlackASideCastleFR:
8154                toX+=2;
8155                currentMoveString[2]++;
8156                break;
8157              case WhiteHSideCastleFR:
8158              case BlackHSideCastleFR:
8159                toX--;
8160                currentMoveString[2]--;
8161                break;
8162              default: ; // nothing to do, but suppresses warning of pedantic compilers
8163            }
8164         }
8165         hintRequested = FALSE;
8166         lastHint[0] = NULLCHAR;
8167         bookRequested = FALSE;
8168         /* Program may be pondering now */
8169         cps->maybeThinking = TRUE;
8170         if (cps->sendTime == 2) cps->sendTime = 1;
8171         if (cps->offeredDraw) cps->offeredDraw--;
8172
8173         /* [AS] Save move info*/
8174         pvInfoList[ forwardMostMove ].score = programStats.score;
8175         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8176         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8177
8178         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8179
8180         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8181         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8182             int count = 0;
8183
8184             while( count < adjudicateLossPlies ) {
8185                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8186
8187                 if( count & 1 ) {
8188                     score = -score; /* Flip score for winning side */
8189                 }
8190
8191                 if( score > adjudicateLossThreshold ) {
8192                     break;
8193                 }
8194
8195                 count++;
8196             }
8197
8198             if( count >= adjudicateLossPlies ) {
8199                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8200
8201                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8202                     "Xboard adjudication",
8203                     GE_XBOARD );
8204
8205                 return;
8206             }
8207         }
8208
8209         if(Adjudicate(cps)) {
8210             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8211             return; // [HGM] adjudicate: for all automatic game ends
8212         }
8213
8214 #if ZIPPY
8215         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8216             first.initDone) {
8217           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8218                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8219                 SendToICS("draw ");
8220                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8221           }
8222           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8223           ics_user_moved = 1;
8224           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8225                 char buf[3*MSG_SIZ];
8226
8227                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8228                         programStats.score / 100.,
8229                         programStats.depth,
8230                         programStats.time / 100.,
8231                         (unsigned int)programStats.nodes,
8232                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8233                         programStats.movelist);
8234                 SendToICS(buf);
8235 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8236           }
8237         }
8238 #endif
8239
8240         /* [AS] Clear stats for next move */
8241         ClearProgramStats();
8242         thinkOutput[0] = NULLCHAR;
8243         hiddenThinkOutputState = 0;
8244
8245         bookHit = NULL;
8246         if (gameMode == TwoMachinesPlay) {
8247             /* [HGM] relaying draw offers moved to after reception of move */
8248             /* and interpreting offer as claim if it brings draw condition */
8249             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8250                 SendToProgram("draw\n", cps->other);
8251             }
8252             if (cps->other->sendTime) {
8253                 SendTimeRemaining(cps->other,
8254                                   cps->other->twoMachinesColor[0] == 'w');
8255             }
8256             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8257             if (firstMove && !bookHit) {
8258                 firstMove = FALSE;
8259                 if (cps->other->useColors) {
8260                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8261                 }
8262                 SendToProgram("go\n", cps->other);
8263             }
8264             cps->other->maybeThinking = TRUE;
8265         }
8266
8267         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8268
8269         if (!pausing && appData.ringBellAfterMoves) {
8270             RingBell();
8271         }
8272
8273         /*
8274          * Reenable menu items that were disabled while
8275          * machine was thinking
8276          */
8277         if (gameMode != TwoMachinesPlay)
8278             SetUserThinkingEnables();
8279
8280         // [HGM] book: after book hit opponent has received move and is now in force mode
8281         // force the book reply into it, and then fake that it outputted this move by jumping
8282         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8283         if(bookHit) {
8284                 static char bookMove[MSG_SIZ]; // a bit generous?
8285
8286                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8287                 strcat(bookMove, bookHit);
8288                 message = bookMove;
8289                 cps = cps->other;
8290                 programStats.nodes = programStats.depth = programStats.time =
8291                 programStats.score = programStats.got_only_move = 0;
8292                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8293
8294                 if(cps->lastPing != cps->lastPong) {
8295                     savedMessage = message; // args for deferred call
8296                     savedState = cps;
8297                     ScheduleDelayedEvent(DeferredBookMove, 10);
8298                     return;
8299                 }
8300                 goto FakeBookMove;
8301         }
8302
8303         return;
8304     }
8305
8306     /* Set special modes for chess engines.  Later something general
8307      *  could be added here; for now there is just one kludge feature,
8308      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8309      *  when "xboard" is given as an interactive command.
8310      */
8311     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8312         cps->useSigint = FALSE;
8313         cps->useSigterm = FALSE;
8314     }
8315     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8316       ParseFeatures(message+8, cps);
8317       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8318     }
8319
8320     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8321                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8322       int dummy, s=6; char buf[MSG_SIZ];
8323       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8324       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8325       if(startedFromSetupPosition) return;
8326       ParseFEN(boards[0], &dummy, message+s);
8327       DrawPosition(TRUE, boards[0]);
8328       startedFromSetupPosition = TRUE;
8329       return;
8330     }
8331     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8332      * want this, I was asked to put it in, and obliged.
8333      */
8334     if (!strncmp(message, "setboard ", 9)) {
8335         Board initial_position;
8336
8337         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8338
8339         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8340             DisplayError(_("Bad FEN received from engine"), 0);
8341             return ;
8342         } else {
8343            Reset(TRUE, FALSE);
8344            CopyBoard(boards[0], initial_position);
8345            initialRulePlies = FENrulePlies;
8346            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8347            else gameMode = MachinePlaysBlack;
8348            DrawPosition(FALSE, boards[currentMove]);
8349         }
8350         return;
8351     }
8352
8353     /*
8354      * Look for communication commands
8355      */
8356     if (!strncmp(message, "telluser ", 9)) {
8357         if(message[9] == '\\' && message[10] == '\\')
8358             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8359         PlayTellSound();
8360         DisplayNote(message + 9);
8361         return;
8362     }
8363     if (!strncmp(message, "tellusererror ", 14)) {
8364         cps->userError = 1;
8365         if(message[14] == '\\' && message[15] == '\\')
8366             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8367         PlayTellSound();
8368         DisplayError(message + 14, 0);
8369         return;
8370     }
8371     if (!strncmp(message, "tellopponent ", 13)) {
8372       if (appData.icsActive) {
8373         if (loggedOn) {
8374           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8375           SendToICS(buf1);
8376         }
8377       } else {
8378         DisplayNote(message + 13);
8379       }
8380       return;
8381     }
8382     if (!strncmp(message, "tellothers ", 11)) {
8383       if (appData.icsActive) {
8384         if (loggedOn) {
8385           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8386           SendToICS(buf1);
8387         }
8388       }
8389       return;
8390     }
8391     if (!strncmp(message, "tellall ", 8)) {
8392       if (appData.icsActive) {
8393         if (loggedOn) {
8394           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8395           SendToICS(buf1);
8396         }
8397       } else {
8398         DisplayNote(message + 8);
8399       }
8400       return;
8401     }
8402     if (strncmp(message, "warning", 7) == 0) {
8403         /* Undocumented feature, use tellusererror in new code */
8404         DisplayError(message, 0);
8405         return;
8406     }
8407     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8408         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8409         strcat(realname, " query");
8410         AskQuestion(realname, buf2, buf1, cps->pr);
8411         return;
8412     }
8413     /* Commands from the engine directly to ICS.  We don't allow these to be
8414      *  sent until we are logged on. Crafty kibitzes have been known to
8415      *  interfere with the login process.
8416      */
8417     if (loggedOn) {
8418         if (!strncmp(message, "tellics ", 8)) {
8419             SendToICS(message + 8);
8420             SendToICS("\n");
8421             return;
8422         }
8423         if (!strncmp(message, "tellicsnoalias ", 15)) {
8424             SendToICS(ics_prefix);
8425             SendToICS(message + 15);
8426             SendToICS("\n");
8427             return;
8428         }
8429         /* The following are for backward compatibility only */
8430         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8431             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8432             SendToICS(ics_prefix);
8433             SendToICS(message);
8434             SendToICS("\n");
8435             return;
8436         }
8437     }
8438     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8439         return;
8440     }
8441     /*
8442      * If the move is illegal, cancel it and redraw the board.
8443      * Also deal with other error cases.  Matching is rather loose
8444      * here to accommodate engines written before the spec.
8445      */
8446     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8447         strncmp(message, "Error", 5) == 0) {
8448         if (StrStr(message, "name") ||
8449             StrStr(message, "rating") || StrStr(message, "?") ||
8450             StrStr(message, "result") || StrStr(message, "board") ||
8451             StrStr(message, "bk") || StrStr(message, "computer") ||
8452             StrStr(message, "variant") || StrStr(message, "hint") ||
8453             StrStr(message, "random") || StrStr(message, "depth") ||
8454             StrStr(message, "accepted")) {
8455             return;
8456         }
8457         if (StrStr(message, "protover")) {
8458           /* Program is responding to input, so it's apparently done
8459              initializing, and this error message indicates it is
8460              protocol version 1.  So we don't need to wait any longer
8461              for it to initialize and send feature commands. */
8462           FeatureDone(cps, 1);
8463           cps->protocolVersion = 1;
8464           return;
8465         }
8466         cps->maybeThinking = FALSE;
8467
8468         if (StrStr(message, "draw")) {
8469             /* Program doesn't have "draw" command */
8470             cps->sendDrawOffers = 0;
8471             return;
8472         }
8473         if (cps->sendTime != 1 &&
8474             (StrStr(message, "time") || StrStr(message, "otim"))) {
8475           /* Program apparently doesn't have "time" or "otim" command */
8476           cps->sendTime = 0;
8477           return;
8478         }
8479         if (StrStr(message, "analyze")) {
8480             cps->analysisSupport = FALSE;
8481             cps->analyzing = FALSE;
8482 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8483             EditGameEvent(); // [HGM] try to preserve loaded game
8484             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8485             DisplayError(buf2, 0);
8486             return;
8487         }
8488         if (StrStr(message, "(no matching move)st")) {
8489           /* Special kludge for GNU Chess 4 only */
8490           cps->stKludge = TRUE;
8491           SendTimeControl(cps, movesPerSession, timeControl,
8492                           timeIncrement, appData.searchDepth,
8493                           searchTime);
8494           return;
8495         }
8496         if (StrStr(message, "(no matching move)sd")) {
8497           /* Special kludge for GNU Chess 4 only */
8498           cps->sdKludge = TRUE;
8499           SendTimeControl(cps, movesPerSession, timeControl,
8500                           timeIncrement, appData.searchDepth,
8501                           searchTime);
8502           return;
8503         }
8504         if (!StrStr(message, "llegal")) {
8505             return;
8506         }
8507         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8508             gameMode == IcsIdle) return;
8509         if (forwardMostMove <= backwardMostMove) return;
8510         if (pausing) PauseEvent();
8511       if(appData.forceIllegal) {
8512             // [HGM] illegal: machine refused move; force position after move into it
8513           SendToProgram("force\n", cps);
8514           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8515                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8516                 // when black is to move, while there might be nothing on a2 or black
8517                 // might already have the move. So send the board as if white has the move.
8518                 // But first we must change the stm of the engine, as it refused the last move
8519                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8520                 if(WhiteOnMove(forwardMostMove)) {
8521                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8522                     SendBoard(cps, forwardMostMove); // kludgeless board
8523                 } else {
8524                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8525                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8526                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8527                 }
8528           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8529             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8530                  gameMode == TwoMachinesPlay)
8531               SendToProgram("go\n", cps);
8532             return;
8533       } else
8534         if (gameMode == PlayFromGameFile) {
8535             /* Stop reading this game file */
8536             gameMode = EditGame;
8537             ModeHighlight();
8538         }
8539         /* [HGM] illegal-move claim should forfeit game when Xboard */
8540         /* only passes fully legal moves                            */
8541         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8542             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8543                                 "False illegal-move claim", GE_XBOARD );
8544             return; // do not take back move we tested as valid
8545         }
8546         currentMove = forwardMostMove-1;
8547         DisplayMove(currentMove-1); /* before DisplayMoveError */
8548         SwitchClocks(forwardMostMove-1); // [HGM] race
8549         DisplayBothClocks();
8550         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8551                 parseList[currentMove], _(cps->which));
8552         DisplayMoveError(buf1);
8553         DrawPosition(FALSE, boards[currentMove]);
8554
8555         SetUserThinkingEnables();
8556         return;
8557     }
8558     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8559         /* Program has a broken "time" command that
8560            outputs a string not ending in newline.
8561            Don't use it. */
8562         cps->sendTime = 0;
8563     }
8564
8565     /*
8566      * If chess program startup fails, exit with an error message.
8567      * Attempts to recover here are futile. [HGM] Well, we try anyway
8568      */
8569     if ((StrStr(message, "unknown host") != NULL)
8570         || (StrStr(message, "No remote directory") != NULL)
8571         || (StrStr(message, "not found") != NULL)
8572         || (StrStr(message, "No such file") != NULL)
8573         || (StrStr(message, "can't alloc") != NULL)
8574         || (StrStr(message, "Permission denied") != NULL)) {
8575
8576         cps->maybeThinking = FALSE;
8577         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8578                 _(cps->which), cps->program, cps->host, message);
8579         RemoveInputSource(cps->isr);
8580         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8581             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8582             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8583         }
8584         return;
8585     }
8586
8587     /*
8588      * Look for hint output
8589      */
8590     if (sscanf(message, "Hint: %s", buf1) == 1) {
8591         if (cps == &first && hintRequested) {
8592             hintRequested = FALSE;
8593             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8594                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8595                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8596                                     PosFlags(forwardMostMove),
8597                                     fromY, fromX, toY, toX, promoChar, buf1);
8598                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8599                 DisplayInformation(buf2);
8600             } else {
8601                 /* Hint move could not be parsed!? */
8602               snprintf(buf2, sizeof(buf2),
8603                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8604                         buf1, _(cps->which));
8605                 DisplayError(buf2, 0);
8606             }
8607         } else {
8608           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8609         }
8610         return;
8611     }
8612
8613     /*
8614      * Ignore other messages if game is not in progress
8615      */
8616     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8617         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8618
8619     /*
8620      * look for win, lose, draw, or draw offer
8621      */
8622     if (strncmp(message, "1-0", 3) == 0) {
8623         char *p, *q, *r = "";
8624         p = strchr(message, '{');
8625         if (p) {
8626             q = strchr(p, '}');
8627             if (q) {
8628                 *q = NULLCHAR;
8629                 r = p + 1;
8630             }
8631         }
8632         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8633         return;
8634     } else if (strncmp(message, "0-1", 3) == 0) {
8635         char *p, *q, *r = "";
8636         p = strchr(message, '{');
8637         if (p) {
8638             q = strchr(p, '}');
8639             if (q) {
8640                 *q = NULLCHAR;
8641                 r = p + 1;
8642             }
8643         }
8644         /* Kludge for Arasan 4.1 bug */
8645         if (strcmp(r, "Black resigns") == 0) {
8646             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8647             return;
8648         }
8649         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8650         return;
8651     } else if (strncmp(message, "1/2", 3) == 0) {
8652         char *p, *q, *r = "";
8653         p = strchr(message, '{');
8654         if (p) {
8655             q = strchr(p, '}');
8656             if (q) {
8657                 *q = NULLCHAR;
8658                 r = p + 1;
8659             }
8660         }
8661
8662         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8663         return;
8664
8665     } else if (strncmp(message, "White resign", 12) == 0) {
8666         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8667         return;
8668     } else if (strncmp(message, "Black resign", 12) == 0) {
8669         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8670         return;
8671     } else if (strncmp(message, "White matches", 13) == 0 ||
8672                strncmp(message, "Black matches", 13) == 0   ) {
8673         /* [HGM] ignore GNUShogi noises */
8674         return;
8675     } else if (strncmp(message, "White", 5) == 0 &&
8676                message[5] != '(' &&
8677                StrStr(message, "Black") == NULL) {
8678         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8679         return;
8680     } else if (strncmp(message, "Black", 5) == 0 &&
8681                message[5] != '(') {
8682         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8683         return;
8684     } else if (strcmp(message, "resign") == 0 ||
8685                strcmp(message, "computer resigns") == 0) {
8686         switch (gameMode) {
8687           case MachinePlaysBlack:
8688           case IcsPlayingBlack:
8689             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8690             break;
8691           case MachinePlaysWhite:
8692           case IcsPlayingWhite:
8693             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8694             break;
8695           case TwoMachinesPlay:
8696             if (cps->twoMachinesColor[0] == 'w')
8697               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8698             else
8699               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8700             break;
8701           default:
8702             /* can't happen */
8703             break;
8704         }
8705         return;
8706     } else if (strncmp(message, "opponent mates", 14) == 0) {
8707         switch (gameMode) {
8708           case MachinePlaysBlack:
8709           case IcsPlayingBlack:
8710             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8711             break;
8712           case MachinePlaysWhite:
8713           case IcsPlayingWhite:
8714             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8715             break;
8716           case TwoMachinesPlay:
8717             if (cps->twoMachinesColor[0] == 'w')
8718               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8719             else
8720               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8721             break;
8722           default:
8723             /* can't happen */
8724             break;
8725         }
8726         return;
8727     } else if (strncmp(message, "computer mates", 14) == 0) {
8728         switch (gameMode) {
8729           case MachinePlaysBlack:
8730           case IcsPlayingBlack:
8731             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8732             break;
8733           case MachinePlaysWhite:
8734           case IcsPlayingWhite:
8735             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8736             break;
8737           case TwoMachinesPlay:
8738             if (cps->twoMachinesColor[0] == 'w')
8739               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8740             else
8741               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8742             break;
8743           default:
8744             /* can't happen */
8745             break;
8746         }
8747         return;
8748     } else if (strncmp(message, "checkmate", 9) == 0) {
8749         if (WhiteOnMove(forwardMostMove)) {
8750             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8751         } else {
8752             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8753         }
8754         return;
8755     } else if (strstr(message, "Draw") != NULL ||
8756                strstr(message, "game is a draw") != NULL) {
8757         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8758         return;
8759     } else if (strstr(message, "offer") != NULL &&
8760                strstr(message, "draw") != NULL) {
8761 #if ZIPPY
8762         if (appData.zippyPlay && first.initDone) {
8763             /* Relay offer to ICS */
8764             SendToICS(ics_prefix);
8765             SendToICS("draw\n");
8766         }
8767 #endif
8768         cps->offeredDraw = 2; /* valid until this engine moves twice */
8769         if (gameMode == TwoMachinesPlay) {
8770             if (cps->other->offeredDraw) {
8771                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8772             /* [HGM] in two-machine mode we delay relaying draw offer      */
8773             /* until after we also have move, to see if it is really claim */
8774             }
8775         } else if (gameMode == MachinePlaysWhite ||
8776                    gameMode == MachinePlaysBlack) {
8777           if (userOfferedDraw) {
8778             DisplayInformation(_("Machine accepts your draw offer"));
8779             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8780           } else {
8781             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8782           }
8783         }
8784     }
8785
8786
8787     /*
8788      * Look for thinking output
8789      */
8790     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8791           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8792                                 ) {
8793         int plylev, mvleft, mvtot, curscore, time;
8794         char mvname[MOVE_LEN];
8795         u64 nodes; // [DM]
8796         char plyext;
8797         int ignore = FALSE;
8798         int prefixHint = FALSE;
8799         mvname[0] = NULLCHAR;
8800
8801         switch (gameMode) {
8802           case MachinePlaysBlack:
8803           case IcsPlayingBlack:
8804             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8805             break;
8806           case MachinePlaysWhite:
8807           case IcsPlayingWhite:
8808             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8809             break;
8810           case AnalyzeMode:
8811           case AnalyzeFile:
8812             break;
8813           case IcsObserving: /* [DM] icsEngineAnalyze */
8814             if (!appData.icsEngineAnalyze) ignore = TRUE;
8815             break;
8816           case TwoMachinesPlay:
8817             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8818                 ignore = TRUE;
8819             }
8820             break;
8821           default:
8822             ignore = TRUE;
8823             break;
8824         }
8825
8826         if (!ignore) {
8827             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8828             buf1[0] = NULLCHAR;
8829             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8830                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8831
8832                 if (plyext != ' ' && plyext != '\t') {
8833                     time *= 100;
8834                 }
8835
8836                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8837                 if( cps->scoreIsAbsolute &&
8838                     ( gameMode == MachinePlaysBlack ||
8839                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8840                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8841                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8842                      !WhiteOnMove(currentMove)
8843                     ) )
8844                 {
8845                     curscore = -curscore;
8846                 }
8847
8848                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8849
8850                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8851                         char buf[MSG_SIZ];
8852                         FILE *f;
8853                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8854                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8855                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8856                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8857                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8858                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8859                                 fclose(f);
8860                         } else DisplayError(_("failed writing PV"), 0);
8861                 }
8862
8863                 tempStats.depth = plylev;
8864                 tempStats.nodes = nodes;
8865                 tempStats.time = time;
8866                 tempStats.score = curscore;
8867                 tempStats.got_only_move = 0;
8868
8869                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8870                         int ticklen;
8871
8872                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8873                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8874                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8875                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8876                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8877                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8878                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8879                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8880                 }
8881
8882                 /* Buffer overflow protection */
8883                 if (pv[0] != NULLCHAR) {
8884                     if (strlen(pv) >= sizeof(tempStats.movelist)
8885                         && appData.debugMode) {
8886                         fprintf(debugFP,
8887                                 "PV is too long; using the first %u bytes.\n",
8888                                 (unsigned) sizeof(tempStats.movelist) - 1);
8889                     }
8890
8891                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8892                 } else {
8893                     sprintf(tempStats.movelist, " no PV\n");
8894                 }
8895
8896                 if (tempStats.seen_stat) {
8897                     tempStats.ok_to_send = 1;
8898                 }
8899
8900                 if (strchr(tempStats.movelist, '(') != NULL) {
8901                     tempStats.line_is_book = 1;
8902                     tempStats.nr_moves = 0;
8903                     tempStats.moves_left = 0;
8904                 } else {
8905                     tempStats.line_is_book = 0;
8906                 }
8907
8908                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8909                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8910
8911                 SendProgramStatsToFrontend( cps, &tempStats );
8912
8913                 /*
8914                     [AS] Protect the thinkOutput buffer from overflow... this
8915                     is only useful if buf1 hasn't overflowed first!
8916                 */
8917                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8918                          plylev,
8919                          (gameMode == TwoMachinesPlay ?
8920                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8921                          ((double) curscore) / 100.0,
8922                          prefixHint ? lastHint : "",
8923                          prefixHint ? " " : "" );
8924
8925                 if( buf1[0] != NULLCHAR ) {
8926                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8927
8928                     if( strlen(pv) > max_len ) {
8929                         if( appData.debugMode) {
8930                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8931                         }
8932                         pv[max_len+1] = '\0';
8933                     }
8934
8935                     strcat( thinkOutput, pv);
8936                 }
8937
8938                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8939                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8940                     DisplayMove(currentMove - 1);
8941                 }
8942                 return;
8943
8944             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8945                 /* crafty (9.25+) says "(only move) <move>"
8946                  * if there is only 1 legal move
8947                  */
8948                 sscanf(p, "(only move) %s", buf1);
8949                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8950                 sprintf(programStats.movelist, "%s (only move)", buf1);
8951                 programStats.depth = 1;
8952                 programStats.nr_moves = 1;
8953                 programStats.moves_left = 1;
8954                 programStats.nodes = 1;
8955                 programStats.time = 1;
8956                 programStats.got_only_move = 1;
8957
8958                 /* Not really, but we also use this member to
8959                    mean "line isn't going to change" (Crafty
8960                    isn't searching, so stats won't change) */
8961                 programStats.line_is_book = 1;
8962
8963                 SendProgramStatsToFrontend( cps, &programStats );
8964
8965                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8966                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8967                     DisplayMove(currentMove - 1);
8968                 }
8969                 return;
8970             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8971                               &time, &nodes, &plylev, &mvleft,
8972                               &mvtot, mvname) >= 5) {
8973                 /* The stat01: line is from Crafty (9.29+) in response
8974                    to the "." command */
8975                 programStats.seen_stat = 1;
8976                 cps->maybeThinking = TRUE;
8977
8978                 if (programStats.got_only_move || !appData.periodicUpdates)
8979                   return;
8980
8981                 programStats.depth = plylev;
8982                 programStats.time = time;
8983                 programStats.nodes = nodes;
8984                 programStats.moves_left = mvleft;
8985                 programStats.nr_moves = mvtot;
8986                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8987                 programStats.ok_to_send = 1;
8988                 programStats.movelist[0] = '\0';
8989
8990                 SendProgramStatsToFrontend( cps, &programStats );
8991
8992                 return;
8993
8994             } else if (strncmp(message,"++",2) == 0) {
8995                 /* Crafty 9.29+ outputs this */
8996                 programStats.got_fail = 2;
8997                 return;
8998
8999             } else if (strncmp(message,"--",2) == 0) {
9000                 /* Crafty 9.29+ outputs this */
9001                 programStats.got_fail = 1;
9002                 return;
9003
9004             } else if (thinkOutput[0] != NULLCHAR &&
9005                        strncmp(message, "    ", 4) == 0) {
9006                 unsigned message_len;
9007
9008                 p = message;
9009                 while (*p && *p == ' ') p++;
9010
9011                 message_len = strlen( p );
9012
9013                 /* [AS] Avoid buffer overflow */
9014                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9015                     strcat(thinkOutput, " ");
9016                     strcat(thinkOutput, p);
9017                 }
9018
9019                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9020                     strcat(programStats.movelist, " ");
9021                     strcat(programStats.movelist, p);
9022                 }
9023
9024                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9025                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9026                     DisplayMove(currentMove - 1);
9027                 }
9028                 return;
9029             }
9030         }
9031         else {
9032             buf1[0] = NULLCHAR;
9033
9034             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9035                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9036             {
9037                 ChessProgramStats cpstats;
9038
9039                 if (plyext != ' ' && plyext != '\t') {
9040                     time *= 100;
9041                 }
9042
9043                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9044                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9045                     curscore = -curscore;
9046                 }
9047
9048                 cpstats.depth = plylev;
9049                 cpstats.nodes = nodes;
9050                 cpstats.time = time;
9051                 cpstats.score = curscore;
9052                 cpstats.got_only_move = 0;
9053                 cpstats.movelist[0] = '\0';
9054
9055                 if (buf1[0] != NULLCHAR) {
9056                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9057                 }
9058
9059                 cpstats.ok_to_send = 0;
9060                 cpstats.line_is_book = 0;
9061                 cpstats.nr_moves = 0;
9062                 cpstats.moves_left = 0;
9063
9064                 SendProgramStatsToFrontend( cps, &cpstats );
9065             }
9066         }
9067     }
9068 }
9069
9070
9071 /* Parse a game score from the character string "game", and
9072    record it as the history of the current game.  The game
9073    score is NOT assumed to start from the standard position.
9074    The display is not updated in any way.
9075    */
9076 void
9077 ParseGameHistory (char *game)
9078 {
9079     ChessMove moveType;
9080     int fromX, fromY, toX, toY, boardIndex;
9081     char promoChar;
9082     char *p, *q;
9083     char buf[MSG_SIZ];
9084
9085     if (appData.debugMode)
9086       fprintf(debugFP, "Parsing game history: %s\n", game);
9087
9088     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9089     gameInfo.site = StrSave(appData.icsHost);
9090     gameInfo.date = PGNDate();
9091     gameInfo.round = StrSave("-");
9092
9093     /* Parse out names of players */
9094     while (*game == ' ') game++;
9095     p = buf;
9096     while (*game != ' ') *p++ = *game++;
9097     *p = NULLCHAR;
9098     gameInfo.white = StrSave(buf);
9099     while (*game == ' ') game++;
9100     p = buf;
9101     while (*game != ' ' && *game != '\n') *p++ = *game++;
9102     *p = NULLCHAR;
9103     gameInfo.black = StrSave(buf);
9104
9105     /* Parse moves */
9106     boardIndex = blackPlaysFirst ? 1 : 0;
9107     yynewstr(game);
9108     for (;;) {
9109         yyboardindex = boardIndex;
9110         moveType = (ChessMove) Myylex();
9111         switch (moveType) {
9112           case IllegalMove:             /* maybe suicide chess, etc. */
9113   if (appData.debugMode) {
9114     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9115     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9116     setbuf(debugFP, NULL);
9117   }
9118           case WhitePromotion:
9119           case BlackPromotion:
9120           case WhiteNonPromotion:
9121           case BlackNonPromotion:
9122           case NormalMove:
9123           case WhiteCapturesEnPassant:
9124           case BlackCapturesEnPassant:
9125           case WhiteKingSideCastle:
9126           case WhiteQueenSideCastle:
9127           case BlackKingSideCastle:
9128           case BlackQueenSideCastle:
9129           case WhiteKingSideCastleWild:
9130           case WhiteQueenSideCastleWild:
9131           case BlackKingSideCastleWild:
9132           case BlackQueenSideCastleWild:
9133           /* PUSH Fabien */
9134           case WhiteHSideCastleFR:
9135           case WhiteASideCastleFR:
9136           case BlackHSideCastleFR:
9137           case BlackASideCastleFR:
9138           /* POP Fabien */
9139             fromX = currentMoveString[0] - AAA;
9140             fromY = currentMoveString[1] - ONE;
9141             toX = currentMoveString[2] - AAA;
9142             toY = currentMoveString[3] - ONE;
9143             promoChar = currentMoveString[4];
9144             break;
9145           case WhiteDrop:
9146           case BlackDrop:
9147             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9148             fromX = moveType == WhiteDrop ?
9149               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9150             (int) CharToPiece(ToLower(currentMoveString[0]));
9151             fromY = DROP_RANK;
9152             toX = currentMoveString[2] - AAA;
9153             toY = currentMoveString[3] - ONE;
9154             promoChar = NULLCHAR;
9155             break;
9156           case AmbiguousMove:
9157             /* bug? */
9158             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9159   if (appData.debugMode) {
9160     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9161     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9162     setbuf(debugFP, NULL);
9163   }
9164             DisplayError(buf, 0);
9165             return;
9166           case ImpossibleMove:
9167             /* bug? */
9168             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9169   if (appData.debugMode) {
9170     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9171     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9172     setbuf(debugFP, NULL);
9173   }
9174             DisplayError(buf, 0);
9175             return;
9176           case EndOfFile:
9177             if (boardIndex < backwardMostMove) {
9178                 /* Oops, gap.  How did that happen? */
9179                 DisplayError(_("Gap in move list"), 0);
9180                 return;
9181             }
9182             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9183             if (boardIndex > forwardMostMove) {
9184                 forwardMostMove = boardIndex;
9185             }
9186             return;
9187           case ElapsedTime:
9188             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9189                 strcat(parseList[boardIndex-1], " ");
9190                 strcat(parseList[boardIndex-1], yy_text);
9191             }
9192             continue;
9193           case Comment:
9194           case PGNTag:
9195           case NAG:
9196           default:
9197             /* ignore */
9198             continue;
9199           case WhiteWins:
9200           case BlackWins:
9201           case GameIsDrawn:
9202           case GameUnfinished:
9203             if (gameMode == IcsExamining) {
9204                 if (boardIndex < backwardMostMove) {
9205                     /* Oops, gap.  How did that happen? */
9206                     return;
9207                 }
9208                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9209                 return;
9210             }
9211             gameInfo.result = moveType;
9212             p = strchr(yy_text, '{');
9213             if (p == NULL) p = strchr(yy_text, '(');
9214             if (p == NULL) {
9215                 p = yy_text;
9216                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9217             } else {
9218                 q = strchr(p, *p == '{' ? '}' : ')');
9219                 if (q != NULL) *q = NULLCHAR;
9220                 p++;
9221             }
9222             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9223             gameInfo.resultDetails = StrSave(p);
9224             continue;
9225         }
9226         if (boardIndex >= forwardMostMove &&
9227             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9228             backwardMostMove = blackPlaysFirst ? 1 : 0;
9229             return;
9230         }
9231         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9232                                  fromY, fromX, toY, toX, promoChar,
9233                                  parseList[boardIndex]);
9234         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9235         /* currentMoveString is set as a side-effect of yylex */
9236         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9237         strcat(moveList[boardIndex], "\n");
9238         boardIndex++;
9239         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9240         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9241           case MT_NONE:
9242           case MT_STALEMATE:
9243           default:
9244             break;
9245           case MT_CHECK:
9246             if(gameInfo.variant != VariantShogi)
9247                 strcat(parseList[boardIndex - 1], "+");
9248             break;
9249           case MT_CHECKMATE:
9250           case MT_STAINMATE:
9251             strcat(parseList[boardIndex - 1], "#");
9252             break;
9253         }
9254     }
9255 }
9256
9257
9258 /* Apply a move to the given board  */
9259 void
9260 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9261 {
9262   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9263   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9264
9265     /* [HGM] compute & store e.p. status and castling rights for new position */
9266     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9267
9268       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9269       oldEP = (signed char)board[EP_STATUS];
9270       board[EP_STATUS] = EP_NONE;
9271
9272   if (fromY == DROP_RANK) {
9273         /* must be first */
9274         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9275             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9276             return;
9277         }
9278         piece = board[toY][toX] = (ChessSquare) fromX;
9279   } else {
9280       int i;
9281
9282       if( board[toY][toX] != EmptySquare )
9283            board[EP_STATUS] = EP_CAPTURE;
9284
9285       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9286            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9287                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9288       } else
9289       if( board[fromY][fromX] == WhitePawn ) {
9290            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9291                board[EP_STATUS] = EP_PAWN_MOVE;
9292            if( toY-fromY==2) {
9293                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9294                         gameInfo.variant != VariantBerolina || toX < fromX)
9295                       board[EP_STATUS] = toX | berolina;
9296                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9297                         gameInfo.variant != VariantBerolina || toX > fromX)
9298                       board[EP_STATUS] = toX;
9299            }
9300       } else
9301       if( board[fromY][fromX] == BlackPawn ) {
9302            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9303                board[EP_STATUS] = EP_PAWN_MOVE;
9304            if( toY-fromY== -2) {
9305                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9306                         gameInfo.variant != VariantBerolina || toX < fromX)
9307                       board[EP_STATUS] = toX | berolina;
9308                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9309                         gameInfo.variant != VariantBerolina || toX > fromX)
9310                       board[EP_STATUS] = toX;
9311            }
9312        }
9313
9314        for(i=0; i<nrCastlingRights; i++) {
9315            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9316               board[CASTLING][i] == toX   && castlingRank[i] == toY
9317              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9318        }
9319
9320      if (fromX == toX && fromY == toY) return;
9321
9322      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9323      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9324      if(gameInfo.variant == VariantKnightmate)
9325          king += (int) WhiteUnicorn - (int) WhiteKing;
9326
9327     /* Code added by Tord: */
9328     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9329     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9330         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9331       board[fromY][fromX] = EmptySquare;
9332       board[toY][toX] = EmptySquare;
9333       if((toX > fromX) != (piece == WhiteRook)) {
9334         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9335       } else {
9336         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9337       }
9338     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9339                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9340       board[fromY][fromX] = EmptySquare;
9341       board[toY][toX] = EmptySquare;
9342       if((toX > fromX) != (piece == BlackRook)) {
9343         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9344       } else {
9345         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9346       }
9347     /* End of code added by Tord */
9348
9349     } else if (board[fromY][fromX] == king
9350         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9351         && toY == fromY && toX > fromX+1) {
9352         board[fromY][fromX] = EmptySquare;
9353         board[toY][toX] = king;
9354         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9355         board[fromY][BOARD_RGHT-1] = EmptySquare;
9356     } else if (board[fromY][fromX] == king
9357         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9358                && toY == fromY && toX < fromX-1) {
9359         board[fromY][fromX] = EmptySquare;
9360         board[toY][toX] = king;
9361         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9362         board[fromY][BOARD_LEFT] = EmptySquare;
9363     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9364                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9365                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9366                ) {
9367         /* white pawn promotion */
9368         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9369         if(gameInfo.variant==VariantBughouse ||
9370            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9371             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9372         board[fromY][fromX] = EmptySquare;
9373     } else if ((fromY >= BOARD_HEIGHT>>1)
9374                && (toX != fromX)
9375                && gameInfo.variant != VariantXiangqi
9376                && gameInfo.variant != VariantBerolina
9377                && (board[fromY][fromX] == WhitePawn)
9378                && (board[toY][toX] == EmptySquare)) {
9379         board[fromY][fromX] = EmptySquare;
9380         board[toY][toX] = WhitePawn;
9381         captured = board[toY - 1][toX];
9382         board[toY - 1][toX] = EmptySquare;
9383     } else if ((fromY == BOARD_HEIGHT-4)
9384                && (toX == fromX)
9385                && gameInfo.variant == VariantBerolina
9386                && (board[fromY][fromX] == WhitePawn)
9387                && (board[toY][toX] == EmptySquare)) {
9388         board[fromY][fromX] = EmptySquare;
9389         board[toY][toX] = WhitePawn;
9390         if(oldEP & EP_BEROLIN_A) {
9391                 captured = board[fromY][fromX-1];
9392                 board[fromY][fromX-1] = EmptySquare;
9393         }else{  captured = board[fromY][fromX+1];
9394                 board[fromY][fromX+1] = EmptySquare;
9395         }
9396     } else if (board[fromY][fromX] == king
9397         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9398                && toY == fromY && toX > fromX+1) {
9399         board[fromY][fromX] = EmptySquare;
9400         board[toY][toX] = king;
9401         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9402         board[fromY][BOARD_RGHT-1] = EmptySquare;
9403     } else if (board[fromY][fromX] == king
9404         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9405                && toY == fromY && toX < fromX-1) {
9406         board[fromY][fromX] = EmptySquare;
9407         board[toY][toX] = king;
9408         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9409         board[fromY][BOARD_LEFT] = EmptySquare;
9410     } else if (fromY == 7 && fromX == 3
9411                && board[fromY][fromX] == BlackKing
9412                && toY == 7 && toX == 5) {
9413         board[fromY][fromX] = EmptySquare;
9414         board[toY][toX] = BlackKing;
9415         board[fromY][7] = EmptySquare;
9416         board[toY][4] = BlackRook;
9417     } else if (fromY == 7 && fromX == 3
9418                && board[fromY][fromX] == BlackKing
9419                && toY == 7 && toX == 1) {
9420         board[fromY][fromX] = EmptySquare;
9421         board[toY][toX] = BlackKing;
9422         board[fromY][0] = EmptySquare;
9423         board[toY][2] = BlackRook;
9424     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9425                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9426                && toY < promoRank && promoChar
9427                ) {
9428         /* black pawn promotion */
9429         board[toY][toX] = CharToPiece(ToLower(promoChar));
9430         if(gameInfo.variant==VariantBughouse ||
9431            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9432             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9433         board[fromY][fromX] = EmptySquare;
9434     } else if ((fromY < BOARD_HEIGHT>>1)
9435                && (toX != fromX)
9436                && gameInfo.variant != VariantXiangqi
9437                && gameInfo.variant != VariantBerolina
9438                && (board[fromY][fromX] == BlackPawn)
9439                && (board[toY][toX] == EmptySquare)) {
9440         board[fromY][fromX] = EmptySquare;
9441         board[toY][toX] = BlackPawn;
9442         captured = board[toY + 1][toX];
9443         board[toY + 1][toX] = EmptySquare;
9444     } else if ((fromY == 3)
9445                && (toX == fromX)
9446                && gameInfo.variant == VariantBerolina
9447                && (board[fromY][fromX] == BlackPawn)
9448                && (board[toY][toX] == EmptySquare)) {
9449         board[fromY][fromX] = EmptySquare;
9450         board[toY][toX] = BlackPawn;
9451         if(oldEP & EP_BEROLIN_A) {
9452                 captured = board[fromY][fromX-1];
9453                 board[fromY][fromX-1] = EmptySquare;
9454         }else{  captured = board[fromY][fromX+1];
9455                 board[fromY][fromX+1] = EmptySquare;
9456         }
9457     } else {
9458         board[toY][toX] = board[fromY][fromX];
9459         board[fromY][fromX] = EmptySquare;
9460     }
9461   }
9462
9463     if (gameInfo.holdingsWidth != 0) {
9464
9465       /* !!A lot more code needs to be written to support holdings  */
9466       /* [HGM] OK, so I have written it. Holdings are stored in the */
9467       /* penultimate board files, so they are automaticlly stored   */
9468       /* in the game history.                                       */
9469       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9470                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9471         /* Delete from holdings, by decreasing count */
9472         /* and erasing image if necessary            */
9473         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9474         if(p < (int) BlackPawn) { /* white drop */
9475              p -= (int)WhitePawn;
9476                  p = PieceToNumber((ChessSquare)p);
9477              if(p >= gameInfo.holdingsSize) p = 0;
9478              if(--board[p][BOARD_WIDTH-2] <= 0)
9479                   board[p][BOARD_WIDTH-1] = EmptySquare;
9480              if((int)board[p][BOARD_WIDTH-2] < 0)
9481                         board[p][BOARD_WIDTH-2] = 0;
9482         } else {                  /* black drop */
9483              p -= (int)BlackPawn;
9484                  p = PieceToNumber((ChessSquare)p);
9485              if(p >= gameInfo.holdingsSize) p = 0;
9486              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9487                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9488              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9489                         board[BOARD_HEIGHT-1-p][1] = 0;
9490         }
9491       }
9492       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9493           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9494         /* [HGM] holdings: Add to holdings, if holdings exist */
9495         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9496                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9497                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9498         }
9499         p = (int) captured;
9500         if (p >= (int) BlackPawn) {
9501           p -= (int)BlackPawn;
9502           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9503                   /* in Shogi restore piece to its original  first */
9504                   captured = (ChessSquare) (DEMOTED captured);
9505                   p = DEMOTED p;
9506           }
9507           p = PieceToNumber((ChessSquare)p);
9508           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9509           board[p][BOARD_WIDTH-2]++;
9510           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9511         } else {
9512           p -= (int)WhitePawn;
9513           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9514                   captured = (ChessSquare) (DEMOTED captured);
9515                   p = DEMOTED p;
9516           }
9517           p = PieceToNumber((ChessSquare)p);
9518           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9519           board[BOARD_HEIGHT-1-p][1]++;
9520           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9521         }
9522       }
9523     } else if (gameInfo.variant == VariantAtomic) {
9524       if (captured != EmptySquare) {
9525         int y, x;
9526         for (y = toY-1; y <= toY+1; y++) {
9527           for (x = toX-1; x <= toX+1; x++) {
9528             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9529                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9530               board[y][x] = EmptySquare;
9531             }
9532           }
9533         }
9534         board[toY][toX] = EmptySquare;
9535       }
9536     }
9537     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9538         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9539     } else
9540     if(promoChar == '+') {
9541         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9542         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9543     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9544         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9545         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9546            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9547         board[toY][toX] = newPiece;
9548     }
9549     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9550                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9551         // [HGM] superchess: take promotion piece out of holdings
9552         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9553         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9554             if(!--board[k][BOARD_WIDTH-2])
9555                 board[k][BOARD_WIDTH-1] = EmptySquare;
9556         } else {
9557             if(!--board[BOARD_HEIGHT-1-k][1])
9558                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9559         }
9560     }
9561
9562 }
9563
9564 /* Updates forwardMostMove */
9565 void
9566 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9567 {
9568 //    forwardMostMove++; // [HGM] bare: moved downstream
9569
9570     (void) CoordsToAlgebraic(boards[forwardMostMove],
9571                              PosFlags(forwardMostMove),
9572                              fromY, fromX, toY, toX, promoChar,
9573                              parseList[forwardMostMove]);
9574
9575     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9576         int timeLeft; static int lastLoadFlag=0; int king, piece;
9577         piece = boards[forwardMostMove][fromY][fromX];
9578         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9579         if(gameInfo.variant == VariantKnightmate)
9580             king += (int) WhiteUnicorn - (int) WhiteKing;
9581         if(forwardMostMove == 0) {
9582             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9583                 fprintf(serverMoves, "%s;", UserName());
9584             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9585                 fprintf(serverMoves, "%s;", second.tidy);
9586             fprintf(serverMoves, "%s;", first.tidy);
9587             if(gameMode == MachinePlaysWhite)
9588                 fprintf(serverMoves, "%s;", UserName());
9589             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9590                 fprintf(serverMoves, "%s;", second.tidy);
9591         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9592         lastLoadFlag = loadFlag;
9593         // print base move
9594         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9595         // print castling suffix
9596         if( toY == fromY && piece == king ) {
9597             if(toX-fromX > 1)
9598                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9599             if(fromX-toX >1)
9600                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9601         }
9602         // e.p. suffix
9603         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9604              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9605              boards[forwardMostMove][toY][toX] == EmptySquare
9606              && fromX != toX && fromY != toY)
9607                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9608         // promotion suffix
9609         if(promoChar != NULLCHAR)
9610                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9611         if(!loadFlag) {
9612                 char buf[MOVE_LEN*2], *p; int len;
9613             fprintf(serverMoves, "/%d/%d",
9614                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9615             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9616             else                      timeLeft = blackTimeRemaining/1000;
9617             fprintf(serverMoves, "/%d", timeLeft);
9618                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9619                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9620                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9621             fprintf(serverMoves, "/%s", buf);
9622         }
9623         fflush(serverMoves);
9624     }
9625
9626     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9627         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9628       return;
9629     }
9630     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9631     if (commentList[forwardMostMove+1] != NULL) {
9632         free(commentList[forwardMostMove+1]);
9633         commentList[forwardMostMove+1] = NULL;
9634     }
9635     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9636     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9637     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9638     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9639     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9640     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9641     adjustedClock = FALSE;
9642     gameInfo.result = GameUnfinished;
9643     if (gameInfo.resultDetails != NULL) {
9644         free(gameInfo.resultDetails);
9645         gameInfo.resultDetails = NULL;
9646     }
9647     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9648                               moveList[forwardMostMove - 1]);
9649     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9650       case MT_NONE:
9651       case MT_STALEMATE:
9652       default:
9653         break;
9654       case MT_CHECK:
9655         if(gameInfo.variant != VariantShogi)
9656             strcat(parseList[forwardMostMove - 1], "+");
9657         break;
9658       case MT_CHECKMATE:
9659       case MT_STAINMATE:
9660         strcat(parseList[forwardMostMove - 1], "#");
9661         break;
9662     }
9663
9664 }
9665
9666 /* Updates currentMove if not pausing */
9667 void
9668 ShowMove (int fromX, int fromY, int toX, int toY)
9669 {
9670     int instant = (gameMode == PlayFromGameFile) ?
9671         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9672     if(appData.noGUI) return;
9673     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9674         if (!instant) {
9675             if (forwardMostMove == currentMove + 1) {
9676                 AnimateMove(boards[forwardMostMove - 1],
9677                             fromX, fromY, toX, toY);
9678             }
9679             if (appData.highlightLastMove) {
9680                 SetHighlights(fromX, fromY, toX, toY);
9681             }
9682         }
9683         currentMove = forwardMostMove;
9684     }
9685
9686     if (instant) return;
9687
9688     DisplayMove(currentMove - 1);
9689     DrawPosition(FALSE, boards[currentMove]);
9690     DisplayBothClocks();
9691     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9692 }
9693
9694 void
9695 SendEgtPath (ChessProgramState *cps)
9696 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9697         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9698
9699         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9700
9701         while(*p) {
9702             char c, *q = name+1, *r, *s;
9703
9704             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9705             while(*p && *p != ',') *q++ = *p++;
9706             *q++ = ':'; *q = 0;
9707             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9708                 strcmp(name, ",nalimov:") == 0 ) {
9709                 // take nalimov path from the menu-changeable option first, if it is defined
9710               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9711                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9712             } else
9713             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9714                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9715                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9716                 s = r = StrStr(s, ":") + 1; // beginning of path info
9717                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9718                 c = *r; *r = 0;             // temporarily null-terminate path info
9719                     *--q = 0;               // strip of trailig ':' from name
9720                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9721                 *r = c;
9722                 SendToProgram(buf,cps);     // send egtbpath command for this format
9723             }
9724             if(*p == ',') p++; // read away comma to position for next format name
9725         }
9726 }
9727
9728 void
9729 InitChessProgram (ChessProgramState *cps, int setup)
9730 /* setup needed to setup FRC opening position */
9731 {
9732     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9733     if (appData.noChessProgram) return;
9734     hintRequested = FALSE;
9735     bookRequested = FALSE;
9736
9737     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9738     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9739     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9740     if(cps->memSize) { /* [HGM] memory */
9741       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9742         SendToProgram(buf, cps);
9743     }
9744     SendEgtPath(cps); /* [HGM] EGT */
9745     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9746       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9747         SendToProgram(buf, cps);
9748     }
9749
9750     SendToProgram(cps->initString, cps);
9751     if (gameInfo.variant != VariantNormal &&
9752         gameInfo.variant != VariantLoadable
9753         /* [HGM] also send variant if board size non-standard */
9754         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9755                                             ) {
9756       char *v = VariantName(gameInfo.variant);
9757       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9758         /* [HGM] in protocol 1 we have to assume all variants valid */
9759         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9760         DisplayFatalError(buf, 0, 1);
9761         return;
9762       }
9763
9764       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9765       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9766       if( gameInfo.variant == VariantXiangqi )
9767            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9768       if( gameInfo.variant == VariantShogi )
9769            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9770       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9771            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9772       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9773           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9774            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9775       if( gameInfo.variant == VariantCourier )
9776            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9777       if( gameInfo.variant == VariantSuper )
9778            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9779       if( gameInfo.variant == VariantGreat )
9780            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9781       if( gameInfo.variant == VariantSChess )
9782            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9783       if( gameInfo.variant == VariantGrand )
9784            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9785
9786       if(overruled) {
9787         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9788                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9789            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9790            if(StrStr(cps->variants, b) == NULL) {
9791                // specific sized variant not known, check if general sizing allowed
9792                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9793                    if(StrStr(cps->variants, "boardsize") == NULL) {
9794                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9795                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9796                        DisplayFatalError(buf, 0, 1);
9797                        return;
9798                    }
9799                    /* [HGM] here we really should compare with the maximum supported board size */
9800                }
9801            }
9802       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9803       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9804       SendToProgram(buf, cps);
9805     }
9806     currentlyInitializedVariant = gameInfo.variant;
9807
9808     /* [HGM] send opening position in FRC to first engine */
9809     if(setup) {
9810           SendToProgram("force\n", cps);
9811           SendBoard(cps, 0);
9812           /* engine is now in force mode! Set flag to wake it up after first move. */
9813           setboardSpoiledMachineBlack = 1;
9814     }
9815
9816     if (cps->sendICS) {
9817       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9818       SendToProgram(buf, cps);
9819     }
9820     cps->maybeThinking = FALSE;
9821     cps->offeredDraw = 0;
9822     if (!appData.icsActive) {
9823         SendTimeControl(cps, movesPerSession, timeControl,
9824                         timeIncrement, appData.searchDepth,
9825                         searchTime);
9826     }
9827     if (appData.showThinking
9828         // [HGM] thinking: four options require thinking output to be sent
9829         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9830                                 ) {
9831         SendToProgram("post\n", cps);
9832     }
9833     SendToProgram("hard\n", cps);
9834     if (!appData.ponderNextMove) {
9835         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9836            it without being sure what state we are in first.  "hard"
9837            is not a toggle, so that one is OK.
9838          */
9839         SendToProgram("easy\n", cps);
9840     }
9841     if (cps->usePing) {
9842       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9843       SendToProgram(buf, cps);
9844     }
9845     cps->initDone = TRUE;
9846     ClearEngineOutputPane(cps == &second);
9847 }
9848
9849
9850 void
9851 StartChessProgram (ChessProgramState *cps)
9852 {
9853     char buf[MSG_SIZ];
9854     int err;
9855
9856     if (appData.noChessProgram) return;
9857     cps->initDone = FALSE;
9858
9859     if (strcmp(cps->host, "localhost") == 0) {
9860         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9861     } else if (*appData.remoteShell == NULLCHAR) {
9862         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9863     } else {
9864         if (*appData.remoteUser == NULLCHAR) {
9865           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9866                     cps->program);
9867         } else {
9868           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9869                     cps->host, appData.remoteUser, cps->program);
9870         }
9871         err = StartChildProcess(buf, "", &cps->pr);
9872     }
9873
9874     if (err != 0) {
9875       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9876         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9877         if(cps != &first) return;
9878         appData.noChessProgram = TRUE;
9879         ThawUI();
9880         SetNCPMode();
9881 //      DisplayFatalError(buf, err, 1);
9882 //      cps->pr = NoProc;
9883 //      cps->isr = NULL;
9884         return;
9885     }
9886
9887     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9888     if (cps->protocolVersion > 1) {
9889       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9890       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9891       cps->comboCnt = 0;  //                and values of combo boxes
9892       SendToProgram(buf, cps);
9893     } else {
9894       SendToProgram("xboard\n", cps);
9895     }
9896 }
9897
9898 void
9899 TwoMachinesEventIfReady P((void))
9900 {
9901   static int curMess = 0;
9902   if (first.lastPing != first.lastPong) {
9903     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9904     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9905     return;
9906   }
9907   if (second.lastPing != second.lastPong) {
9908     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9909     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9910     return;
9911   }
9912   DisplayMessage("", ""); curMess = 0;
9913   ThawUI();
9914   TwoMachinesEvent();
9915 }
9916
9917 char *
9918 MakeName (char *template)
9919 {
9920     time_t clock;
9921     struct tm *tm;
9922     static char buf[MSG_SIZ];
9923     char *p = buf;
9924     int i;
9925
9926     clock = time((time_t *)NULL);
9927     tm = localtime(&clock);
9928
9929     while(*p++ = *template++) if(p[-1] == '%') {
9930         switch(*template++) {
9931           case 0:   *p = 0; return buf;
9932           case 'Y': i = tm->tm_year+1900; break;
9933           case 'y': i = tm->tm_year-100; break;
9934           case 'M': i = tm->tm_mon+1; break;
9935           case 'd': i = tm->tm_mday; break;
9936           case 'h': i = tm->tm_hour; break;
9937           case 'm': i = tm->tm_min; break;
9938           case 's': i = tm->tm_sec; break;
9939           default:  i = 0;
9940         }
9941         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9942     }
9943     return buf;
9944 }
9945
9946 int
9947 CountPlayers (char *p)
9948 {
9949     int n = 0;
9950     while(p = strchr(p, '\n')) p++, n++; // count participants
9951     return n;
9952 }
9953
9954 FILE *
9955 WriteTourneyFile (char *results, FILE *f)
9956 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9957     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9958     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9959         // create a file with tournament description
9960         fprintf(f, "-participants {%s}\n", appData.participants);
9961         fprintf(f, "-seedBase %d\n", appData.seedBase);
9962         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9963         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9964         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9965         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9966         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9967         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9968         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9969         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9970         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9971         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9972         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9973         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9974         if(searchTime > 0)
9975                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9976         else {
9977                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9978                 fprintf(f, "-tc %s\n", appData.timeControl);
9979                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9980         }
9981         fprintf(f, "-results \"%s\"\n", results);
9982     }
9983     return f;
9984 }
9985
9986 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9987
9988 void
9989 Substitute (char *participants, int expunge)
9990 {
9991     int i, changed, changes=0, nPlayers=0;
9992     char *p, *q, *r, buf[MSG_SIZ];
9993     if(participants == NULL) return;
9994     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9995     r = p = participants; q = appData.participants;
9996     while(*p && *p == *q) {
9997         if(*p == '\n') r = p+1, nPlayers++;
9998         p++; q++;
9999     }
10000     if(*p) { // difference
10001         while(*p && *p++ != '\n');
10002         while(*q && *q++ != '\n');
10003       changed = nPlayers;
10004         changes = 1 + (strcmp(p, q) != 0);
10005     }
10006     if(changes == 1) { // a single engine mnemonic was changed
10007         q = r; while(*q) nPlayers += (*q++ == '\n');
10008         p = buf; while(*r && (*p = *r++) != '\n') p++;
10009         *p = NULLCHAR;
10010         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10011         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10012         if(mnemonic[i]) { // The substitute is valid
10013             FILE *f;
10014             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10015                 flock(fileno(f), LOCK_EX);
10016                 ParseArgsFromFile(f);
10017                 fseek(f, 0, SEEK_SET);
10018                 FREE(appData.participants); appData.participants = participants;
10019                 if(expunge) { // erase results of replaced engine
10020                     int len = strlen(appData.results), w, b, dummy;
10021                     for(i=0; i<len; i++) {
10022                         Pairing(i, nPlayers, &w, &b, &dummy);
10023                         if((w == changed || b == changed) && appData.results[i] == '*') {
10024                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10025                             fclose(f);
10026                             return;
10027                         }
10028                     }
10029                     for(i=0; i<len; i++) {
10030                         Pairing(i, nPlayers, &w, &b, &dummy);
10031                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10032                     }
10033                 }
10034                 WriteTourneyFile(appData.results, f);
10035                 fclose(f); // release lock
10036                 return;
10037             }
10038         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10039     }
10040     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10041     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10042     free(participants);
10043     return;
10044 }
10045
10046 int
10047 CreateTourney (char *name)
10048 {
10049         FILE *f;
10050         if(matchMode && strcmp(name, appData.tourneyFile)) {
10051              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10052         }
10053         if(name[0] == NULLCHAR) {
10054             if(appData.participants[0])
10055                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10056             return 0;
10057         }
10058         f = fopen(name, "r");
10059         if(f) { // file exists
10060             ASSIGN(appData.tourneyFile, name);
10061             ParseArgsFromFile(f); // parse it
10062         } else {
10063             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10064             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10065                 DisplayError(_("Not enough participants"), 0);
10066                 return 0;
10067             }
10068             ASSIGN(appData.tourneyFile, name);
10069             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10070             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10071         }
10072         fclose(f);
10073         appData.noChessProgram = FALSE;
10074         appData.clockMode = TRUE;
10075         SetGNUMode();
10076         return 1;
10077 }
10078
10079 int
10080 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10081 {
10082     char buf[MSG_SIZ], *p, *q;
10083     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10084     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10085     skip = !all && group[0]; // if group requested, we start in skip mode
10086     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10087         p = names; q = buf; header = 0;
10088         while(*p && *p != '\n') *q++ = *p++;
10089         *q = 0;
10090         if(*p == '\n') p++;
10091         if(buf[0] == '#') {
10092             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10093             depth++; // we must be entering a new group
10094             if(all) continue; // suppress printing group headers when complete list requested
10095             header = 1;
10096             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10097         }
10098         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10099         if(engineList[i]) free(engineList[i]);
10100         engineList[i] = strdup(buf);
10101         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10102         if(engineMnemonic[i]) free(engineMnemonic[i]);
10103         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10104             strcat(buf, " (");
10105             sscanf(q + 8, "%s", buf + strlen(buf));
10106             strcat(buf, ")");
10107         }
10108         engineMnemonic[i] = strdup(buf);
10109         i++;
10110     }
10111     engineList[i] = engineMnemonic[i] = NULL;
10112     return i;
10113 }
10114
10115 // following implemented as macro to avoid type limitations
10116 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10117
10118 void
10119 SwapEngines (int n)
10120 {   // swap settings for first engine and other engine (so far only some selected options)
10121     int h;
10122     char *p;
10123     if(n == 0) return;
10124     SWAP(directory, p)
10125     SWAP(chessProgram, p)
10126     SWAP(isUCI, h)
10127     SWAP(hasOwnBookUCI, h)
10128     SWAP(protocolVersion, h)
10129     SWAP(reuse, h)
10130     SWAP(scoreIsAbsolute, h)
10131     SWAP(timeOdds, h)
10132     SWAP(logo, p)
10133     SWAP(pgnName, p)
10134     SWAP(pvSAN, h)
10135     SWAP(engOptions, p)
10136     SWAP(engInitString, p)
10137     SWAP(computerString, p)
10138     SWAP(features, p)
10139     SWAP(fenOverride, p)
10140     SWAP(NPS, h)
10141     SWAP(accumulateTC, h)
10142     SWAP(host, p)
10143 }
10144
10145 int
10146 SetPlayer (int player, char *p)
10147 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10148     int i;
10149     char buf[MSG_SIZ], *engineName;
10150     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10151     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10152     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10153     if(mnemonic[i]) {
10154         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10155         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10156         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10157         ParseArgsFromString(buf);
10158     }
10159     free(engineName);
10160     return i;
10161 }
10162
10163 char *recentEngines;
10164
10165 void
10166 RecentEngineEvent (int nr)
10167 {
10168     int n;
10169 //    SwapEngines(1); // bump first to second
10170 //    ReplaceEngine(&second, 1); // and load it there
10171     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10172     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10173     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10174         ReplaceEngine(&first, 0);
10175         FloatToFront(&appData.recentEngineList, command[n]);
10176     }
10177 }
10178
10179 int
10180 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10181 {   // determine players from game number
10182     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10183
10184     if(appData.tourneyType == 0) {
10185         roundsPerCycle = (nPlayers - 1) | 1;
10186         pairingsPerRound = nPlayers / 2;
10187     } else if(appData.tourneyType > 0) {
10188         roundsPerCycle = nPlayers - appData.tourneyType;
10189         pairingsPerRound = appData.tourneyType;
10190     }
10191     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10192     gamesPerCycle = gamesPerRound * roundsPerCycle;
10193     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10194     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10195     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10196     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10197     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10198     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10199
10200     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10201     if(appData.roundSync) *syncInterval = gamesPerRound;
10202
10203     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10204
10205     if(appData.tourneyType == 0) {
10206         if(curPairing == (nPlayers-1)/2 ) {
10207             *whitePlayer = curRound;
10208             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10209         } else {
10210             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10211             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10212             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10213             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10214         }
10215     } else if(appData.tourneyType > 1) {
10216         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10217         *whitePlayer = curRound + appData.tourneyType;
10218     } else if(appData.tourneyType > 0) {
10219         *whitePlayer = curPairing;
10220         *blackPlayer = curRound + appData.tourneyType;
10221     }
10222
10223     // take care of white/black alternation per round. 
10224     // For cycles and games this is already taken care of by default, derived from matchGame!
10225     return curRound & 1;
10226 }
10227
10228 int
10229 NextTourneyGame (int nr, int *swapColors)
10230 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10231     char *p, *q;
10232     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10233     FILE *tf;
10234     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10235     tf = fopen(appData.tourneyFile, "r");
10236     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10237     ParseArgsFromFile(tf); fclose(tf);
10238     InitTimeControls(); // TC might be altered from tourney file
10239
10240     nPlayers = CountPlayers(appData.participants); // count participants
10241     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10242     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10243
10244     if(syncInterval) {
10245         p = q = appData.results;
10246         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10247         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10248             DisplayMessage(_("Waiting for other game(s)"),"");
10249             waitingForGame = TRUE;
10250             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10251             return 0;
10252         }
10253         waitingForGame = FALSE;
10254     }
10255
10256     if(appData.tourneyType < 0) {
10257         if(nr>=0 && !pairingReceived) {
10258             char buf[1<<16];
10259             if(pairing.pr == NoProc) {
10260                 if(!appData.pairingEngine[0]) {
10261                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10262                     return 0;
10263                 }
10264                 StartChessProgram(&pairing); // starts the pairing engine
10265             }
10266             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10267             SendToProgram(buf, &pairing);
10268             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10269             SendToProgram(buf, &pairing);
10270             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10271         }
10272         pairingReceived = 0;                              // ... so we continue here 
10273         *swapColors = 0;
10274         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10275         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10276         matchGame = 1; roundNr = nr / syncInterval + 1;
10277     }
10278
10279     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10280
10281     // redefine engines, engine dir, etc.
10282     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10283     if(first.pr == NoProc) {
10284       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10285       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10286     }
10287     if(second.pr == NoProc) {
10288       SwapEngines(1);
10289       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10290       SwapEngines(1);         // and make that valid for second engine by swapping
10291       InitEngine(&second, 1);
10292     }
10293     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10294     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10295     return 1;
10296 }
10297
10298 void
10299 NextMatchGame ()
10300 {   // performs game initialization that does not invoke engines, and then tries to start the game
10301     int res, firstWhite, swapColors = 0;
10302     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10303     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
10304         char buf[MSG_SIZ];
10305         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10306         if(strcmp(buf, currentDebugFile)) { // name has changed
10307             FILE *f = fopen(buf, "w");
10308             if(f) { // if opening the new file failed, just keep using the old one
10309                 ASSIGN(currentDebugFile, buf);
10310                 fclose(debugFP);
10311                 debugFP = f;
10312             }
10313             if(appData.serverFileName) {
10314                 if(serverFP) fclose(serverFP);
10315                 serverFP = fopen(appData.serverFileName, "w");
10316                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10317                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10318             }
10319         }
10320     }
10321     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10322     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10323     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10324     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10325     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10326     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10327     Reset(FALSE, first.pr != NoProc);
10328     res = LoadGameOrPosition(matchGame); // setup game
10329     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10330     if(!res) return; // abort when bad game/pos file
10331     TwoMachinesEvent();
10332 }
10333
10334 void
10335 UserAdjudicationEvent (int result)
10336 {
10337     ChessMove gameResult = GameIsDrawn;
10338
10339     if( result > 0 ) {
10340         gameResult = WhiteWins;
10341     }
10342     else if( result < 0 ) {
10343         gameResult = BlackWins;
10344     }
10345
10346     if( gameMode == TwoMachinesPlay ) {
10347         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10348     }
10349 }
10350
10351
10352 // [HGM] save: calculate checksum of game to make games easily identifiable
10353 int
10354 StringCheckSum (char *s)
10355 {
10356         int i = 0;
10357         if(s==NULL) return 0;
10358         while(*s) i = i*259 + *s++;
10359         return i;
10360 }
10361
10362 int
10363 GameCheckSum ()
10364 {
10365         int i, sum=0;
10366         for(i=backwardMostMove; i<forwardMostMove; i++) {
10367                 sum += pvInfoList[i].depth;
10368                 sum += StringCheckSum(parseList[i]);
10369                 sum += StringCheckSum(commentList[i]);
10370                 sum *= 261;
10371         }
10372         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10373         return sum + StringCheckSum(commentList[i]);
10374 } // end of save patch
10375
10376 void
10377 GameEnds (ChessMove result, char *resultDetails, int whosays)
10378 {
10379     GameMode nextGameMode;
10380     int isIcsGame;
10381     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10382
10383     if(endingGame) return; /* [HGM] crash: forbid recursion */
10384     endingGame = 1;
10385     if(twoBoards) { // [HGM] dual: switch back to one board
10386         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10387         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10388     }
10389     if (appData.debugMode) {
10390       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10391               result, resultDetails ? resultDetails : "(null)", whosays);
10392     }
10393
10394     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10395
10396     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10397         /* If we are playing on ICS, the server decides when the
10398            game is over, but the engine can offer to draw, claim
10399            a draw, or resign.
10400          */
10401 #if ZIPPY
10402         if (appData.zippyPlay && first.initDone) {
10403             if (result == GameIsDrawn) {
10404                 /* In case draw still needs to be claimed */
10405                 SendToICS(ics_prefix);
10406                 SendToICS("draw\n");
10407             } else if (StrCaseStr(resultDetails, "resign")) {
10408                 SendToICS(ics_prefix);
10409                 SendToICS("resign\n");
10410             }
10411         }
10412 #endif
10413         endingGame = 0; /* [HGM] crash */
10414         return;
10415     }
10416
10417     /* If we're loading the game from a file, stop */
10418     if (whosays == GE_FILE) {
10419       (void) StopLoadGameTimer();
10420       gameFileFP = NULL;
10421     }
10422
10423     /* Cancel draw offers */
10424     first.offeredDraw = second.offeredDraw = 0;
10425
10426     /* If this is an ICS game, only ICS can really say it's done;
10427        if not, anyone can. */
10428     isIcsGame = (gameMode == IcsPlayingWhite ||
10429                  gameMode == IcsPlayingBlack ||
10430                  gameMode == IcsObserving    ||
10431                  gameMode == IcsExamining);
10432
10433     if (!isIcsGame || whosays == GE_ICS) {
10434         /* OK -- not an ICS game, or ICS said it was done */
10435         StopClocks();
10436         if (!isIcsGame && !appData.noChessProgram)
10437           SetUserThinkingEnables();
10438
10439         /* [HGM] if a machine claims the game end we verify this claim */
10440         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10441             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10442                 char claimer;
10443                 ChessMove trueResult = (ChessMove) -1;
10444
10445                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10446                                             first.twoMachinesColor[0] :
10447                                             second.twoMachinesColor[0] ;
10448
10449                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10450                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10451                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10452                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10453                 } else
10454                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10455                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10456                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10457                 } else
10458                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10459                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10460                 }
10461
10462                 // now verify win claims, but not in drop games, as we don't understand those yet
10463                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10464                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10465                     (result == WhiteWins && claimer == 'w' ||
10466                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10467                       if (appData.debugMode) {
10468                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10469                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10470                       }
10471                       if(result != trueResult) {
10472                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10473                               result = claimer == 'w' ? BlackWins : WhiteWins;
10474                               resultDetails = buf;
10475                       }
10476                 } else
10477                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10478                     && (forwardMostMove <= backwardMostMove ||
10479                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10480                         (claimer=='b')==(forwardMostMove&1))
10481                                                                                   ) {
10482                       /* [HGM] verify: draws that were not flagged are false claims */
10483                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10484                       result = claimer == 'w' ? BlackWins : WhiteWins;
10485                       resultDetails = buf;
10486                 }
10487                 /* (Claiming a loss is accepted no questions asked!) */
10488             }
10489             /* [HGM] bare: don't allow bare King to win */
10490             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10491                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10492                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10493                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10494                && result != GameIsDrawn)
10495             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10496                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10497                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10498                         if(p >= 0 && p <= (int)WhiteKing) k++;
10499                 }
10500                 if (appData.debugMode) {
10501                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10502                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10503                 }
10504                 if(k <= 1) {
10505                         result = GameIsDrawn;
10506                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10507                         resultDetails = buf;
10508                 }
10509             }
10510         }
10511
10512
10513         if(serverMoves != NULL && !loadFlag) { char c = '=';
10514             if(result==WhiteWins) c = '+';
10515             if(result==BlackWins) c = '-';
10516             if(resultDetails != NULL)
10517                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10518         }
10519         if (resultDetails != NULL) {
10520             gameInfo.result = result;
10521             gameInfo.resultDetails = StrSave(resultDetails);
10522
10523             /* display last move only if game was not loaded from file */
10524             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10525                 DisplayMove(currentMove - 1);
10526
10527             if (forwardMostMove != 0) {
10528                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10529                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10530                                                                 ) {
10531                     if (*appData.saveGameFile != NULLCHAR) {
10532                         SaveGameToFile(appData.saveGameFile, TRUE);
10533                     } else if (appData.autoSaveGames) {
10534                         AutoSaveGame();
10535                     }
10536                     if (*appData.savePositionFile != NULLCHAR) {
10537                         SavePositionToFile(appData.savePositionFile);
10538                     }
10539                 }
10540             }
10541
10542             /* Tell program how game ended in case it is learning */
10543             /* [HGM] Moved this to after saving the PGN, just in case */
10544             /* engine died and we got here through time loss. In that */
10545             /* case we will get a fatal error writing the pipe, which */
10546             /* would otherwise lose us the PGN.                       */
10547             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10548             /* output during GameEnds should never be fatal anymore   */
10549             if (gameMode == MachinePlaysWhite ||
10550                 gameMode == MachinePlaysBlack ||
10551                 gameMode == TwoMachinesPlay ||
10552                 gameMode == IcsPlayingWhite ||
10553                 gameMode == IcsPlayingBlack ||
10554                 gameMode == BeginningOfGame) {
10555                 char buf[MSG_SIZ];
10556                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10557                         resultDetails);
10558                 if (first.pr != NoProc) {
10559                     SendToProgram(buf, &first);
10560                 }
10561                 if (second.pr != NoProc &&
10562                     gameMode == TwoMachinesPlay) {
10563                     SendToProgram(buf, &second);
10564                 }
10565             }
10566         }
10567
10568         if (appData.icsActive) {
10569             if (appData.quietPlay &&
10570                 (gameMode == IcsPlayingWhite ||
10571                  gameMode == IcsPlayingBlack)) {
10572                 SendToICS(ics_prefix);
10573                 SendToICS("set shout 1\n");
10574             }
10575             nextGameMode = IcsIdle;
10576             ics_user_moved = FALSE;
10577             /* clean up premove.  It's ugly when the game has ended and the
10578              * premove highlights are still on the board.
10579              */
10580             if (gotPremove) {
10581               gotPremove = FALSE;
10582               ClearPremoveHighlights();
10583               DrawPosition(FALSE, boards[currentMove]);
10584             }
10585             if (whosays == GE_ICS) {
10586                 switch (result) {
10587                 case WhiteWins:
10588                     if (gameMode == IcsPlayingWhite)
10589                         PlayIcsWinSound();
10590                     else if(gameMode == IcsPlayingBlack)
10591                         PlayIcsLossSound();
10592                     break;
10593                 case BlackWins:
10594                     if (gameMode == IcsPlayingBlack)
10595                         PlayIcsWinSound();
10596                     else if(gameMode == IcsPlayingWhite)
10597                         PlayIcsLossSound();
10598                     break;
10599                 case GameIsDrawn:
10600                     PlayIcsDrawSound();
10601                     break;
10602                 default:
10603                     PlayIcsUnfinishedSound();
10604                 }
10605             }
10606         } else if (gameMode == EditGame ||
10607                    gameMode == PlayFromGameFile ||
10608                    gameMode == AnalyzeMode ||
10609                    gameMode == AnalyzeFile) {
10610             nextGameMode = gameMode;
10611         } else {
10612             nextGameMode = EndOfGame;
10613         }
10614         pausing = FALSE;
10615         ModeHighlight();
10616     } else {
10617         nextGameMode = gameMode;
10618     }
10619
10620     if (appData.noChessProgram) {
10621         gameMode = nextGameMode;
10622         ModeHighlight();
10623         endingGame = 0; /* [HGM] crash */
10624         return;
10625     }
10626
10627     if (first.reuse) {
10628         /* Put first chess program into idle state */
10629         if (first.pr != NoProc &&
10630             (gameMode == MachinePlaysWhite ||
10631              gameMode == MachinePlaysBlack ||
10632              gameMode == TwoMachinesPlay ||
10633              gameMode == IcsPlayingWhite ||
10634              gameMode == IcsPlayingBlack ||
10635              gameMode == BeginningOfGame)) {
10636             SendToProgram("force\n", &first);
10637             if (first.usePing) {
10638               char buf[MSG_SIZ];
10639               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10640               SendToProgram(buf, &first);
10641             }
10642         }
10643     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10644         /* Kill off first chess program */
10645         if (first.isr != NULL)
10646           RemoveInputSource(first.isr);
10647         first.isr = NULL;
10648
10649         if (first.pr != NoProc) {
10650             ExitAnalyzeMode();
10651             DoSleep( appData.delayBeforeQuit );
10652             SendToProgram("quit\n", &first);
10653             DoSleep( appData.delayAfterQuit );
10654             DestroyChildProcess(first.pr, first.useSigterm);
10655         }
10656         first.pr = NoProc;
10657     }
10658     if (second.reuse) {
10659         /* Put second chess program into idle state */
10660         if (second.pr != NoProc &&
10661             gameMode == TwoMachinesPlay) {
10662             SendToProgram("force\n", &second);
10663             if (second.usePing) {
10664               char buf[MSG_SIZ];
10665               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10666               SendToProgram(buf, &second);
10667             }
10668         }
10669     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10670         /* Kill off second chess program */
10671         if (second.isr != NULL)
10672           RemoveInputSource(second.isr);
10673         second.isr = NULL;
10674
10675         if (second.pr != NoProc) {
10676             DoSleep( appData.delayBeforeQuit );
10677             SendToProgram("quit\n", &second);
10678             DoSleep( appData.delayAfterQuit );
10679             DestroyChildProcess(second.pr, second.useSigterm);
10680         }
10681         second.pr = NoProc;
10682     }
10683
10684     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10685         char resChar = '=';
10686         switch (result) {
10687         case WhiteWins:
10688           resChar = '+';
10689           if (first.twoMachinesColor[0] == 'w') {
10690             first.matchWins++;
10691           } else {
10692             second.matchWins++;
10693           }
10694           break;
10695         case BlackWins:
10696           resChar = '-';
10697           if (first.twoMachinesColor[0] == 'b') {
10698             first.matchWins++;
10699           } else {
10700             second.matchWins++;
10701           }
10702           break;
10703         case GameUnfinished:
10704           resChar = ' ';
10705         default:
10706           break;
10707         }
10708
10709         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10710         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10711             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10712             ReserveGame(nextGame, resChar); // sets nextGame
10713             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10714             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10715         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10716
10717         if (nextGame <= appData.matchGames && !abortMatch) {
10718             gameMode = nextGameMode;
10719             matchGame = nextGame; // this will be overruled in tourney mode!
10720             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10721             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10722             endingGame = 0; /* [HGM] crash */
10723             return;
10724         } else {
10725             gameMode = nextGameMode;
10726             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10727                      first.tidy, second.tidy,
10728                      first.matchWins, second.matchWins,
10729                      appData.matchGames - (first.matchWins + second.matchWins));
10730             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10731             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10732             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10733             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10734                 first.twoMachinesColor = "black\n";
10735                 second.twoMachinesColor = "white\n";
10736             } else {
10737                 first.twoMachinesColor = "white\n";
10738                 second.twoMachinesColor = "black\n";
10739             }
10740         }
10741     }
10742     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10743         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10744       ExitAnalyzeMode();
10745     gameMode = nextGameMode;
10746     ModeHighlight();
10747     endingGame = 0;  /* [HGM] crash */
10748     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10749         if(matchMode == TRUE) { // match through command line: exit with or without popup
10750             if(ranking) {
10751                 ToNrEvent(forwardMostMove);
10752                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10753                 else ExitEvent(0);
10754             } else DisplayFatalError(buf, 0, 0);
10755         } else { // match through menu; just stop, with or without popup
10756             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10757             ModeHighlight();
10758             if(ranking){
10759                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10760             } else DisplayNote(buf);
10761       }
10762       if(ranking) free(ranking);
10763     }
10764 }
10765
10766 /* Assumes program was just initialized (initString sent).
10767    Leaves program in force mode. */
10768 void
10769 FeedMovesToProgram (ChessProgramState *cps, int upto)
10770 {
10771     int i;
10772
10773     if (appData.debugMode)
10774       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10775               startedFromSetupPosition ? "position and " : "",
10776               backwardMostMove, upto, cps->which);
10777     if(currentlyInitializedVariant != gameInfo.variant) {
10778       char buf[MSG_SIZ];
10779         // [HGM] variantswitch: make engine aware of new variant
10780         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10781                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10782         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10783         SendToProgram(buf, cps);
10784         currentlyInitializedVariant = gameInfo.variant;
10785     }
10786     SendToProgram("force\n", cps);
10787     if (startedFromSetupPosition) {
10788         SendBoard(cps, backwardMostMove);
10789     if (appData.debugMode) {
10790         fprintf(debugFP, "feedMoves\n");
10791     }
10792     }
10793     for (i = backwardMostMove; i < upto; i++) {
10794         SendMoveToProgram(i, cps);
10795     }
10796 }
10797
10798
10799 int
10800 ResurrectChessProgram ()
10801 {
10802      /* The chess program may have exited.
10803         If so, restart it and feed it all the moves made so far. */
10804     static int doInit = 0;
10805
10806     if (appData.noChessProgram) return 1;
10807
10808     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10809         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10810         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10811         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10812     } else {
10813         if (first.pr != NoProc) return 1;
10814         StartChessProgram(&first);
10815     }
10816     InitChessProgram(&first, FALSE);
10817     FeedMovesToProgram(&first, currentMove);
10818
10819     if (!first.sendTime) {
10820         /* can't tell gnuchess what its clock should read,
10821            so we bow to its notion. */
10822         ResetClocks();
10823         timeRemaining[0][currentMove] = whiteTimeRemaining;
10824         timeRemaining[1][currentMove] = blackTimeRemaining;
10825     }
10826
10827     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10828                 appData.icsEngineAnalyze) && first.analysisSupport) {
10829       SendToProgram("analyze\n", &first);
10830       first.analyzing = TRUE;
10831     }
10832     return 1;
10833 }
10834
10835 /*
10836  * Button procedures
10837  */
10838 void
10839 Reset (int redraw, int init)
10840 {
10841     int i;
10842
10843     if (appData.debugMode) {
10844         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10845                 redraw, init, gameMode);
10846     }
10847     CleanupTail(); // [HGM] vari: delete any stored variations
10848     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10849     pausing = pauseExamInvalid = FALSE;
10850     startedFromSetupPosition = blackPlaysFirst = FALSE;
10851     firstMove = TRUE;
10852     whiteFlag = blackFlag = FALSE;
10853     userOfferedDraw = FALSE;
10854     hintRequested = bookRequested = FALSE;
10855     first.maybeThinking = FALSE;
10856     second.maybeThinking = FALSE;
10857     first.bookSuspend = FALSE; // [HGM] book
10858     second.bookSuspend = FALSE;
10859     thinkOutput[0] = NULLCHAR;
10860     lastHint[0] = NULLCHAR;
10861     ClearGameInfo(&gameInfo);
10862     gameInfo.variant = StringToVariant(appData.variant);
10863     ics_user_moved = ics_clock_paused = FALSE;
10864     ics_getting_history = H_FALSE;
10865     ics_gamenum = -1;
10866     white_holding[0] = black_holding[0] = NULLCHAR;
10867     ClearProgramStats();
10868     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10869
10870     ResetFrontEnd();
10871     ClearHighlights();
10872     flipView = appData.flipView;
10873     ClearPremoveHighlights();
10874     gotPremove = FALSE;
10875     alarmSounded = FALSE;
10876
10877     GameEnds(EndOfFile, NULL, GE_PLAYER);
10878     if(appData.serverMovesName != NULL) {
10879         /* [HGM] prepare to make moves file for broadcasting */
10880         clock_t t = clock();
10881         if(serverMoves != NULL) fclose(serverMoves);
10882         serverMoves = fopen(appData.serverMovesName, "r");
10883         if(serverMoves != NULL) {
10884             fclose(serverMoves);
10885             /* delay 15 sec before overwriting, so all clients can see end */
10886             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10887         }
10888         serverMoves = fopen(appData.serverMovesName, "w");
10889     }
10890
10891     ExitAnalyzeMode();
10892     gameMode = BeginningOfGame;
10893     ModeHighlight();
10894     if(appData.icsActive) gameInfo.variant = VariantNormal;
10895     currentMove = forwardMostMove = backwardMostMove = 0;
10896     MarkTargetSquares(1);
10897     InitPosition(redraw);
10898     for (i = 0; i < MAX_MOVES; i++) {
10899         if (commentList[i] != NULL) {
10900             free(commentList[i]);
10901             commentList[i] = NULL;
10902         }
10903     }
10904     ResetClocks();
10905     timeRemaining[0][0] = whiteTimeRemaining;
10906     timeRemaining[1][0] = blackTimeRemaining;
10907
10908     if (first.pr == NoProc) {
10909         StartChessProgram(&first);
10910     }
10911     if (init) {
10912             InitChessProgram(&first, startedFromSetupPosition);
10913     }
10914     DisplayTitle("");
10915     DisplayMessage("", "");
10916     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10917     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10918     ClearMap();        // [HGM] exclude: invalidate map
10919 }
10920
10921 void
10922 AutoPlayGameLoop ()
10923 {
10924     for (;;) {
10925         if (!AutoPlayOneMove())
10926           return;
10927         if (matchMode || appData.timeDelay == 0)
10928           continue;
10929         if (appData.timeDelay < 0)
10930           return;
10931         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10932         break;
10933     }
10934 }
10935
10936
10937 int
10938 AutoPlayOneMove ()
10939 {
10940     int fromX, fromY, toX, toY;
10941
10942     if (appData.debugMode) {
10943       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10944     }
10945
10946     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10947       return FALSE;
10948
10949     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10950       pvInfoList[currentMove].depth = programStats.depth;
10951       pvInfoList[currentMove].score = programStats.score;
10952       pvInfoList[currentMove].time  = 0;
10953       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10954     }
10955
10956     if (currentMove >= forwardMostMove) {
10957       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10958 //      gameMode = EndOfGame;
10959 //      ModeHighlight();
10960
10961       /* [AS] Clear current move marker at the end of a game */
10962       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10963
10964       return FALSE;
10965     }
10966
10967     toX = moveList[currentMove][2] - AAA;
10968     toY = moveList[currentMove][3] - ONE;
10969
10970     if (moveList[currentMove][1] == '@') {
10971         if (appData.highlightLastMove) {
10972             SetHighlights(-1, -1, toX, toY);
10973         }
10974     } else {
10975         fromX = moveList[currentMove][0] - AAA;
10976         fromY = moveList[currentMove][1] - ONE;
10977
10978         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10979
10980         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10981
10982         if (appData.highlightLastMove) {
10983             SetHighlights(fromX, fromY, toX, toY);
10984         }
10985     }
10986     DisplayMove(currentMove);
10987     SendMoveToProgram(currentMove++, &first);
10988     DisplayBothClocks();
10989     DrawPosition(FALSE, boards[currentMove]);
10990     // [HGM] PV info: always display, routine tests if empty
10991     DisplayComment(currentMove - 1, commentList[currentMove]);
10992     return TRUE;
10993 }
10994
10995
10996 int
10997 LoadGameOneMove (ChessMove readAhead)
10998 {
10999     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11000     char promoChar = NULLCHAR;
11001     ChessMove moveType;
11002     char move[MSG_SIZ];
11003     char *p, *q;
11004
11005     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11006         gameMode != AnalyzeMode && gameMode != Training) {
11007         gameFileFP = NULL;
11008         return FALSE;
11009     }
11010
11011     yyboardindex = forwardMostMove;
11012     if (readAhead != EndOfFile) {
11013       moveType = readAhead;
11014     } else {
11015       if (gameFileFP == NULL)
11016           return FALSE;
11017       moveType = (ChessMove) Myylex();
11018     }
11019
11020     done = FALSE;
11021     switch (moveType) {
11022       case Comment:
11023         if (appData.debugMode)
11024           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11025         p = yy_text;
11026
11027         /* append the comment but don't display it */
11028         AppendComment(currentMove, p, FALSE);
11029         return TRUE;
11030
11031       case WhiteCapturesEnPassant:
11032       case BlackCapturesEnPassant:
11033       case WhitePromotion:
11034       case BlackPromotion:
11035       case WhiteNonPromotion:
11036       case BlackNonPromotion:
11037       case NormalMove:
11038       case WhiteKingSideCastle:
11039       case WhiteQueenSideCastle:
11040       case BlackKingSideCastle:
11041       case BlackQueenSideCastle:
11042       case WhiteKingSideCastleWild:
11043       case WhiteQueenSideCastleWild:
11044       case BlackKingSideCastleWild:
11045       case BlackQueenSideCastleWild:
11046       /* PUSH Fabien */
11047       case WhiteHSideCastleFR:
11048       case WhiteASideCastleFR:
11049       case BlackHSideCastleFR:
11050       case BlackASideCastleFR:
11051       /* POP Fabien */
11052         if (appData.debugMode)
11053           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11054         fromX = currentMoveString[0] - AAA;
11055         fromY = currentMoveString[1] - ONE;
11056         toX = currentMoveString[2] - AAA;
11057         toY = currentMoveString[3] - ONE;
11058         promoChar = currentMoveString[4];
11059         break;
11060
11061       case WhiteDrop:
11062       case BlackDrop:
11063         if (appData.debugMode)
11064           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11065         fromX = moveType == WhiteDrop ?
11066           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11067         (int) CharToPiece(ToLower(currentMoveString[0]));
11068         fromY = DROP_RANK;
11069         toX = currentMoveString[2] - AAA;
11070         toY = currentMoveString[3] - ONE;
11071         break;
11072
11073       case WhiteWins:
11074       case BlackWins:
11075       case GameIsDrawn:
11076       case GameUnfinished:
11077         if (appData.debugMode)
11078           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11079         p = strchr(yy_text, '{');
11080         if (p == NULL) p = strchr(yy_text, '(');
11081         if (p == NULL) {
11082             p = yy_text;
11083             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11084         } else {
11085             q = strchr(p, *p == '{' ? '}' : ')');
11086             if (q != NULL) *q = NULLCHAR;
11087             p++;
11088         }
11089         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11090         GameEnds(moveType, p, GE_FILE);
11091         done = TRUE;
11092         if (cmailMsgLoaded) {
11093             ClearHighlights();
11094             flipView = WhiteOnMove(currentMove);
11095             if (moveType == GameUnfinished) flipView = !flipView;
11096             if (appData.debugMode)
11097               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11098         }
11099         break;
11100
11101       case EndOfFile:
11102         if (appData.debugMode)
11103           fprintf(debugFP, "Parser hit end of file\n");
11104         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11105           case MT_NONE:
11106           case MT_CHECK:
11107             break;
11108           case MT_CHECKMATE:
11109           case MT_STAINMATE:
11110             if (WhiteOnMove(currentMove)) {
11111                 GameEnds(BlackWins, "Black mates", GE_FILE);
11112             } else {
11113                 GameEnds(WhiteWins, "White mates", GE_FILE);
11114             }
11115             break;
11116           case MT_STALEMATE:
11117             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11118             break;
11119         }
11120         done = TRUE;
11121         break;
11122
11123       case MoveNumberOne:
11124         if (lastLoadGameStart == GNUChessGame) {
11125             /* GNUChessGames have numbers, but they aren't move numbers */
11126             if (appData.debugMode)
11127               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11128                       yy_text, (int) moveType);
11129             return LoadGameOneMove(EndOfFile); /* tail recursion */
11130         }
11131         /* else fall thru */
11132
11133       case XBoardGame:
11134       case GNUChessGame:
11135       case PGNTag:
11136         /* Reached start of next game in file */
11137         if (appData.debugMode)
11138           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11139         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11140           case MT_NONE:
11141           case MT_CHECK:
11142             break;
11143           case MT_CHECKMATE:
11144           case MT_STAINMATE:
11145             if (WhiteOnMove(currentMove)) {
11146                 GameEnds(BlackWins, "Black mates", GE_FILE);
11147             } else {
11148                 GameEnds(WhiteWins, "White mates", GE_FILE);
11149             }
11150             break;
11151           case MT_STALEMATE:
11152             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11153             break;
11154         }
11155         done = TRUE;
11156         break;
11157
11158       case PositionDiagram:     /* should not happen; ignore */
11159       case ElapsedTime:         /* ignore */
11160       case NAG:                 /* ignore */
11161         if (appData.debugMode)
11162           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11163                   yy_text, (int) moveType);
11164         return LoadGameOneMove(EndOfFile); /* tail recursion */
11165
11166       case IllegalMove:
11167         if (appData.testLegality) {
11168             if (appData.debugMode)
11169               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11170             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11171                     (forwardMostMove / 2) + 1,
11172                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11173             DisplayError(move, 0);
11174             done = TRUE;
11175         } else {
11176             if (appData.debugMode)
11177               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11178                       yy_text, currentMoveString);
11179             fromX = currentMoveString[0] - AAA;
11180             fromY = currentMoveString[1] - ONE;
11181             toX = currentMoveString[2] - AAA;
11182             toY = currentMoveString[3] - ONE;
11183             promoChar = currentMoveString[4];
11184         }
11185         break;
11186
11187       case AmbiguousMove:
11188         if (appData.debugMode)
11189           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11190         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11191                 (forwardMostMove / 2) + 1,
11192                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11193         DisplayError(move, 0);
11194         done = TRUE;
11195         break;
11196
11197       default:
11198       case ImpossibleMove:
11199         if (appData.debugMode)
11200           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11201         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11202                 (forwardMostMove / 2) + 1,
11203                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11204         DisplayError(move, 0);
11205         done = TRUE;
11206         break;
11207     }
11208
11209     if (done) {
11210         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11211             DrawPosition(FALSE, boards[currentMove]);
11212             DisplayBothClocks();
11213             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11214               DisplayComment(currentMove - 1, commentList[currentMove]);
11215         }
11216         (void) StopLoadGameTimer();
11217         gameFileFP = NULL;
11218         cmailOldMove = forwardMostMove;
11219         return FALSE;
11220     } else {
11221         /* currentMoveString is set as a side-effect of yylex */
11222
11223         thinkOutput[0] = NULLCHAR;
11224         MakeMove(fromX, fromY, toX, toY, promoChar);
11225         currentMove = forwardMostMove;
11226         return TRUE;
11227     }
11228 }
11229
11230 /* Load the nth game from the given file */
11231 int
11232 LoadGameFromFile (char *filename, int n, char *title, int useList)
11233 {
11234     FILE *f;
11235     char buf[MSG_SIZ];
11236
11237     if (strcmp(filename, "-") == 0) {
11238         f = stdin;
11239         title = "stdin";
11240     } else {
11241         f = fopen(filename, "rb");
11242         if (f == NULL) {
11243           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11244             DisplayError(buf, errno);
11245             return FALSE;
11246         }
11247     }
11248     if (fseek(f, 0, 0) == -1) {
11249         /* f is not seekable; probably a pipe */
11250         useList = FALSE;
11251     }
11252     if (useList && n == 0) {
11253         int error = GameListBuild(f);
11254         if (error) {
11255             DisplayError(_("Cannot build game list"), error);
11256         } else if (!ListEmpty(&gameList) &&
11257                    ((ListGame *) gameList.tailPred)->number > 1) {
11258             GameListPopUp(f, title);
11259             return TRUE;
11260         }
11261         GameListDestroy();
11262         n = 1;
11263     }
11264     if (n == 0) n = 1;
11265     return LoadGame(f, n, title, FALSE);
11266 }
11267
11268
11269 void
11270 MakeRegisteredMove ()
11271 {
11272     int fromX, fromY, toX, toY;
11273     char promoChar;
11274     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11275         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11276           case CMAIL_MOVE:
11277           case CMAIL_DRAW:
11278             if (appData.debugMode)
11279               fprintf(debugFP, "Restoring %s for game %d\n",
11280                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11281
11282             thinkOutput[0] = NULLCHAR;
11283             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11284             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11285             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11286             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11287             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11288             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11289             MakeMove(fromX, fromY, toX, toY, promoChar);
11290             ShowMove(fromX, fromY, toX, toY);
11291
11292             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11293               case MT_NONE:
11294               case MT_CHECK:
11295                 break;
11296
11297               case MT_CHECKMATE:
11298               case MT_STAINMATE:
11299                 if (WhiteOnMove(currentMove)) {
11300                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11301                 } else {
11302                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11303                 }
11304                 break;
11305
11306               case MT_STALEMATE:
11307                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11308                 break;
11309             }
11310
11311             break;
11312
11313           case CMAIL_RESIGN:
11314             if (WhiteOnMove(currentMove)) {
11315                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11316             } else {
11317                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11318             }
11319             break;
11320
11321           case CMAIL_ACCEPT:
11322             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11323             break;
11324
11325           default:
11326             break;
11327         }
11328     }
11329
11330     return;
11331 }
11332
11333 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11334 int
11335 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11336 {
11337     int retVal;
11338
11339     if (gameNumber > nCmailGames) {
11340         DisplayError(_("No more games in this message"), 0);
11341         return FALSE;
11342     }
11343     if (f == lastLoadGameFP) {
11344         int offset = gameNumber - lastLoadGameNumber;
11345         if (offset == 0) {
11346             cmailMsg[0] = NULLCHAR;
11347             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11348                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11349                 nCmailMovesRegistered--;
11350             }
11351             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11352             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11353                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11354             }
11355         } else {
11356             if (! RegisterMove()) return FALSE;
11357         }
11358     }
11359
11360     retVal = LoadGame(f, gameNumber, title, useList);
11361
11362     /* Make move registered during previous look at this game, if any */
11363     MakeRegisteredMove();
11364
11365     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11366         commentList[currentMove]
11367           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11368         DisplayComment(currentMove - 1, commentList[currentMove]);
11369     }
11370
11371     return retVal;
11372 }
11373
11374 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11375 int
11376 ReloadGame (int offset)
11377 {
11378     int gameNumber = lastLoadGameNumber + offset;
11379     if (lastLoadGameFP == NULL) {
11380         DisplayError(_("No game has been loaded yet"), 0);
11381         return FALSE;
11382     }
11383     if (gameNumber <= 0) {
11384         DisplayError(_("Can't back up any further"), 0);
11385         return FALSE;
11386     }
11387     if (cmailMsgLoaded) {
11388         return CmailLoadGame(lastLoadGameFP, gameNumber,
11389                              lastLoadGameTitle, lastLoadGameUseList);
11390     } else {
11391         return LoadGame(lastLoadGameFP, gameNumber,
11392                         lastLoadGameTitle, lastLoadGameUseList);
11393     }
11394 }
11395
11396 int keys[EmptySquare+1];
11397
11398 int
11399 PositionMatches (Board b1, Board b2)
11400 {
11401     int r, f, sum=0;
11402     switch(appData.searchMode) {
11403         case 1: return CompareWithRights(b1, b2);
11404         case 2:
11405             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11406                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11407             }
11408             return TRUE;
11409         case 3:
11410             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11411               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11412                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11413             }
11414             return sum==0;
11415         case 4:
11416             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11417                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11418             }
11419             return sum==0;
11420     }
11421     return TRUE;
11422 }
11423
11424 #define Q_PROMO  4
11425 #define Q_EP     3
11426 #define Q_BCASTL 2
11427 #define Q_WCASTL 1
11428
11429 int pieceList[256], quickBoard[256];
11430 ChessSquare pieceType[256] = { EmptySquare };
11431 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11432 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11433 int soughtTotal, turn;
11434 Boolean epOK, flipSearch;
11435
11436 typedef struct {
11437     unsigned char piece, to;
11438 } Move;
11439
11440 #define DSIZE (250000)
11441
11442 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11443 Move *moveDatabase = initialSpace;
11444 unsigned int movePtr, dataSize = DSIZE;
11445
11446 int
11447 MakePieceList (Board board, int *counts)
11448 {
11449     int r, f, n=Q_PROMO, total=0;
11450     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11451     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11452         int sq = f + (r<<4);
11453         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11454             quickBoard[sq] = ++n;
11455             pieceList[n] = sq;
11456             pieceType[n] = board[r][f];
11457             counts[board[r][f]]++;
11458             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11459             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11460             total++;
11461         }
11462     }
11463     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11464     return total;
11465 }
11466
11467 void
11468 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11469 {
11470     int sq = fromX + (fromY<<4);
11471     int piece = quickBoard[sq];
11472     quickBoard[sq] = 0;
11473     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11474     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11475         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11476         moveDatabase[movePtr++].piece = Q_WCASTL;
11477         quickBoard[sq] = piece;
11478         piece = quickBoard[from]; quickBoard[from] = 0;
11479         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11480     } else
11481     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11482         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11483         moveDatabase[movePtr++].piece = Q_BCASTL;
11484         quickBoard[sq] = piece;
11485         piece = quickBoard[from]; quickBoard[from] = 0;
11486         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11487     } else
11488     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11489         quickBoard[(fromY<<4)+toX] = 0;
11490         moveDatabase[movePtr].piece = Q_EP;
11491         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11492         moveDatabase[movePtr].to = sq;
11493     } else
11494     if(promoPiece != pieceType[piece]) {
11495         moveDatabase[movePtr++].piece = Q_PROMO;
11496         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11497     }
11498     moveDatabase[movePtr].piece = piece;
11499     quickBoard[sq] = piece;
11500     movePtr++;
11501 }
11502
11503 int
11504 PackGame (Board board)
11505 {
11506     Move *newSpace = NULL;
11507     moveDatabase[movePtr].piece = 0; // terminate previous game
11508     if(movePtr > dataSize) {
11509         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11510         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11511         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11512         if(newSpace) {
11513             int i;
11514             Move *p = moveDatabase, *q = newSpace;
11515             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11516             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11517             moveDatabase = newSpace;
11518         } else { // calloc failed, we must be out of memory. Too bad...
11519             dataSize = 0; // prevent calloc events for all subsequent games
11520             return 0;     // and signal this one isn't cached
11521         }
11522     }
11523     movePtr++;
11524     MakePieceList(board, counts);
11525     return movePtr;
11526 }
11527
11528 int
11529 QuickCompare (Board board, int *minCounts, int *maxCounts)
11530 {   // compare according to search mode
11531     int r, f;
11532     switch(appData.searchMode)
11533     {
11534       case 1: // exact position match
11535         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11536         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11537             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11538         }
11539         break;
11540       case 2: // can have extra material on empty squares
11541         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11542             if(board[r][f] == EmptySquare) continue;
11543             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11544         }
11545         break;
11546       case 3: // material with exact Pawn structure
11547         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11548             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11549             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11550         } // fall through to material comparison
11551       case 4: // exact material
11552         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11553         break;
11554       case 6: // material range with given imbalance
11555         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11556         // fall through to range comparison
11557       case 5: // material range
11558         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11559     }
11560     return TRUE;
11561 }
11562
11563 int
11564 QuickScan (Board board, Move *move)
11565 {   // reconstruct game,and compare all positions in it
11566     int cnt=0, stretch=0, total = MakePieceList(board, counts), delayedKing = -1;
11567     do {
11568         int piece = move->piece;
11569         int to = move->to, from = pieceList[piece];
11570         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11571           if(!piece) return -1;
11572           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11573             piece = (++move)->piece;
11574             from = pieceList[piece];
11575             counts[pieceType[piece]]--;
11576             pieceType[piece] = (ChessSquare) move->to;
11577             counts[move->to]++;
11578           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11579             counts[pieceType[quickBoard[to]]]--;
11580             quickBoard[to] = 0; total--;
11581             move++;
11582             continue;
11583           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11584             int rook;
11585             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11586             from  = pieceList[piece]; // so this must be King
11587             quickBoard[from] = 0;
11588             pieceList[piece] = to;
11589             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11590             quickBoard[from] = 0; // rook
11591             quickBoard[to] = piece;
11592             to = move->to; piece = move->piece;
11593             goto aftercastle;
11594           }
11595         }
11596         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11597         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11598         quickBoard[from] = 0;
11599       aftercastle:
11600         quickBoard[to] = piece;
11601         pieceList[piece] = to;
11602         cnt++; turn ^= 3;
11603         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11604            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11605            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11606                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11607           ) {
11608             static int lastCounts[EmptySquare+1];
11609             int i;
11610             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11611             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11612         } else stretch = 0;
11613         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11614         move++; delayedKing = -1;
11615     } while(1);
11616 }
11617
11618 void
11619 InitSearch ()
11620 {
11621     int r, f;
11622     flipSearch = FALSE;
11623     CopyBoard(soughtBoard, boards[currentMove]);
11624     soughtTotal = MakePieceList(soughtBoard, maxSought);
11625     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11626     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11627     CopyBoard(reverseBoard, boards[currentMove]);
11628     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11629         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11630         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11631         reverseBoard[r][f] = piece;
11632     }
11633     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11634     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11635     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11636                  || (boards[currentMove][CASTLING][2] == NoRights || 
11637                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11638                  && (boards[currentMove][CASTLING][5] == NoRights || 
11639                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11640       ) {
11641         flipSearch = TRUE;
11642         CopyBoard(flipBoard, soughtBoard);
11643         CopyBoard(rotateBoard, reverseBoard);
11644         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11645             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11646             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11647         }
11648     }
11649     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11650     if(appData.searchMode >= 5) {
11651         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11652         MakePieceList(soughtBoard, minSought);
11653         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11654     }
11655     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11656         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11657 }
11658
11659 GameInfo dummyInfo;
11660
11661 int
11662 GameContainsPosition (FILE *f, ListGame *lg)
11663 {
11664     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11665     int fromX, fromY, toX, toY;
11666     char promoChar;
11667     static int initDone=FALSE;
11668
11669     // weed out games based on numerical tag comparison
11670     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11671     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11672     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11673     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11674     if(!initDone) {
11675         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11676         initDone = TRUE;
11677     }
11678     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11679     else CopyBoard(boards[scratch], initialPosition); // default start position
11680     if(lg->moves) {
11681         turn = btm + 1;
11682         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11683         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11684     }
11685     if(btm) plyNr++;
11686     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11687     fseek(f, lg->offset, 0);
11688     yynewfile(f);
11689     while(1) {
11690         yyboardindex = scratch;
11691         quickFlag = plyNr+1;
11692         next = Myylex();
11693         quickFlag = 0;
11694         switch(next) {
11695             case PGNTag:
11696                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11697             default:
11698                 continue;
11699
11700             case XBoardGame:
11701             case GNUChessGame:
11702                 if(plyNr) return -1; // after we have seen moves, this is for new game
11703               continue;
11704
11705             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11706             case ImpossibleMove:
11707             case WhiteWins: // game ends here with these four
11708             case BlackWins:
11709             case GameIsDrawn:
11710             case GameUnfinished:
11711                 return -1;
11712
11713             case IllegalMove:
11714                 if(appData.testLegality) return -1;
11715             case WhiteCapturesEnPassant:
11716             case BlackCapturesEnPassant:
11717             case WhitePromotion:
11718             case BlackPromotion:
11719             case WhiteNonPromotion:
11720             case BlackNonPromotion:
11721             case NormalMove:
11722             case WhiteKingSideCastle:
11723             case WhiteQueenSideCastle:
11724             case BlackKingSideCastle:
11725             case BlackQueenSideCastle:
11726             case WhiteKingSideCastleWild:
11727             case WhiteQueenSideCastleWild:
11728             case BlackKingSideCastleWild:
11729             case BlackQueenSideCastleWild:
11730             case WhiteHSideCastleFR:
11731             case WhiteASideCastleFR:
11732             case BlackHSideCastleFR:
11733             case BlackASideCastleFR:
11734                 fromX = currentMoveString[0] - AAA;
11735                 fromY = currentMoveString[1] - ONE;
11736                 toX = currentMoveString[2] - AAA;
11737                 toY = currentMoveString[3] - ONE;
11738                 promoChar = currentMoveString[4];
11739                 break;
11740             case WhiteDrop:
11741             case BlackDrop:
11742                 fromX = next == WhiteDrop ?
11743                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11744                   (int) CharToPiece(ToLower(currentMoveString[0]));
11745                 fromY = DROP_RANK;
11746                 toX = currentMoveString[2] - AAA;
11747                 toY = currentMoveString[3] - ONE;
11748                 promoChar = 0;
11749                 break;
11750         }
11751         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11752         plyNr++;
11753         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11754         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11755         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11756         if(appData.findMirror) {
11757             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11758             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11759         }
11760     }
11761 }
11762
11763 /* Load the nth game from open file f */
11764 int
11765 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11766 {
11767     ChessMove cm;
11768     char buf[MSG_SIZ];
11769     int gn = gameNumber;
11770     ListGame *lg = NULL;
11771     int numPGNTags = 0;
11772     int err, pos = -1;
11773     GameMode oldGameMode;
11774     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11775
11776     if (appData.debugMode)
11777         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11778
11779     if (gameMode == Training )
11780         SetTrainingModeOff();
11781
11782     oldGameMode = gameMode;
11783     if (gameMode != BeginningOfGame) {
11784       Reset(FALSE, TRUE);
11785     }
11786
11787     gameFileFP = f;
11788     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11789         fclose(lastLoadGameFP);
11790     }
11791
11792     if (useList) {
11793         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11794
11795         if (lg) {
11796             fseek(f, lg->offset, 0);
11797             GameListHighlight(gameNumber);
11798             pos = lg->position;
11799             gn = 1;
11800         }
11801         else {
11802             DisplayError(_("Game number out of range"), 0);
11803             return FALSE;
11804         }
11805     } else {
11806         GameListDestroy();
11807         if (fseek(f, 0, 0) == -1) {
11808             if (f == lastLoadGameFP ?
11809                 gameNumber == lastLoadGameNumber + 1 :
11810                 gameNumber == 1) {
11811                 gn = 1;
11812             } else {
11813                 DisplayError(_("Can't seek on game file"), 0);
11814                 return FALSE;
11815             }
11816         }
11817     }
11818     lastLoadGameFP = f;
11819     lastLoadGameNumber = gameNumber;
11820     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11821     lastLoadGameUseList = useList;
11822
11823     yynewfile(f);
11824
11825     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11826       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11827                 lg->gameInfo.black);
11828             DisplayTitle(buf);
11829     } else if (*title != NULLCHAR) {
11830         if (gameNumber > 1) {
11831           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11832             DisplayTitle(buf);
11833         } else {
11834             DisplayTitle(title);
11835         }
11836     }
11837
11838     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11839         gameMode = PlayFromGameFile;
11840         ModeHighlight();
11841     }
11842
11843     currentMove = forwardMostMove = backwardMostMove = 0;
11844     CopyBoard(boards[0], initialPosition);
11845     StopClocks();
11846
11847     /*
11848      * Skip the first gn-1 games in the file.
11849      * Also skip over anything that precedes an identifiable
11850      * start of game marker, to avoid being confused by
11851      * garbage at the start of the file.  Currently
11852      * recognized start of game markers are the move number "1",
11853      * the pattern "gnuchess .* game", the pattern
11854      * "^[#;%] [^ ]* game file", and a PGN tag block.
11855      * A game that starts with one of the latter two patterns
11856      * will also have a move number 1, possibly
11857      * following a position diagram.
11858      * 5-4-02: Let's try being more lenient and allowing a game to
11859      * start with an unnumbered move.  Does that break anything?
11860      */
11861     cm = lastLoadGameStart = EndOfFile;
11862     while (gn > 0) {
11863         yyboardindex = forwardMostMove;
11864         cm = (ChessMove) Myylex();
11865         switch (cm) {
11866           case EndOfFile:
11867             if (cmailMsgLoaded) {
11868                 nCmailGames = CMAIL_MAX_GAMES - gn;
11869             } else {
11870                 Reset(TRUE, TRUE);
11871                 DisplayError(_("Game not found in file"), 0);
11872             }
11873             return FALSE;
11874
11875           case GNUChessGame:
11876           case XBoardGame:
11877             gn--;
11878             lastLoadGameStart = cm;
11879             break;
11880
11881           case MoveNumberOne:
11882             switch (lastLoadGameStart) {
11883               case GNUChessGame:
11884               case XBoardGame:
11885               case PGNTag:
11886                 break;
11887               case MoveNumberOne:
11888               case EndOfFile:
11889                 gn--;           /* count this game */
11890                 lastLoadGameStart = cm;
11891                 break;
11892               default:
11893                 /* impossible */
11894                 break;
11895             }
11896             break;
11897
11898           case PGNTag:
11899             switch (lastLoadGameStart) {
11900               case GNUChessGame:
11901               case PGNTag:
11902               case MoveNumberOne:
11903               case EndOfFile:
11904                 gn--;           /* count this game */
11905                 lastLoadGameStart = cm;
11906                 break;
11907               case XBoardGame:
11908                 lastLoadGameStart = cm; /* game counted already */
11909                 break;
11910               default:
11911                 /* impossible */
11912                 break;
11913             }
11914             if (gn > 0) {
11915                 do {
11916                     yyboardindex = forwardMostMove;
11917                     cm = (ChessMove) Myylex();
11918                 } while (cm == PGNTag || cm == Comment);
11919             }
11920             break;
11921
11922           case WhiteWins:
11923           case BlackWins:
11924           case GameIsDrawn:
11925             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11926                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11927                     != CMAIL_OLD_RESULT) {
11928                     nCmailResults ++ ;
11929                     cmailResult[  CMAIL_MAX_GAMES
11930                                 - gn - 1] = CMAIL_OLD_RESULT;
11931                 }
11932             }
11933             break;
11934
11935           case NormalMove:
11936             /* Only a NormalMove can be at the start of a game
11937              * without a position diagram. */
11938             if (lastLoadGameStart == EndOfFile ) {
11939               gn--;
11940               lastLoadGameStart = MoveNumberOne;
11941             }
11942             break;
11943
11944           default:
11945             break;
11946         }
11947     }
11948
11949     if (appData.debugMode)
11950       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11951
11952     if (cm == XBoardGame) {
11953         /* Skip any header junk before position diagram and/or move 1 */
11954         for (;;) {
11955             yyboardindex = forwardMostMove;
11956             cm = (ChessMove) Myylex();
11957
11958             if (cm == EndOfFile ||
11959                 cm == GNUChessGame || cm == XBoardGame) {
11960                 /* Empty game; pretend end-of-file and handle later */
11961                 cm = EndOfFile;
11962                 break;
11963             }
11964
11965             if (cm == MoveNumberOne || cm == PositionDiagram ||
11966                 cm == PGNTag || cm == Comment)
11967               break;
11968         }
11969     } else if (cm == GNUChessGame) {
11970         if (gameInfo.event != NULL) {
11971             free(gameInfo.event);
11972         }
11973         gameInfo.event = StrSave(yy_text);
11974     }
11975
11976     startedFromSetupPosition = FALSE;
11977     while (cm == PGNTag) {
11978         if (appData.debugMode)
11979           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11980         err = ParsePGNTag(yy_text, &gameInfo);
11981         if (!err) numPGNTags++;
11982
11983         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11984         if(gameInfo.variant != oldVariant) {
11985             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11986             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11987             InitPosition(TRUE);
11988             oldVariant = gameInfo.variant;
11989             if (appData.debugMode)
11990               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11991         }
11992
11993
11994         if (gameInfo.fen != NULL) {
11995           Board initial_position;
11996           startedFromSetupPosition = TRUE;
11997           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11998             Reset(TRUE, TRUE);
11999             DisplayError(_("Bad FEN position in file"), 0);
12000             return FALSE;
12001           }
12002           CopyBoard(boards[0], initial_position);
12003           if (blackPlaysFirst) {
12004             currentMove = forwardMostMove = backwardMostMove = 1;
12005             CopyBoard(boards[1], initial_position);
12006             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12007             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12008             timeRemaining[0][1] = whiteTimeRemaining;
12009             timeRemaining[1][1] = blackTimeRemaining;
12010             if (commentList[0] != NULL) {
12011               commentList[1] = commentList[0];
12012               commentList[0] = NULL;
12013             }
12014           } else {
12015             currentMove = forwardMostMove = backwardMostMove = 0;
12016           }
12017           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12018           {   int i;
12019               initialRulePlies = FENrulePlies;
12020               for( i=0; i< nrCastlingRights; i++ )
12021                   initialRights[i] = initial_position[CASTLING][i];
12022           }
12023           yyboardindex = forwardMostMove;
12024           free(gameInfo.fen);
12025           gameInfo.fen = NULL;
12026         }
12027
12028         yyboardindex = forwardMostMove;
12029         cm = (ChessMove) Myylex();
12030
12031         /* Handle comments interspersed among the tags */
12032         while (cm == Comment) {
12033             char *p;
12034             if (appData.debugMode)
12035               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12036             p = yy_text;
12037             AppendComment(currentMove, p, FALSE);
12038             yyboardindex = forwardMostMove;
12039             cm = (ChessMove) Myylex();
12040         }
12041     }
12042
12043     /* don't rely on existence of Event tag since if game was
12044      * pasted from clipboard the Event tag may not exist
12045      */
12046     if (numPGNTags > 0){
12047         char *tags;
12048         if (gameInfo.variant == VariantNormal) {
12049           VariantClass v = StringToVariant(gameInfo.event);
12050           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12051           if(v < VariantShogi) gameInfo.variant = v;
12052         }
12053         if (!matchMode) {
12054           if( appData.autoDisplayTags ) {
12055             tags = PGNTags(&gameInfo);
12056             TagsPopUp(tags, CmailMsg());
12057             free(tags);
12058           }
12059         }
12060     } else {
12061         /* Make something up, but don't display it now */
12062         SetGameInfo();
12063         TagsPopDown();
12064     }
12065
12066     if (cm == PositionDiagram) {
12067         int i, j;
12068         char *p;
12069         Board initial_position;
12070
12071         if (appData.debugMode)
12072           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12073
12074         if (!startedFromSetupPosition) {
12075             p = yy_text;
12076             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12077               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12078                 switch (*p) {
12079                   case '{':
12080                   case '[':
12081                   case '-':
12082                   case ' ':
12083                   case '\t':
12084                   case '\n':
12085                   case '\r':
12086                     break;
12087                   default:
12088                     initial_position[i][j++] = CharToPiece(*p);
12089                     break;
12090                 }
12091             while (*p == ' ' || *p == '\t' ||
12092                    *p == '\n' || *p == '\r') p++;
12093
12094             if (strncmp(p, "black", strlen("black"))==0)
12095               blackPlaysFirst = TRUE;
12096             else
12097               blackPlaysFirst = FALSE;
12098             startedFromSetupPosition = TRUE;
12099
12100             CopyBoard(boards[0], initial_position);
12101             if (blackPlaysFirst) {
12102                 currentMove = forwardMostMove = backwardMostMove = 1;
12103                 CopyBoard(boards[1], initial_position);
12104                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12105                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12106                 timeRemaining[0][1] = whiteTimeRemaining;
12107                 timeRemaining[1][1] = blackTimeRemaining;
12108                 if (commentList[0] != NULL) {
12109                     commentList[1] = commentList[0];
12110                     commentList[0] = NULL;
12111                 }
12112             } else {
12113                 currentMove = forwardMostMove = backwardMostMove = 0;
12114             }
12115         }
12116         yyboardindex = forwardMostMove;
12117         cm = (ChessMove) Myylex();
12118     }
12119
12120     if (first.pr == NoProc) {
12121         StartChessProgram(&first);
12122     }
12123     InitChessProgram(&first, FALSE);
12124     SendToProgram("force\n", &first);
12125     if (startedFromSetupPosition) {
12126         SendBoard(&first, forwardMostMove);
12127     if (appData.debugMode) {
12128         fprintf(debugFP, "Load Game\n");
12129     }
12130         DisplayBothClocks();
12131     }
12132
12133     /* [HGM] server: flag to write setup moves in broadcast file as one */
12134     loadFlag = appData.suppressLoadMoves;
12135
12136     while (cm == Comment) {
12137         char *p;
12138         if (appData.debugMode)
12139           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12140         p = yy_text;
12141         AppendComment(currentMove, p, FALSE);
12142         yyboardindex = forwardMostMove;
12143         cm = (ChessMove) Myylex();
12144     }
12145
12146     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12147         cm == WhiteWins || cm == BlackWins ||
12148         cm == GameIsDrawn || cm == GameUnfinished) {
12149         DisplayMessage("", _("No moves in game"));
12150         if (cmailMsgLoaded) {
12151             if (appData.debugMode)
12152               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12153             ClearHighlights();
12154             flipView = FALSE;
12155         }
12156         DrawPosition(FALSE, boards[currentMove]);
12157         DisplayBothClocks();
12158         gameMode = EditGame;
12159         ModeHighlight();
12160         gameFileFP = NULL;
12161         cmailOldMove = 0;
12162         return TRUE;
12163     }
12164
12165     // [HGM] PV info: routine tests if comment empty
12166     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12167         DisplayComment(currentMove - 1, commentList[currentMove]);
12168     }
12169     if (!matchMode && appData.timeDelay != 0)
12170       DrawPosition(FALSE, boards[currentMove]);
12171
12172     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12173       programStats.ok_to_send = 1;
12174     }
12175
12176     /* if the first token after the PGN tags is a move
12177      * and not move number 1, retrieve it from the parser
12178      */
12179     if (cm != MoveNumberOne)
12180         LoadGameOneMove(cm);
12181
12182     /* load the remaining moves from the file */
12183     while (LoadGameOneMove(EndOfFile)) {
12184       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12185       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12186     }
12187
12188     /* rewind to the start of the game */
12189     currentMove = backwardMostMove;
12190
12191     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12192
12193     if (oldGameMode == AnalyzeFile ||
12194         oldGameMode == AnalyzeMode) {
12195       AnalyzeFileEvent();
12196     }
12197
12198     if (!matchMode && pos > 0) {
12199         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12200     } else
12201     if (matchMode || appData.timeDelay == 0) {
12202       ToEndEvent();
12203     } else if (appData.timeDelay > 0) {
12204       AutoPlayGameLoop();
12205     }
12206
12207     if (appData.debugMode)
12208         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12209
12210     loadFlag = 0; /* [HGM] true game starts */
12211     return TRUE;
12212 }
12213
12214 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12215 int
12216 ReloadPosition (int offset)
12217 {
12218     int positionNumber = lastLoadPositionNumber + offset;
12219     if (lastLoadPositionFP == NULL) {
12220         DisplayError(_("No position has been loaded yet"), 0);
12221         return FALSE;
12222     }
12223     if (positionNumber <= 0) {
12224         DisplayError(_("Can't back up any further"), 0);
12225         return FALSE;
12226     }
12227     return LoadPosition(lastLoadPositionFP, positionNumber,
12228                         lastLoadPositionTitle);
12229 }
12230
12231 /* Load the nth position from the given file */
12232 int
12233 LoadPositionFromFile (char *filename, int n, char *title)
12234 {
12235     FILE *f;
12236     char buf[MSG_SIZ];
12237
12238     if (strcmp(filename, "-") == 0) {
12239         return LoadPosition(stdin, n, "stdin");
12240     } else {
12241         f = fopen(filename, "rb");
12242         if (f == NULL) {
12243             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12244             DisplayError(buf, errno);
12245             return FALSE;
12246         } else {
12247             return LoadPosition(f, n, title);
12248         }
12249     }
12250 }
12251
12252 /* Load the nth position from the given open file, and close it */
12253 int
12254 LoadPosition (FILE *f, int positionNumber, char *title)
12255 {
12256     char *p, line[MSG_SIZ];
12257     Board initial_position;
12258     int i, j, fenMode, pn;
12259
12260     if (gameMode == Training )
12261         SetTrainingModeOff();
12262
12263     if (gameMode != BeginningOfGame) {
12264         Reset(FALSE, TRUE);
12265     }
12266     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12267         fclose(lastLoadPositionFP);
12268     }
12269     if (positionNumber == 0) positionNumber = 1;
12270     lastLoadPositionFP = f;
12271     lastLoadPositionNumber = positionNumber;
12272     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12273     if (first.pr == NoProc && !appData.noChessProgram) {
12274       StartChessProgram(&first);
12275       InitChessProgram(&first, FALSE);
12276     }
12277     pn = positionNumber;
12278     if (positionNumber < 0) {
12279         /* Negative position number means to seek to that byte offset */
12280         if (fseek(f, -positionNumber, 0) == -1) {
12281             DisplayError(_("Can't seek on position file"), 0);
12282             return FALSE;
12283         };
12284         pn = 1;
12285     } else {
12286         if (fseek(f, 0, 0) == -1) {
12287             if (f == lastLoadPositionFP ?
12288                 positionNumber == lastLoadPositionNumber + 1 :
12289                 positionNumber == 1) {
12290                 pn = 1;
12291             } else {
12292                 DisplayError(_("Can't seek on position file"), 0);
12293                 return FALSE;
12294             }
12295         }
12296     }
12297     /* See if this file is FEN or old-style xboard */
12298     if (fgets(line, MSG_SIZ, f) == NULL) {
12299         DisplayError(_("Position not found in file"), 0);
12300         return FALSE;
12301     }
12302     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12303     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12304
12305     if (pn >= 2) {
12306         if (fenMode || line[0] == '#') pn--;
12307         while (pn > 0) {
12308             /* skip positions before number pn */
12309             if (fgets(line, MSG_SIZ, f) == NULL) {
12310                 Reset(TRUE, TRUE);
12311                 DisplayError(_("Position not found in file"), 0);
12312                 return FALSE;
12313             }
12314             if (fenMode || line[0] == '#') pn--;
12315         }
12316     }
12317
12318     if (fenMode) {
12319         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12320             DisplayError(_("Bad FEN position in file"), 0);
12321             return FALSE;
12322         }
12323     } else {
12324         (void) fgets(line, MSG_SIZ, f);
12325         (void) fgets(line, MSG_SIZ, f);
12326
12327         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12328             (void) fgets(line, MSG_SIZ, f);
12329             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12330                 if (*p == ' ')
12331                   continue;
12332                 initial_position[i][j++] = CharToPiece(*p);
12333             }
12334         }
12335
12336         blackPlaysFirst = FALSE;
12337         if (!feof(f)) {
12338             (void) fgets(line, MSG_SIZ, f);
12339             if (strncmp(line, "black", strlen("black"))==0)
12340               blackPlaysFirst = TRUE;
12341         }
12342     }
12343     startedFromSetupPosition = TRUE;
12344
12345     CopyBoard(boards[0], initial_position);
12346     if (blackPlaysFirst) {
12347         currentMove = forwardMostMove = backwardMostMove = 1;
12348         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12349         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12350         CopyBoard(boards[1], initial_position);
12351         DisplayMessage("", _("Black to play"));
12352     } else {
12353         currentMove = forwardMostMove = backwardMostMove = 0;
12354         DisplayMessage("", _("White to play"));
12355     }
12356     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12357     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12358         SendToProgram("force\n", &first);
12359         SendBoard(&first, forwardMostMove);
12360     }
12361     if (appData.debugMode) {
12362 int i, j;
12363   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12364   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12365         fprintf(debugFP, "Load Position\n");
12366     }
12367
12368     if (positionNumber > 1) {
12369       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12370         DisplayTitle(line);
12371     } else {
12372         DisplayTitle(title);
12373     }
12374     gameMode = EditGame;
12375     ModeHighlight();
12376     ResetClocks();
12377     timeRemaining[0][1] = whiteTimeRemaining;
12378     timeRemaining[1][1] = blackTimeRemaining;
12379     DrawPosition(FALSE, boards[currentMove]);
12380
12381     return TRUE;
12382 }
12383
12384
12385 void
12386 CopyPlayerNameIntoFileName (char **dest, char *src)
12387 {
12388     while (*src != NULLCHAR && *src != ',') {
12389         if (*src == ' ') {
12390             *(*dest)++ = '_';
12391             src++;
12392         } else {
12393             *(*dest)++ = *src++;
12394         }
12395     }
12396 }
12397
12398 char *
12399 DefaultFileName (char *ext)
12400 {
12401     static char def[MSG_SIZ];
12402     char *p;
12403
12404     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12405         p = def;
12406         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12407         *p++ = '-';
12408         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12409         *p++ = '.';
12410         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12411     } else {
12412         def[0] = NULLCHAR;
12413     }
12414     return def;
12415 }
12416
12417 /* Save the current game to the given file */
12418 int
12419 SaveGameToFile (char *filename, int append)
12420 {
12421     FILE *f;
12422     char buf[MSG_SIZ];
12423     int result, i, t,tot=0;
12424
12425     if (strcmp(filename, "-") == 0) {
12426         return SaveGame(stdout, 0, NULL);
12427     } else {
12428         for(i=0; i<10; i++) { // upto 10 tries
12429              f = fopen(filename, append ? "a" : "w");
12430              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12431              if(f || errno != 13) break;
12432              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12433              tot += t;
12434         }
12435         if (f == NULL) {
12436             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12437             DisplayError(buf, errno);
12438             return FALSE;
12439         } else {
12440             safeStrCpy(buf, lastMsg, MSG_SIZ);
12441             DisplayMessage(_("Waiting for access to save file"), "");
12442             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12443             DisplayMessage(_("Saving game"), "");
12444             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12445             result = SaveGame(f, 0, NULL);
12446             DisplayMessage(buf, "");
12447             return result;
12448         }
12449     }
12450 }
12451
12452 char *
12453 SavePart (char *str)
12454 {
12455     static char buf[MSG_SIZ];
12456     char *p;
12457
12458     p = strchr(str, ' ');
12459     if (p == NULL) return str;
12460     strncpy(buf, str, p - str);
12461     buf[p - str] = NULLCHAR;
12462     return buf;
12463 }
12464
12465 #define PGN_MAX_LINE 75
12466
12467 #define PGN_SIDE_WHITE  0
12468 #define PGN_SIDE_BLACK  1
12469
12470 static int
12471 FindFirstMoveOutOfBook (int side)
12472 {
12473     int result = -1;
12474
12475     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12476         int index = backwardMostMove;
12477         int has_book_hit = 0;
12478
12479         if( (index % 2) != side ) {
12480             index++;
12481         }
12482
12483         while( index < forwardMostMove ) {
12484             /* Check to see if engine is in book */
12485             int depth = pvInfoList[index].depth;
12486             int score = pvInfoList[index].score;
12487             int in_book = 0;
12488
12489             if( depth <= 2 ) {
12490                 in_book = 1;
12491             }
12492             else if( score == 0 && depth == 63 ) {
12493                 in_book = 1; /* Zappa */
12494             }
12495             else if( score == 2 && depth == 99 ) {
12496                 in_book = 1; /* Abrok */
12497             }
12498
12499             has_book_hit += in_book;
12500
12501             if( ! in_book ) {
12502                 result = index;
12503
12504                 break;
12505             }
12506
12507             index += 2;
12508         }
12509     }
12510
12511     return result;
12512 }
12513
12514 void
12515 GetOutOfBookInfo (char * buf)
12516 {
12517     int oob[2];
12518     int i;
12519     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12520
12521     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12522     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12523
12524     *buf = '\0';
12525
12526     if( oob[0] >= 0 || oob[1] >= 0 ) {
12527         for( i=0; i<2; i++ ) {
12528             int idx = oob[i];
12529
12530             if( idx >= 0 ) {
12531                 if( i > 0 && oob[0] >= 0 ) {
12532                     strcat( buf, "   " );
12533                 }
12534
12535                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12536                 sprintf( buf+strlen(buf), "%s%.2f",
12537                     pvInfoList[idx].score >= 0 ? "+" : "",
12538                     pvInfoList[idx].score / 100.0 );
12539             }
12540         }
12541     }
12542 }
12543
12544 /* Save game in PGN style and close the file */
12545 int
12546 SaveGamePGN (FILE *f)
12547 {
12548     int i, offset, linelen, newblock;
12549     time_t tm;
12550 //    char *movetext;
12551     char numtext[32];
12552     int movelen, numlen, blank;
12553     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12554
12555     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12556
12557     tm = time((time_t *) NULL);
12558
12559     PrintPGNTags(f, &gameInfo);
12560
12561     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12562
12563     if (backwardMostMove > 0 || startedFromSetupPosition) {
12564         char *fen = PositionToFEN(backwardMostMove, NULL);
12565         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12566         fprintf(f, "\n{--------------\n");
12567         PrintPosition(f, backwardMostMove);
12568         fprintf(f, "--------------}\n");
12569         free(fen);
12570     }
12571     else {
12572         /* [AS] Out of book annotation */
12573         if( appData.saveOutOfBookInfo ) {
12574             char buf[64];
12575
12576             GetOutOfBookInfo( buf );
12577
12578             if( buf[0] != '\0' ) {
12579                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12580             }
12581         }
12582
12583         fprintf(f, "\n");
12584     }
12585
12586     i = backwardMostMove;
12587     linelen = 0;
12588     newblock = TRUE;
12589
12590     while (i < forwardMostMove) {
12591         /* Print comments preceding this move */
12592         if (commentList[i] != NULL) {
12593             if (linelen > 0) fprintf(f, "\n");
12594             fprintf(f, "%s", commentList[i]);
12595             linelen = 0;
12596             newblock = TRUE;
12597         }
12598
12599         /* Format move number */
12600         if ((i % 2) == 0)
12601           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12602         else
12603           if (newblock)
12604             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12605           else
12606             numtext[0] = NULLCHAR;
12607
12608         numlen = strlen(numtext);
12609         newblock = FALSE;
12610
12611         /* Print move number */
12612         blank = linelen > 0 && numlen > 0;
12613         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12614             fprintf(f, "\n");
12615             linelen = 0;
12616             blank = 0;
12617         }
12618         if (blank) {
12619             fprintf(f, " ");
12620             linelen++;
12621         }
12622         fprintf(f, "%s", numtext);
12623         linelen += numlen;
12624
12625         /* Get move */
12626         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12627         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12628
12629         /* Print move */
12630         blank = linelen > 0 && movelen > 0;
12631         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12632             fprintf(f, "\n");
12633             linelen = 0;
12634             blank = 0;
12635         }
12636         if (blank) {
12637             fprintf(f, " ");
12638             linelen++;
12639         }
12640         fprintf(f, "%s", move_buffer);
12641         linelen += movelen;
12642
12643         /* [AS] Add PV info if present */
12644         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12645             /* [HGM] add time */
12646             char buf[MSG_SIZ]; int seconds;
12647
12648             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12649
12650             if( seconds <= 0)
12651               buf[0] = 0;
12652             else
12653               if( seconds < 30 )
12654                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12655               else
12656                 {
12657                   seconds = (seconds + 4)/10; // round to full seconds
12658                   if( seconds < 60 )
12659                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12660                   else
12661                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12662                 }
12663
12664             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12665                       pvInfoList[i].score >= 0 ? "+" : "",
12666                       pvInfoList[i].score / 100.0,
12667                       pvInfoList[i].depth,
12668                       buf );
12669
12670             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12671
12672             /* Print score/depth */
12673             blank = linelen > 0 && movelen > 0;
12674             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12675                 fprintf(f, "\n");
12676                 linelen = 0;
12677                 blank = 0;
12678             }
12679             if (blank) {
12680                 fprintf(f, " ");
12681                 linelen++;
12682             }
12683             fprintf(f, "%s", move_buffer);
12684             linelen += movelen;
12685         }
12686
12687         i++;
12688     }
12689
12690     /* Start a new line */
12691     if (linelen > 0) fprintf(f, "\n");
12692
12693     /* Print comments after last move */
12694     if (commentList[i] != NULL) {
12695         fprintf(f, "%s\n", commentList[i]);
12696     }
12697
12698     /* Print result */
12699     if (gameInfo.resultDetails != NULL &&
12700         gameInfo.resultDetails[0] != NULLCHAR) {
12701         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12702                 PGNResult(gameInfo.result));
12703     } else {
12704         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12705     }
12706
12707     fclose(f);
12708     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12709     return TRUE;
12710 }
12711
12712 /* Save game in old style and close the file */
12713 int
12714 SaveGameOldStyle (FILE *f)
12715 {
12716     int i, offset;
12717     time_t tm;
12718
12719     tm = time((time_t *) NULL);
12720
12721     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12722     PrintOpponents(f);
12723
12724     if (backwardMostMove > 0 || startedFromSetupPosition) {
12725         fprintf(f, "\n[--------------\n");
12726         PrintPosition(f, backwardMostMove);
12727         fprintf(f, "--------------]\n");
12728     } else {
12729         fprintf(f, "\n");
12730     }
12731
12732     i = backwardMostMove;
12733     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12734
12735     while (i < forwardMostMove) {
12736         if (commentList[i] != NULL) {
12737             fprintf(f, "[%s]\n", commentList[i]);
12738         }
12739
12740         if ((i % 2) == 1) {
12741             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12742             i++;
12743         } else {
12744             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12745             i++;
12746             if (commentList[i] != NULL) {
12747                 fprintf(f, "\n");
12748                 continue;
12749             }
12750             if (i >= forwardMostMove) {
12751                 fprintf(f, "\n");
12752                 break;
12753             }
12754             fprintf(f, "%s\n", parseList[i]);
12755             i++;
12756         }
12757     }
12758
12759     if (commentList[i] != NULL) {
12760         fprintf(f, "[%s]\n", commentList[i]);
12761     }
12762
12763     /* This isn't really the old style, but it's close enough */
12764     if (gameInfo.resultDetails != NULL &&
12765         gameInfo.resultDetails[0] != NULLCHAR) {
12766         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12767                 gameInfo.resultDetails);
12768     } else {
12769         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12770     }
12771
12772     fclose(f);
12773     return TRUE;
12774 }
12775
12776 /* Save the current game to open file f and close the file */
12777 int
12778 SaveGame (FILE *f, int dummy, char *dummy2)
12779 {
12780     if (gameMode == EditPosition) EditPositionDone(TRUE);
12781     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12782     if (appData.oldSaveStyle)
12783       return SaveGameOldStyle(f);
12784     else
12785       return SaveGamePGN(f);
12786 }
12787
12788 /* Save the current position to the given file */
12789 int
12790 SavePositionToFile (char *filename)
12791 {
12792     FILE *f;
12793     char buf[MSG_SIZ];
12794
12795     if (strcmp(filename, "-") == 0) {
12796         return SavePosition(stdout, 0, NULL);
12797     } else {
12798         f = fopen(filename, "a");
12799         if (f == NULL) {
12800             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12801             DisplayError(buf, errno);
12802             return FALSE;
12803         } else {
12804             safeStrCpy(buf, lastMsg, MSG_SIZ);
12805             DisplayMessage(_("Waiting for access to save file"), "");
12806             flock(fileno(f), LOCK_EX); // [HGM] lock
12807             DisplayMessage(_("Saving position"), "");
12808             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12809             SavePosition(f, 0, NULL);
12810             DisplayMessage(buf, "");
12811             return TRUE;
12812         }
12813     }
12814 }
12815
12816 /* Save the current position to the given open file and close the file */
12817 int
12818 SavePosition (FILE *f, int dummy, char *dummy2)
12819 {
12820     time_t tm;
12821     char *fen;
12822
12823     if (gameMode == EditPosition) EditPositionDone(TRUE);
12824     if (appData.oldSaveStyle) {
12825         tm = time((time_t *) NULL);
12826
12827         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12828         PrintOpponents(f);
12829         fprintf(f, "[--------------\n");
12830         PrintPosition(f, currentMove);
12831         fprintf(f, "--------------]\n");
12832     } else {
12833         fen = PositionToFEN(currentMove, NULL);
12834         fprintf(f, "%s\n", fen);
12835         free(fen);
12836     }
12837     fclose(f);
12838     return TRUE;
12839 }
12840
12841 void
12842 ReloadCmailMsgEvent (int unregister)
12843 {
12844 #if !WIN32
12845     static char *inFilename = NULL;
12846     static char *outFilename;
12847     int i;
12848     struct stat inbuf, outbuf;
12849     int status;
12850
12851     /* Any registered moves are unregistered if unregister is set, */
12852     /* i.e. invoked by the signal handler */
12853     if (unregister) {
12854         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12855             cmailMoveRegistered[i] = FALSE;
12856             if (cmailCommentList[i] != NULL) {
12857                 free(cmailCommentList[i]);
12858                 cmailCommentList[i] = NULL;
12859             }
12860         }
12861         nCmailMovesRegistered = 0;
12862     }
12863
12864     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12865         cmailResult[i] = CMAIL_NOT_RESULT;
12866     }
12867     nCmailResults = 0;
12868
12869     if (inFilename == NULL) {
12870         /* Because the filenames are static they only get malloced once  */
12871         /* and they never get freed                                      */
12872         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12873         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12874
12875         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12876         sprintf(outFilename, "%s.out", appData.cmailGameName);
12877     }
12878
12879     status = stat(outFilename, &outbuf);
12880     if (status < 0) {
12881         cmailMailedMove = FALSE;
12882     } else {
12883         status = stat(inFilename, &inbuf);
12884         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12885     }
12886
12887     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12888        counts the games, notes how each one terminated, etc.
12889
12890        It would be nice to remove this kludge and instead gather all
12891        the information while building the game list.  (And to keep it
12892        in the game list nodes instead of having a bunch of fixed-size
12893        parallel arrays.)  Note this will require getting each game's
12894        termination from the PGN tags, as the game list builder does
12895        not process the game moves.  --mann
12896        */
12897     cmailMsgLoaded = TRUE;
12898     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12899
12900     /* Load first game in the file or popup game menu */
12901     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12902
12903 #endif /* !WIN32 */
12904     return;
12905 }
12906
12907 int
12908 RegisterMove ()
12909 {
12910     FILE *f;
12911     char string[MSG_SIZ];
12912
12913     if (   cmailMailedMove
12914         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12915         return TRUE;            /* Allow free viewing  */
12916     }
12917
12918     /* Unregister move to ensure that we don't leave RegisterMove        */
12919     /* with the move registered when the conditions for registering no   */
12920     /* longer hold                                                       */
12921     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12922         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12923         nCmailMovesRegistered --;
12924
12925         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12926           {
12927               free(cmailCommentList[lastLoadGameNumber - 1]);
12928               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12929           }
12930     }
12931
12932     if (cmailOldMove == -1) {
12933         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12934         return FALSE;
12935     }
12936
12937     if (currentMove > cmailOldMove + 1) {
12938         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12939         return FALSE;
12940     }
12941
12942     if (currentMove < cmailOldMove) {
12943         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12944         return FALSE;
12945     }
12946
12947     if (forwardMostMove > currentMove) {
12948         /* Silently truncate extra moves */
12949         TruncateGame();
12950     }
12951
12952     if (   (currentMove == cmailOldMove + 1)
12953         || (   (currentMove == cmailOldMove)
12954             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12955                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12956         if (gameInfo.result != GameUnfinished) {
12957             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12958         }
12959
12960         if (commentList[currentMove] != NULL) {
12961             cmailCommentList[lastLoadGameNumber - 1]
12962               = StrSave(commentList[currentMove]);
12963         }
12964         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12965
12966         if (appData.debugMode)
12967           fprintf(debugFP, "Saving %s for game %d\n",
12968                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12969
12970         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12971
12972         f = fopen(string, "w");
12973         if (appData.oldSaveStyle) {
12974             SaveGameOldStyle(f); /* also closes the file */
12975
12976             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12977             f = fopen(string, "w");
12978             SavePosition(f, 0, NULL); /* also closes the file */
12979         } else {
12980             fprintf(f, "{--------------\n");
12981             PrintPosition(f, currentMove);
12982             fprintf(f, "--------------}\n\n");
12983
12984             SaveGame(f, 0, NULL); /* also closes the file*/
12985         }
12986
12987         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12988         nCmailMovesRegistered ++;
12989     } else if (nCmailGames == 1) {
12990         DisplayError(_("You have not made a move yet"), 0);
12991         return FALSE;
12992     }
12993
12994     return TRUE;
12995 }
12996
12997 void
12998 MailMoveEvent ()
12999 {
13000 #if !WIN32
13001     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13002     FILE *commandOutput;
13003     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13004     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13005     int nBuffers;
13006     int i;
13007     int archived;
13008     char *arcDir;
13009
13010     if (! cmailMsgLoaded) {
13011         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13012         return;
13013     }
13014
13015     if (nCmailGames == nCmailResults) {
13016         DisplayError(_("No unfinished games"), 0);
13017         return;
13018     }
13019
13020 #if CMAIL_PROHIBIT_REMAIL
13021     if (cmailMailedMove) {
13022       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);
13023         DisplayError(msg, 0);
13024         return;
13025     }
13026 #endif
13027
13028     if (! (cmailMailedMove || RegisterMove())) return;
13029
13030     if (   cmailMailedMove
13031         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13032       snprintf(string, MSG_SIZ, partCommandString,
13033                appData.debugMode ? " -v" : "", appData.cmailGameName);
13034         commandOutput = popen(string, "r");
13035
13036         if (commandOutput == NULL) {
13037             DisplayError(_("Failed to invoke cmail"), 0);
13038         } else {
13039             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13040                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13041             }
13042             if (nBuffers > 1) {
13043                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13044                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13045                 nBytes = MSG_SIZ - 1;
13046             } else {
13047                 (void) memcpy(msg, buffer, nBytes);
13048             }
13049             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13050
13051             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13052                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13053
13054                 archived = TRUE;
13055                 for (i = 0; i < nCmailGames; i ++) {
13056                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13057                         archived = FALSE;
13058                     }
13059                 }
13060                 if (   archived
13061                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13062                         != NULL)) {
13063                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13064                            arcDir,
13065                            appData.cmailGameName,
13066                            gameInfo.date);
13067                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13068                     cmailMsgLoaded = FALSE;
13069                 }
13070             }
13071
13072             DisplayInformation(msg);
13073             pclose(commandOutput);
13074         }
13075     } else {
13076         if ((*cmailMsg) != '\0') {
13077             DisplayInformation(cmailMsg);
13078         }
13079     }
13080
13081     return;
13082 #endif /* !WIN32 */
13083 }
13084
13085 char *
13086 CmailMsg ()
13087 {
13088 #if WIN32
13089     return NULL;
13090 #else
13091     int  prependComma = 0;
13092     char number[5];
13093     char string[MSG_SIZ];       /* Space for game-list */
13094     int  i;
13095
13096     if (!cmailMsgLoaded) return "";
13097
13098     if (cmailMailedMove) {
13099       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13100     } else {
13101         /* Create a list of games left */
13102       snprintf(string, MSG_SIZ, "[");
13103         for (i = 0; i < nCmailGames; i ++) {
13104             if (! (   cmailMoveRegistered[i]
13105                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13106                 if (prependComma) {
13107                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13108                 } else {
13109                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13110                     prependComma = 1;
13111                 }
13112
13113                 strcat(string, number);
13114             }
13115         }
13116         strcat(string, "]");
13117
13118         if (nCmailMovesRegistered + nCmailResults == 0) {
13119             switch (nCmailGames) {
13120               case 1:
13121                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13122                 break;
13123
13124               case 2:
13125                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13126                 break;
13127
13128               default:
13129                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13130                          nCmailGames);
13131                 break;
13132             }
13133         } else {
13134             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13135               case 1:
13136                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13137                          string);
13138                 break;
13139
13140               case 0:
13141                 if (nCmailResults == nCmailGames) {
13142                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13143                 } else {
13144                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13145                 }
13146                 break;
13147
13148               default:
13149                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13150                          string);
13151             }
13152         }
13153     }
13154     return cmailMsg;
13155 #endif /* WIN32 */
13156 }
13157
13158 void
13159 ResetGameEvent ()
13160 {
13161     if (gameMode == Training)
13162       SetTrainingModeOff();
13163
13164     Reset(TRUE, TRUE);
13165     cmailMsgLoaded = FALSE;
13166     if (appData.icsActive) {
13167       SendToICS(ics_prefix);
13168       SendToICS("refresh\n");
13169     }
13170 }
13171
13172 void
13173 ExitEvent (int status)
13174 {
13175     exiting++;
13176     if (exiting > 2) {
13177       /* Give up on clean exit */
13178       exit(status);
13179     }
13180     if (exiting > 1) {
13181       /* Keep trying for clean exit */
13182       return;
13183     }
13184
13185     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13186
13187     if (telnetISR != NULL) {
13188       RemoveInputSource(telnetISR);
13189     }
13190     if (icsPR != NoProc) {
13191       DestroyChildProcess(icsPR, TRUE);
13192     }
13193
13194     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13195     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13196
13197     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13198     /* make sure this other one finishes before killing it!                  */
13199     if(endingGame) { int count = 0;
13200         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13201         while(endingGame && count++ < 10) DoSleep(1);
13202         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13203     }
13204
13205     /* Kill off chess programs */
13206     if (first.pr != NoProc) {
13207         ExitAnalyzeMode();
13208
13209         DoSleep( appData.delayBeforeQuit );
13210         SendToProgram("quit\n", &first);
13211         DoSleep( appData.delayAfterQuit );
13212         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13213     }
13214     if (second.pr != NoProc) {
13215         DoSleep( appData.delayBeforeQuit );
13216         SendToProgram("quit\n", &second);
13217         DoSleep( appData.delayAfterQuit );
13218         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13219     }
13220     if (first.isr != NULL) {
13221         RemoveInputSource(first.isr);
13222     }
13223     if (second.isr != NULL) {
13224         RemoveInputSource(second.isr);
13225     }
13226
13227     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13228     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13229
13230     ShutDownFrontEnd();
13231     exit(status);
13232 }
13233
13234 void
13235 PauseEvent ()
13236 {
13237     if (appData.debugMode)
13238         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13239     if (pausing) {
13240         pausing = FALSE;
13241         ModeHighlight();
13242         if (gameMode == MachinePlaysWhite ||
13243             gameMode == MachinePlaysBlack) {
13244             StartClocks();
13245         } else {
13246             DisplayBothClocks();
13247         }
13248         if (gameMode == PlayFromGameFile) {
13249             if (appData.timeDelay >= 0)
13250                 AutoPlayGameLoop();
13251         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13252             Reset(FALSE, TRUE);
13253             SendToICS(ics_prefix);
13254             SendToICS("refresh\n");
13255         } else if (currentMove < forwardMostMove) {
13256             ForwardInner(forwardMostMove);
13257         }
13258         pauseExamInvalid = FALSE;
13259     } else {
13260         switch (gameMode) {
13261           default:
13262             return;
13263           case IcsExamining:
13264             pauseExamForwardMostMove = forwardMostMove;
13265             pauseExamInvalid = FALSE;
13266             /* fall through */
13267           case IcsObserving:
13268           case IcsPlayingWhite:
13269           case IcsPlayingBlack:
13270             pausing = TRUE;
13271             ModeHighlight();
13272             return;
13273           case PlayFromGameFile:
13274             (void) StopLoadGameTimer();
13275             pausing = TRUE;
13276             ModeHighlight();
13277             break;
13278           case BeginningOfGame:
13279             if (appData.icsActive) return;
13280             /* else fall through */
13281           case MachinePlaysWhite:
13282           case MachinePlaysBlack:
13283           case TwoMachinesPlay:
13284             if (forwardMostMove == 0)
13285               return;           /* don't pause if no one has moved */
13286             if ((gameMode == MachinePlaysWhite &&
13287                  !WhiteOnMove(forwardMostMove)) ||
13288                 (gameMode == MachinePlaysBlack &&
13289                  WhiteOnMove(forwardMostMove))) {
13290                 StopClocks();
13291             }
13292             pausing = TRUE;
13293             ModeHighlight();
13294             break;
13295         }
13296     }
13297 }
13298
13299 void
13300 EditCommentEvent ()
13301 {
13302     char title[MSG_SIZ];
13303
13304     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13305       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13306     } else {
13307       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13308                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13309                parseList[currentMove - 1]);
13310     }
13311
13312     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13313 }
13314
13315
13316 void
13317 EditTagsEvent ()
13318 {
13319     char *tags = PGNTags(&gameInfo);
13320     bookUp = FALSE;
13321     EditTagsPopUp(tags, NULL);
13322     free(tags);
13323 }
13324
13325 void
13326 AnalyzeModeEvent ()
13327 {
13328     if (appData.noChessProgram || gameMode == AnalyzeMode)
13329       return;
13330
13331     if (gameMode != AnalyzeFile) {
13332         if (!appData.icsEngineAnalyze) {
13333                EditGameEvent();
13334                if (gameMode != EditGame) return;
13335         }
13336         ResurrectChessProgram();
13337         SendToProgram("analyze\n", &first);
13338         first.analyzing = TRUE;
13339         /*first.maybeThinking = TRUE;*/
13340         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13341         EngineOutputPopUp();
13342     }
13343     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13344     pausing = FALSE;
13345     ModeHighlight();
13346     SetGameInfo();
13347
13348     StartAnalysisClock();
13349     GetTimeMark(&lastNodeCountTime);
13350     lastNodeCount = 0;
13351 }
13352
13353 void
13354 AnalyzeFileEvent ()
13355 {
13356     if (appData.noChessProgram || gameMode == AnalyzeFile)
13357       return;
13358
13359     if (gameMode != AnalyzeMode) {
13360         EditGameEvent();
13361         if (gameMode != EditGame) return;
13362         ResurrectChessProgram();
13363         SendToProgram("analyze\n", &first);
13364         first.analyzing = TRUE;
13365         /*first.maybeThinking = TRUE;*/
13366         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13367         EngineOutputPopUp();
13368     }
13369     gameMode = AnalyzeFile;
13370     pausing = FALSE;
13371     ModeHighlight();
13372     SetGameInfo();
13373
13374     StartAnalysisClock();
13375     GetTimeMark(&lastNodeCountTime);
13376     lastNodeCount = 0;
13377     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13378 }
13379
13380 void
13381 MachineWhiteEvent ()
13382 {
13383     char buf[MSG_SIZ];
13384     char *bookHit = NULL;
13385
13386     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13387       return;
13388
13389
13390     if (gameMode == PlayFromGameFile ||
13391         gameMode == TwoMachinesPlay  ||
13392         gameMode == Training         ||
13393         gameMode == AnalyzeMode      ||
13394         gameMode == EndOfGame)
13395         EditGameEvent();
13396
13397     if (gameMode == EditPosition)
13398         EditPositionDone(TRUE);
13399
13400     if (!WhiteOnMove(currentMove)) {
13401         DisplayError(_("It is not White's turn"), 0);
13402         return;
13403     }
13404
13405     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13406       ExitAnalyzeMode();
13407
13408     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13409         gameMode == AnalyzeFile)
13410         TruncateGame();
13411
13412     ResurrectChessProgram();    /* in case it isn't running */
13413     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13414         gameMode = MachinePlaysWhite;
13415         ResetClocks();
13416     } else
13417     gameMode = MachinePlaysWhite;
13418     pausing = FALSE;
13419     ModeHighlight();
13420     SetGameInfo();
13421     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13422     DisplayTitle(buf);
13423     if (first.sendName) {
13424       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13425       SendToProgram(buf, &first);
13426     }
13427     if (first.sendTime) {
13428       if (first.useColors) {
13429         SendToProgram("black\n", &first); /*gnu kludge*/
13430       }
13431       SendTimeRemaining(&first, TRUE);
13432     }
13433     if (first.useColors) {
13434       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13435     }
13436     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13437     SetMachineThinkingEnables();
13438     first.maybeThinking = TRUE;
13439     StartClocks();
13440     firstMove = FALSE;
13441
13442     if (appData.autoFlipView && !flipView) {
13443       flipView = !flipView;
13444       DrawPosition(FALSE, NULL);
13445       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13446     }
13447
13448     if(bookHit) { // [HGM] book: simulate book reply
13449         static char bookMove[MSG_SIZ]; // a bit generous?
13450
13451         programStats.nodes = programStats.depth = programStats.time =
13452         programStats.score = programStats.got_only_move = 0;
13453         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13454
13455         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13456         strcat(bookMove, bookHit);
13457         HandleMachineMove(bookMove, &first);
13458     }
13459 }
13460
13461 void
13462 MachineBlackEvent ()
13463 {
13464   char buf[MSG_SIZ];
13465   char *bookHit = NULL;
13466
13467     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13468         return;
13469
13470
13471     if (gameMode == PlayFromGameFile ||
13472         gameMode == TwoMachinesPlay  ||
13473         gameMode == Training         ||
13474         gameMode == AnalyzeMode      ||
13475         gameMode == EndOfGame)
13476         EditGameEvent();
13477
13478     if (gameMode == EditPosition)
13479         EditPositionDone(TRUE);
13480
13481     if (WhiteOnMove(currentMove)) {
13482         DisplayError(_("It is not Black's turn"), 0);
13483         return;
13484     }
13485
13486     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13487       ExitAnalyzeMode();
13488
13489     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13490         gameMode == AnalyzeFile)
13491         TruncateGame();
13492
13493     ResurrectChessProgram();    /* in case it isn't running */
13494     gameMode = MachinePlaysBlack;
13495     pausing = FALSE;
13496     ModeHighlight();
13497     SetGameInfo();
13498     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13499     DisplayTitle(buf);
13500     if (first.sendName) {
13501       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13502       SendToProgram(buf, &first);
13503     }
13504     if (first.sendTime) {
13505       if (first.useColors) {
13506         SendToProgram("white\n", &first); /*gnu kludge*/
13507       }
13508       SendTimeRemaining(&first, FALSE);
13509     }
13510     if (first.useColors) {
13511       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13512     }
13513     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13514     SetMachineThinkingEnables();
13515     first.maybeThinking = TRUE;
13516     StartClocks();
13517
13518     if (appData.autoFlipView && flipView) {
13519       flipView = !flipView;
13520       DrawPosition(FALSE, NULL);
13521       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13522     }
13523     if(bookHit) { // [HGM] book: simulate book reply
13524         static char bookMove[MSG_SIZ]; // a bit generous?
13525
13526         programStats.nodes = programStats.depth = programStats.time =
13527         programStats.score = programStats.got_only_move = 0;
13528         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13529
13530         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13531         strcat(bookMove, bookHit);
13532         HandleMachineMove(bookMove, &first);
13533     }
13534 }
13535
13536
13537 void
13538 DisplayTwoMachinesTitle ()
13539 {
13540     char buf[MSG_SIZ];
13541     if (appData.matchGames > 0) {
13542         if(appData.tourneyFile[0]) {
13543           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13544                    gameInfo.white, _("vs."), gameInfo.black,
13545                    nextGame+1, appData.matchGames+1,
13546                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13547         } else 
13548         if (first.twoMachinesColor[0] == 'w') {
13549           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13550                    gameInfo.white, _("vs."),  gameInfo.black,
13551                    first.matchWins, second.matchWins,
13552                    matchGame - 1 - (first.matchWins + second.matchWins));
13553         } else {
13554           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13555                    gameInfo.white, _("vs."), gameInfo.black,
13556                    second.matchWins, first.matchWins,
13557                    matchGame - 1 - (first.matchWins + second.matchWins));
13558         }
13559     } else {
13560       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13561     }
13562     DisplayTitle(buf);
13563 }
13564
13565 void
13566 SettingsMenuIfReady ()
13567 {
13568   if (second.lastPing != second.lastPong) {
13569     DisplayMessage("", _("Waiting for second chess program"));
13570     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13571     return;
13572   }
13573   ThawUI();
13574   DisplayMessage("", "");
13575   SettingsPopUp(&second);
13576 }
13577
13578 int
13579 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13580 {
13581     char buf[MSG_SIZ];
13582     if (cps->pr == NoProc) {
13583         StartChessProgram(cps);
13584         if (cps->protocolVersion == 1) {
13585           retry();
13586         } else {
13587           /* kludge: allow timeout for initial "feature" command */
13588           FreezeUI();
13589           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13590           DisplayMessage("", buf);
13591           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13592         }
13593         return 1;
13594     }
13595     return 0;
13596 }
13597
13598 void
13599 TwoMachinesEvent P((void))
13600 {
13601     int i;
13602     char buf[MSG_SIZ];
13603     ChessProgramState *onmove;
13604     char *bookHit = NULL;
13605     static int stalling = 0;
13606     TimeMark now;
13607     long wait;
13608
13609     if (appData.noChessProgram) return;
13610
13611     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13612         DisplayError("second engine does not play this", 0);
13613         return;
13614     }
13615
13616     switch (gameMode) {
13617       case TwoMachinesPlay:
13618         return;
13619       case MachinePlaysWhite:
13620       case MachinePlaysBlack:
13621         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13622             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13623             return;
13624         }
13625         /* fall through */
13626       case BeginningOfGame:
13627       case PlayFromGameFile:
13628       case EndOfGame:
13629         EditGameEvent();
13630         if (gameMode != EditGame) return;
13631         break;
13632       case EditPosition:
13633         EditPositionDone(TRUE);
13634         break;
13635       case AnalyzeMode:
13636       case AnalyzeFile:
13637         ExitAnalyzeMode();
13638         break;
13639       case EditGame:
13640       default:
13641         break;
13642     }
13643
13644 //    forwardMostMove = currentMove;
13645     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13646
13647     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13648
13649     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13650     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13651       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13652       return;
13653     }
13654     if(!stalling) {
13655       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13656       SendToProgram("force\n", &second);
13657       stalling = 1;
13658       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13659       return;
13660     }
13661     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13662     if(appData.matchPause>10000 || appData.matchPause<10)
13663                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13664     wait = SubtractTimeMarks(&now, &pauseStart);
13665     if(wait < appData.matchPause) {
13666         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13667         return;
13668     }
13669     // we are now committed to starting the game
13670     stalling = 0;
13671     DisplayMessage("", "");
13672     if (startedFromSetupPosition) {
13673         SendBoard(&second, backwardMostMove);
13674     if (appData.debugMode) {
13675         fprintf(debugFP, "Two Machines\n");
13676     }
13677     }
13678     for (i = backwardMostMove; i < forwardMostMove; i++) {
13679         SendMoveToProgram(i, &second);
13680     }
13681
13682     gameMode = TwoMachinesPlay;
13683     pausing = FALSE;
13684     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13685     SetGameInfo();
13686     DisplayTwoMachinesTitle();
13687     firstMove = TRUE;
13688     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13689         onmove = &first;
13690     } else {
13691         onmove = &second;
13692     }
13693     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13694     SendToProgram(first.computerString, &first);
13695     if (first.sendName) {
13696       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13697       SendToProgram(buf, &first);
13698     }
13699     SendToProgram(second.computerString, &second);
13700     if (second.sendName) {
13701       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13702       SendToProgram(buf, &second);
13703     }
13704
13705     ResetClocks();
13706     if (!first.sendTime || !second.sendTime) {
13707         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13708         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13709     }
13710     if (onmove->sendTime) {
13711       if (onmove->useColors) {
13712         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13713       }
13714       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13715     }
13716     if (onmove->useColors) {
13717       SendToProgram(onmove->twoMachinesColor, onmove);
13718     }
13719     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13720 //    SendToProgram("go\n", onmove);
13721     onmove->maybeThinking = TRUE;
13722     SetMachineThinkingEnables();
13723
13724     StartClocks();
13725
13726     if(bookHit) { // [HGM] book: simulate book reply
13727         static char bookMove[MSG_SIZ]; // a bit generous?
13728
13729         programStats.nodes = programStats.depth = programStats.time =
13730         programStats.score = programStats.got_only_move = 0;
13731         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13732
13733         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13734         strcat(bookMove, bookHit);
13735         savedMessage = bookMove; // args for deferred call
13736         savedState = onmove;
13737         ScheduleDelayedEvent(DeferredBookMove, 1);
13738     }
13739 }
13740
13741 void
13742 TrainingEvent ()
13743 {
13744     if (gameMode == Training) {
13745       SetTrainingModeOff();
13746       gameMode = PlayFromGameFile;
13747       DisplayMessage("", _("Training mode off"));
13748     } else {
13749       gameMode = Training;
13750       animateTraining = appData.animate;
13751
13752       /* make sure we are not already at the end of the game */
13753       if (currentMove < forwardMostMove) {
13754         SetTrainingModeOn();
13755         DisplayMessage("", _("Training mode on"));
13756       } else {
13757         gameMode = PlayFromGameFile;
13758         DisplayError(_("Already at end of game"), 0);
13759       }
13760     }
13761     ModeHighlight();
13762 }
13763
13764 void
13765 IcsClientEvent ()
13766 {
13767     if (!appData.icsActive) return;
13768     switch (gameMode) {
13769       case IcsPlayingWhite:
13770       case IcsPlayingBlack:
13771       case IcsObserving:
13772       case IcsIdle:
13773       case BeginningOfGame:
13774       case IcsExamining:
13775         return;
13776
13777       case EditGame:
13778         break;
13779
13780       case EditPosition:
13781         EditPositionDone(TRUE);
13782         break;
13783
13784       case AnalyzeMode:
13785       case AnalyzeFile:
13786         ExitAnalyzeMode();
13787         break;
13788
13789       default:
13790         EditGameEvent();
13791         break;
13792     }
13793
13794     gameMode = IcsIdle;
13795     ModeHighlight();
13796     return;
13797 }
13798
13799 void
13800 EditGameEvent ()
13801 {
13802     int i;
13803
13804     switch (gameMode) {
13805       case Training:
13806         SetTrainingModeOff();
13807         break;
13808       case MachinePlaysWhite:
13809       case MachinePlaysBlack:
13810       case BeginningOfGame:
13811         SendToProgram("force\n", &first);
13812         SetUserThinkingEnables();
13813         break;
13814       case PlayFromGameFile:
13815         (void) StopLoadGameTimer();
13816         if (gameFileFP != NULL) {
13817             gameFileFP = NULL;
13818         }
13819         break;
13820       case EditPosition:
13821         EditPositionDone(TRUE);
13822         break;
13823       case AnalyzeMode:
13824       case AnalyzeFile:
13825         ExitAnalyzeMode();
13826         SendToProgram("force\n", &first);
13827         break;
13828       case TwoMachinesPlay:
13829         GameEnds(EndOfFile, NULL, GE_PLAYER);
13830         ResurrectChessProgram();
13831         SetUserThinkingEnables();
13832         break;
13833       case EndOfGame:
13834         ResurrectChessProgram();
13835         break;
13836       case IcsPlayingBlack:
13837       case IcsPlayingWhite:
13838         DisplayError(_("Warning: You are still playing a game"), 0);
13839         break;
13840       case IcsObserving:
13841         DisplayError(_("Warning: You are still observing a game"), 0);
13842         break;
13843       case IcsExamining:
13844         DisplayError(_("Warning: You are still examining a game"), 0);
13845         break;
13846       case IcsIdle:
13847         break;
13848       case EditGame:
13849       default:
13850         return;
13851     }
13852
13853     pausing = FALSE;
13854     StopClocks();
13855     first.offeredDraw = second.offeredDraw = 0;
13856
13857     if (gameMode == PlayFromGameFile) {
13858         whiteTimeRemaining = timeRemaining[0][currentMove];
13859         blackTimeRemaining = timeRemaining[1][currentMove];
13860         DisplayTitle("");
13861     }
13862
13863     if (gameMode == MachinePlaysWhite ||
13864         gameMode == MachinePlaysBlack ||
13865         gameMode == TwoMachinesPlay ||
13866         gameMode == EndOfGame) {
13867         i = forwardMostMove;
13868         while (i > currentMove) {
13869             SendToProgram("undo\n", &first);
13870             i--;
13871         }
13872         if(!adjustedClock) {
13873         whiteTimeRemaining = timeRemaining[0][currentMove];
13874         blackTimeRemaining = timeRemaining[1][currentMove];
13875         DisplayBothClocks();
13876         }
13877         if (whiteFlag || blackFlag) {
13878             whiteFlag = blackFlag = 0;
13879         }
13880         DisplayTitle("");
13881     }
13882
13883     gameMode = EditGame;
13884     ModeHighlight();
13885     SetGameInfo();
13886 }
13887
13888
13889 void
13890 EditPositionEvent ()
13891 {
13892     if (gameMode == EditPosition) {
13893         EditGameEvent();
13894         return;
13895     }
13896
13897     EditGameEvent();
13898     if (gameMode != EditGame) return;
13899
13900     gameMode = EditPosition;
13901     ModeHighlight();
13902     SetGameInfo();
13903     if (currentMove > 0)
13904       CopyBoard(boards[0], boards[currentMove]);
13905
13906     blackPlaysFirst = !WhiteOnMove(currentMove);
13907     ResetClocks();
13908     currentMove = forwardMostMove = backwardMostMove = 0;
13909     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13910     DisplayMove(-1);
13911     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13912 }
13913
13914 void
13915 ExitAnalyzeMode ()
13916 {
13917     /* [DM] icsEngineAnalyze - possible call from other functions */
13918     if (appData.icsEngineAnalyze) {
13919         appData.icsEngineAnalyze = FALSE;
13920
13921         DisplayMessage("",_("Close ICS engine analyze..."));
13922     }
13923     if (first.analysisSupport && first.analyzing) {
13924       SendToProgram("exit\n", &first);
13925       first.analyzing = FALSE;
13926     }
13927     thinkOutput[0] = NULLCHAR;
13928 }
13929
13930 void
13931 EditPositionDone (Boolean fakeRights)
13932 {
13933     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13934
13935     startedFromSetupPosition = TRUE;
13936     InitChessProgram(&first, FALSE);
13937     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13938       boards[0][EP_STATUS] = EP_NONE;
13939       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13940     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13941         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13942         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13943       } else boards[0][CASTLING][2] = NoRights;
13944     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13945         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13946         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13947       } else boards[0][CASTLING][5] = NoRights;
13948     }
13949     SendToProgram("force\n", &first);
13950     if (blackPlaysFirst) {
13951         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13952         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13953         currentMove = forwardMostMove = backwardMostMove = 1;
13954         CopyBoard(boards[1], boards[0]);
13955     } else {
13956         currentMove = forwardMostMove = backwardMostMove = 0;
13957     }
13958     SendBoard(&first, forwardMostMove);
13959     if (appData.debugMode) {
13960         fprintf(debugFP, "EditPosDone\n");
13961     }
13962     DisplayTitle("");
13963     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13964     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13965     gameMode = EditGame;
13966     ModeHighlight();
13967     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13968     ClearHighlights(); /* [AS] */
13969 }
13970
13971 /* Pause for `ms' milliseconds */
13972 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13973 void
13974 TimeDelay (long ms)
13975 {
13976     TimeMark m1, m2;
13977
13978     GetTimeMark(&m1);
13979     do {
13980         GetTimeMark(&m2);
13981     } while (SubtractTimeMarks(&m2, &m1) < ms);
13982 }
13983
13984 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13985 void
13986 SendMultiLineToICS (char *buf)
13987 {
13988     char temp[MSG_SIZ+1], *p;
13989     int len;
13990
13991     len = strlen(buf);
13992     if (len > MSG_SIZ)
13993       len = MSG_SIZ;
13994
13995     strncpy(temp, buf, len);
13996     temp[len] = 0;
13997
13998     p = temp;
13999     while (*p) {
14000         if (*p == '\n' || *p == '\r')
14001           *p = ' ';
14002         ++p;
14003     }
14004
14005     strcat(temp, "\n");
14006     SendToICS(temp);
14007     SendToPlayer(temp, strlen(temp));
14008 }
14009
14010 void
14011 SetWhiteToPlayEvent ()
14012 {
14013     if (gameMode == EditPosition) {
14014         blackPlaysFirst = FALSE;
14015         DisplayBothClocks();    /* works because currentMove is 0 */
14016     } else if (gameMode == IcsExamining) {
14017         SendToICS(ics_prefix);
14018         SendToICS("tomove white\n");
14019     }
14020 }
14021
14022 void
14023 SetBlackToPlayEvent ()
14024 {
14025     if (gameMode == EditPosition) {
14026         blackPlaysFirst = TRUE;
14027         currentMove = 1;        /* kludge */
14028         DisplayBothClocks();
14029         currentMove = 0;
14030     } else if (gameMode == IcsExamining) {
14031         SendToICS(ics_prefix);
14032         SendToICS("tomove black\n");
14033     }
14034 }
14035
14036 void
14037 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14038 {
14039     char buf[MSG_SIZ];
14040     ChessSquare piece = boards[0][y][x];
14041
14042     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14043
14044     switch (selection) {
14045       case ClearBoard:
14046         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14047             SendToICS(ics_prefix);
14048             SendToICS("bsetup clear\n");
14049         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14050             SendToICS(ics_prefix);
14051             SendToICS("clearboard\n");
14052         } else {
14053             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14054                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14055                 for (y = 0; y < BOARD_HEIGHT; y++) {
14056                     if (gameMode == IcsExamining) {
14057                         if (boards[currentMove][y][x] != EmptySquare) {
14058                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14059                                     AAA + x, ONE + y);
14060                             SendToICS(buf);
14061                         }
14062                     } else {
14063                         boards[0][y][x] = p;
14064                     }
14065                 }
14066             }
14067         }
14068         if (gameMode == EditPosition) {
14069             DrawPosition(FALSE, boards[0]);
14070         }
14071         break;
14072
14073       case WhitePlay:
14074         SetWhiteToPlayEvent();
14075         break;
14076
14077       case BlackPlay:
14078         SetBlackToPlayEvent();
14079         break;
14080
14081       case EmptySquare:
14082         if (gameMode == IcsExamining) {
14083             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14084             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14085             SendToICS(buf);
14086         } else {
14087             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14088                 if(x == BOARD_LEFT-2) {
14089                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14090                     boards[0][y][1] = 0;
14091                 } else
14092                 if(x == BOARD_RGHT+1) {
14093                     if(y >= gameInfo.holdingsSize) break;
14094                     boards[0][y][BOARD_WIDTH-2] = 0;
14095                 } else break;
14096             }
14097             boards[0][y][x] = EmptySquare;
14098             DrawPosition(FALSE, boards[0]);
14099         }
14100         break;
14101
14102       case PromotePiece:
14103         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14104            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14105             selection = (ChessSquare) (PROMOTED piece);
14106         } else if(piece == EmptySquare) selection = WhiteSilver;
14107         else selection = (ChessSquare)((int)piece - 1);
14108         goto defaultlabel;
14109
14110       case DemotePiece:
14111         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14112            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14113             selection = (ChessSquare) (DEMOTED piece);
14114         } else if(piece == EmptySquare) selection = BlackSilver;
14115         else selection = (ChessSquare)((int)piece + 1);
14116         goto defaultlabel;
14117
14118       case WhiteQueen:
14119       case BlackQueen:
14120         if(gameInfo.variant == VariantShatranj ||
14121            gameInfo.variant == VariantXiangqi  ||
14122            gameInfo.variant == VariantCourier  ||
14123            gameInfo.variant == VariantMakruk     )
14124             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14125         goto defaultlabel;
14126
14127       case WhiteKing:
14128       case BlackKing:
14129         if(gameInfo.variant == VariantXiangqi)
14130             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14131         if(gameInfo.variant == VariantKnightmate)
14132             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14133       default:
14134         defaultlabel:
14135         if (gameMode == IcsExamining) {
14136             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14137             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14138                      PieceToChar(selection), AAA + x, ONE + y);
14139             SendToICS(buf);
14140         } else {
14141             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14142                 int n;
14143                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14144                     n = PieceToNumber(selection - BlackPawn);
14145                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14146                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14147                     boards[0][BOARD_HEIGHT-1-n][1]++;
14148                 } else
14149                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14150                     n = PieceToNumber(selection);
14151                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14152                     boards[0][n][BOARD_WIDTH-1] = selection;
14153                     boards[0][n][BOARD_WIDTH-2]++;
14154                 }
14155             } else
14156             boards[0][y][x] = selection;
14157             DrawPosition(TRUE, boards[0]);
14158             ClearHighlights();
14159             fromX = fromY = -1;
14160         }
14161         break;
14162     }
14163 }
14164
14165
14166 void
14167 DropMenuEvent (ChessSquare selection, int x, int y)
14168 {
14169     ChessMove moveType;
14170
14171     switch (gameMode) {
14172       case IcsPlayingWhite:
14173       case MachinePlaysBlack:
14174         if (!WhiteOnMove(currentMove)) {
14175             DisplayMoveError(_("It is Black's turn"));
14176             return;
14177         }
14178         moveType = WhiteDrop;
14179         break;
14180       case IcsPlayingBlack:
14181       case MachinePlaysWhite:
14182         if (WhiteOnMove(currentMove)) {
14183             DisplayMoveError(_("It is White's turn"));
14184             return;
14185         }
14186         moveType = BlackDrop;
14187         break;
14188       case EditGame:
14189         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14190         break;
14191       default:
14192         return;
14193     }
14194
14195     if (moveType == BlackDrop && selection < BlackPawn) {
14196       selection = (ChessSquare) ((int) selection
14197                                  + (int) BlackPawn - (int) WhitePawn);
14198     }
14199     if (boards[currentMove][y][x] != EmptySquare) {
14200         DisplayMoveError(_("That square is occupied"));
14201         return;
14202     }
14203
14204     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14205 }
14206
14207 void
14208 AcceptEvent ()
14209 {
14210     /* Accept a pending offer of any kind from opponent */
14211
14212     if (appData.icsActive) {
14213         SendToICS(ics_prefix);
14214         SendToICS("accept\n");
14215     } else if (cmailMsgLoaded) {
14216         if (currentMove == cmailOldMove &&
14217             commentList[cmailOldMove] != NULL &&
14218             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14219                    "Black offers a draw" : "White offers a draw")) {
14220             TruncateGame();
14221             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14222             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14223         } else {
14224             DisplayError(_("There is no pending offer on this move"), 0);
14225             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14226         }
14227     } else {
14228         /* Not used for offers from chess program */
14229     }
14230 }
14231
14232 void
14233 DeclineEvent ()
14234 {
14235     /* Decline a pending offer of any kind from opponent */
14236
14237     if (appData.icsActive) {
14238         SendToICS(ics_prefix);
14239         SendToICS("decline\n");
14240     } else if (cmailMsgLoaded) {
14241         if (currentMove == cmailOldMove &&
14242             commentList[cmailOldMove] != NULL &&
14243             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14244                    "Black offers a draw" : "White offers a draw")) {
14245 #ifdef NOTDEF
14246             AppendComment(cmailOldMove, "Draw declined", TRUE);
14247             DisplayComment(cmailOldMove - 1, "Draw declined");
14248 #endif /*NOTDEF*/
14249         } else {
14250             DisplayError(_("There is no pending offer on this move"), 0);
14251         }
14252     } else {
14253         /* Not used for offers from chess program */
14254     }
14255 }
14256
14257 void
14258 RematchEvent ()
14259 {
14260     /* Issue ICS rematch command */
14261     if (appData.icsActive) {
14262         SendToICS(ics_prefix);
14263         SendToICS("rematch\n");
14264     }
14265 }
14266
14267 void
14268 CallFlagEvent ()
14269 {
14270     /* Call your opponent's flag (claim a win on time) */
14271     if (appData.icsActive) {
14272         SendToICS(ics_prefix);
14273         SendToICS("flag\n");
14274     } else {
14275         switch (gameMode) {
14276           default:
14277             return;
14278           case MachinePlaysWhite:
14279             if (whiteFlag) {
14280                 if (blackFlag)
14281                   GameEnds(GameIsDrawn, "Both players ran out of time",
14282                            GE_PLAYER);
14283                 else
14284                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14285             } else {
14286                 DisplayError(_("Your opponent is not out of time"), 0);
14287             }
14288             break;
14289           case MachinePlaysBlack:
14290             if (blackFlag) {
14291                 if (whiteFlag)
14292                   GameEnds(GameIsDrawn, "Both players ran out of time",
14293                            GE_PLAYER);
14294                 else
14295                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14296             } else {
14297                 DisplayError(_("Your opponent is not out of time"), 0);
14298             }
14299             break;
14300         }
14301     }
14302 }
14303
14304 void
14305 ClockClick (int which)
14306 {       // [HGM] code moved to back-end from winboard.c
14307         if(which) { // black clock
14308           if (gameMode == EditPosition || gameMode == IcsExamining) {
14309             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14310             SetBlackToPlayEvent();
14311           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14312           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14313           } else if (shiftKey) {
14314             AdjustClock(which, -1);
14315           } else if (gameMode == IcsPlayingWhite ||
14316                      gameMode == MachinePlaysBlack) {
14317             CallFlagEvent();
14318           }
14319         } else { // white clock
14320           if (gameMode == EditPosition || gameMode == IcsExamining) {
14321             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14322             SetWhiteToPlayEvent();
14323           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14324           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14325           } else if (shiftKey) {
14326             AdjustClock(which, -1);
14327           } else if (gameMode == IcsPlayingBlack ||
14328                    gameMode == MachinePlaysWhite) {
14329             CallFlagEvent();
14330           }
14331         }
14332 }
14333
14334 void
14335 DrawEvent ()
14336 {
14337     /* Offer draw or accept pending draw offer from opponent */
14338
14339     if (appData.icsActive) {
14340         /* Note: tournament rules require draw offers to be
14341            made after you make your move but before you punch
14342            your clock.  Currently ICS doesn't let you do that;
14343            instead, you immediately punch your clock after making
14344            a move, but you can offer a draw at any time. */
14345
14346         SendToICS(ics_prefix);
14347         SendToICS("draw\n");
14348         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14349     } else if (cmailMsgLoaded) {
14350         if (currentMove == cmailOldMove &&
14351             commentList[cmailOldMove] != NULL &&
14352             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14353                    "Black offers a draw" : "White offers a draw")) {
14354             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14355             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14356         } else if (currentMove == cmailOldMove + 1) {
14357             char *offer = WhiteOnMove(cmailOldMove) ?
14358               "White offers a draw" : "Black offers a draw";
14359             AppendComment(currentMove, offer, TRUE);
14360             DisplayComment(currentMove - 1, offer);
14361             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14362         } else {
14363             DisplayError(_("You must make your move before offering a draw"), 0);
14364             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14365         }
14366     } else if (first.offeredDraw) {
14367         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14368     } else {
14369         if (first.sendDrawOffers) {
14370             SendToProgram("draw\n", &first);
14371             userOfferedDraw = TRUE;
14372         }
14373     }
14374 }
14375
14376 void
14377 AdjournEvent ()
14378 {
14379     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14380
14381     if (appData.icsActive) {
14382         SendToICS(ics_prefix);
14383         SendToICS("adjourn\n");
14384     } else {
14385         /* Currently GNU Chess doesn't offer or accept Adjourns */
14386     }
14387 }
14388
14389
14390 void
14391 AbortEvent ()
14392 {
14393     /* Offer Abort or accept pending Abort offer from opponent */
14394
14395     if (appData.icsActive) {
14396         SendToICS(ics_prefix);
14397         SendToICS("abort\n");
14398     } else {
14399         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14400     }
14401 }
14402
14403 void
14404 ResignEvent ()
14405 {
14406     /* Resign.  You can do this even if it's not your turn. */
14407
14408     if (appData.icsActive) {
14409         SendToICS(ics_prefix);
14410         SendToICS("resign\n");
14411     } else {
14412         switch (gameMode) {
14413           case MachinePlaysWhite:
14414             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14415             break;
14416           case MachinePlaysBlack:
14417             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14418             break;
14419           case EditGame:
14420             if (cmailMsgLoaded) {
14421                 TruncateGame();
14422                 if (WhiteOnMove(cmailOldMove)) {
14423                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14424                 } else {
14425                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14426                 }
14427                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14428             }
14429             break;
14430           default:
14431             break;
14432         }
14433     }
14434 }
14435
14436
14437 void
14438 StopObservingEvent ()
14439 {
14440     /* Stop observing current games */
14441     SendToICS(ics_prefix);
14442     SendToICS("unobserve\n");
14443 }
14444
14445 void
14446 StopExaminingEvent ()
14447 {
14448     /* Stop observing current game */
14449     SendToICS(ics_prefix);
14450     SendToICS("unexamine\n");
14451 }
14452
14453 void
14454 ForwardInner (int target)
14455 {
14456     int limit; int oldSeekGraphUp = seekGraphUp;
14457
14458     if (appData.debugMode)
14459         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14460                 target, currentMove, forwardMostMove);
14461
14462     if (gameMode == EditPosition)
14463       return;
14464
14465     seekGraphUp = FALSE;
14466     MarkTargetSquares(1);
14467
14468     if (gameMode == PlayFromGameFile && !pausing)
14469       PauseEvent();
14470
14471     if (gameMode == IcsExamining && pausing)
14472       limit = pauseExamForwardMostMove;
14473     else
14474       limit = forwardMostMove;
14475
14476     if (target > limit) target = limit;
14477
14478     if (target > 0 && moveList[target - 1][0]) {
14479         int fromX, fromY, toX, toY;
14480         toX = moveList[target - 1][2] - AAA;
14481         toY = moveList[target - 1][3] - ONE;
14482         if (moveList[target - 1][1] == '@') {
14483             if (appData.highlightLastMove) {
14484                 SetHighlights(-1, -1, toX, toY);
14485             }
14486         } else {
14487             fromX = moveList[target - 1][0] - AAA;
14488             fromY = moveList[target - 1][1] - ONE;
14489             if (target == currentMove + 1) {
14490                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14491             }
14492             if (appData.highlightLastMove) {
14493                 SetHighlights(fromX, fromY, toX, toY);
14494             }
14495         }
14496     }
14497     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14498         gameMode == Training || gameMode == PlayFromGameFile ||
14499         gameMode == AnalyzeFile) {
14500         while (currentMove < target) {
14501             SendMoveToProgram(currentMove++, &first);
14502         }
14503     } else {
14504         currentMove = target;
14505     }
14506
14507     if (gameMode == EditGame || gameMode == EndOfGame) {
14508         whiteTimeRemaining = timeRemaining[0][currentMove];
14509         blackTimeRemaining = timeRemaining[1][currentMove];
14510     }
14511     DisplayBothClocks();
14512     DisplayMove(currentMove - 1);
14513     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14514     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14515     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14516         DisplayComment(currentMove - 1, commentList[currentMove]);
14517     }
14518     ClearMap(); // [HGM] exclude: invalidate map
14519 }
14520
14521
14522 void
14523 ForwardEvent ()
14524 {
14525     if (gameMode == IcsExamining && !pausing) {
14526         SendToICS(ics_prefix);
14527         SendToICS("forward\n");
14528     } else {
14529         ForwardInner(currentMove + 1);
14530     }
14531 }
14532
14533 void
14534 ToEndEvent ()
14535 {
14536     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14537         /* to optimze, we temporarily turn off analysis mode while we feed
14538          * the remaining moves to the engine. Otherwise we get analysis output
14539          * after each move.
14540          */
14541         if (first.analysisSupport) {
14542           SendToProgram("exit\nforce\n", &first);
14543           first.analyzing = FALSE;
14544         }
14545     }
14546
14547     if (gameMode == IcsExamining && !pausing) {
14548         SendToICS(ics_prefix);
14549         SendToICS("forward 999999\n");
14550     } else {
14551         ForwardInner(forwardMostMove);
14552     }
14553
14554     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14555         /* we have fed all the moves, so reactivate analysis mode */
14556         SendToProgram("analyze\n", &first);
14557         first.analyzing = TRUE;
14558         /*first.maybeThinking = TRUE;*/
14559         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14560     }
14561 }
14562
14563 void
14564 BackwardInner (int target)
14565 {
14566     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14567
14568     if (appData.debugMode)
14569         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14570                 target, currentMove, forwardMostMove);
14571
14572     if (gameMode == EditPosition) return;
14573     seekGraphUp = FALSE;
14574     MarkTargetSquares(1);
14575     if (currentMove <= backwardMostMove) {
14576         ClearHighlights();
14577         DrawPosition(full_redraw, boards[currentMove]);
14578         return;
14579     }
14580     if (gameMode == PlayFromGameFile && !pausing)
14581       PauseEvent();
14582
14583     if (moveList[target][0]) {
14584         int fromX, fromY, toX, toY;
14585         toX = moveList[target][2] - AAA;
14586         toY = moveList[target][3] - ONE;
14587         if (moveList[target][1] == '@') {
14588             if (appData.highlightLastMove) {
14589                 SetHighlights(-1, -1, toX, toY);
14590             }
14591         } else {
14592             fromX = moveList[target][0] - AAA;
14593             fromY = moveList[target][1] - ONE;
14594             if (target == currentMove - 1) {
14595                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14596             }
14597             if (appData.highlightLastMove) {
14598                 SetHighlights(fromX, fromY, toX, toY);
14599             }
14600         }
14601     }
14602     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14603         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14604         while (currentMove > target) {
14605             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14606                 // null move cannot be undone. Reload program with move history before it.
14607                 int i;
14608                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14609                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14610                 }
14611                 SendBoard(&first, i); 
14612                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14613                 break;
14614             }
14615             SendToProgram("undo\n", &first);
14616             currentMove--;
14617         }
14618     } else {
14619         currentMove = target;
14620     }
14621
14622     if (gameMode == EditGame || gameMode == EndOfGame) {
14623         whiteTimeRemaining = timeRemaining[0][currentMove];
14624         blackTimeRemaining = timeRemaining[1][currentMove];
14625     }
14626     DisplayBothClocks();
14627     DisplayMove(currentMove - 1);
14628     DrawPosition(full_redraw, boards[currentMove]);
14629     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14630     // [HGM] PV info: routine tests if comment empty
14631     DisplayComment(currentMove - 1, commentList[currentMove]);
14632     ClearMap(); // [HGM] exclude: invalidate map
14633 }
14634
14635 void
14636 BackwardEvent ()
14637 {
14638     if (gameMode == IcsExamining && !pausing) {
14639         SendToICS(ics_prefix);
14640         SendToICS("backward\n");
14641     } else {
14642         BackwardInner(currentMove - 1);
14643     }
14644 }
14645
14646 void
14647 ToStartEvent ()
14648 {
14649     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14650         /* to optimize, we temporarily turn off analysis mode while we undo
14651          * all the moves. Otherwise we get analysis output after each undo.
14652          */
14653         if (first.analysisSupport) {
14654           SendToProgram("exit\nforce\n", &first);
14655           first.analyzing = FALSE;
14656         }
14657     }
14658
14659     if (gameMode == IcsExamining && !pausing) {
14660         SendToICS(ics_prefix);
14661         SendToICS("backward 999999\n");
14662     } else {
14663         BackwardInner(backwardMostMove);
14664     }
14665
14666     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14667         /* we have fed all the moves, so reactivate analysis mode */
14668         SendToProgram("analyze\n", &first);
14669         first.analyzing = TRUE;
14670         /*first.maybeThinking = TRUE;*/
14671         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14672     }
14673 }
14674
14675 void
14676 ToNrEvent (int to)
14677 {
14678   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14679   if (to >= forwardMostMove) to = forwardMostMove;
14680   if (to <= backwardMostMove) to = backwardMostMove;
14681   if (to < currentMove) {
14682     BackwardInner(to);
14683   } else {
14684     ForwardInner(to);
14685   }
14686 }
14687
14688 void
14689 RevertEvent (Boolean annotate)
14690 {
14691     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14692         return;
14693     }
14694     if (gameMode != IcsExamining) {
14695         DisplayError(_("You are not examining a game"), 0);
14696         return;
14697     }
14698     if (pausing) {
14699         DisplayError(_("You can't revert while pausing"), 0);
14700         return;
14701     }
14702     SendToICS(ics_prefix);
14703     SendToICS("revert\n");
14704 }
14705
14706 void
14707 RetractMoveEvent ()
14708 {
14709     switch (gameMode) {
14710       case MachinePlaysWhite:
14711       case MachinePlaysBlack:
14712         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14713             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14714             return;
14715         }
14716         if (forwardMostMove < 2) return;
14717         currentMove = forwardMostMove = forwardMostMove - 2;
14718         whiteTimeRemaining = timeRemaining[0][currentMove];
14719         blackTimeRemaining = timeRemaining[1][currentMove];
14720         DisplayBothClocks();
14721         DisplayMove(currentMove - 1);
14722         ClearHighlights();/*!! could figure this out*/
14723         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14724         SendToProgram("remove\n", &first);
14725         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14726         break;
14727
14728       case BeginningOfGame:
14729       default:
14730         break;
14731
14732       case IcsPlayingWhite:
14733       case IcsPlayingBlack:
14734         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14735             SendToICS(ics_prefix);
14736             SendToICS("takeback 2\n");
14737         } else {
14738             SendToICS(ics_prefix);
14739             SendToICS("takeback 1\n");
14740         }
14741         break;
14742     }
14743 }
14744
14745 void
14746 MoveNowEvent ()
14747 {
14748     ChessProgramState *cps;
14749
14750     switch (gameMode) {
14751       case MachinePlaysWhite:
14752         if (!WhiteOnMove(forwardMostMove)) {
14753             DisplayError(_("It is your turn"), 0);
14754             return;
14755         }
14756         cps = &first;
14757         break;
14758       case MachinePlaysBlack:
14759         if (WhiteOnMove(forwardMostMove)) {
14760             DisplayError(_("It is your turn"), 0);
14761             return;
14762         }
14763         cps = &first;
14764         break;
14765       case TwoMachinesPlay:
14766         if (WhiteOnMove(forwardMostMove) ==
14767             (first.twoMachinesColor[0] == 'w')) {
14768             cps = &first;
14769         } else {
14770             cps = &second;
14771         }
14772         break;
14773       case BeginningOfGame:
14774       default:
14775         return;
14776     }
14777     SendToProgram("?\n", cps);
14778 }
14779
14780 void
14781 TruncateGameEvent ()
14782 {
14783     EditGameEvent();
14784     if (gameMode != EditGame) return;
14785     TruncateGame();
14786 }
14787
14788 void
14789 TruncateGame ()
14790 {
14791     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14792     if (forwardMostMove > currentMove) {
14793         if (gameInfo.resultDetails != NULL) {
14794             free(gameInfo.resultDetails);
14795             gameInfo.resultDetails = NULL;
14796             gameInfo.result = GameUnfinished;
14797         }
14798         forwardMostMove = currentMove;
14799         HistorySet(parseList, backwardMostMove, forwardMostMove,
14800                    currentMove-1);
14801     }
14802 }
14803
14804 void
14805 HintEvent ()
14806 {
14807     if (appData.noChessProgram) return;
14808     switch (gameMode) {
14809       case MachinePlaysWhite:
14810         if (WhiteOnMove(forwardMostMove)) {
14811             DisplayError(_("Wait until your turn"), 0);
14812             return;
14813         }
14814         break;
14815       case BeginningOfGame:
14816       case MachinePlaysBlack:
14817         if (!WhiteOnMove(forwardMostMove)) {
14818             DisplayError(_("Wait until your turn"), 0);
14819             return;
14820         }
14821         break;
14822       default:
14823         DisplayError(_("No hint available"), 0);
14824         return;
14825     }
14826     SendToProgram("hint\n", &first);
14827     hintRequested = TRUE;
14828 }
14829
14830 void
14831 BookEvent ()
14832 {
14833     if (appData.noChessProgram) return;
14834     switch (gameMode) {
14835       case MachinePlaysWhite:
14836         if (WhiteOnMove(forwardMostMove)) {
14837             DisplayError(_("Wait until your turn"), 0);
14838             return;
14839         }
14840         break;
14841       case BeginningOfGame:
14842       case MachinePlaysBlack:
14843         if (!WhiteOnMove(forwardMostMove)) {
14844             DisplayError(_("Wait until your turn"), 0);
14845             return;
14846         }
14847         break;
14848       case EditPosition:
14849         EditPositionDone(TRUE);
14850         break;
14851       case TwoMachinesPlay:
14852         return;
14853       default:
14854         break;
14855     }
14856     SendToProgram("bk\n", &first);
14857     bookOutput[0] = NULLCHAR;
14858     bookRequested = TRUE;
14859 }
14860
14861 void
14862 AboutGameEvent ()
14863 {
14864     char *tags = PGNTags(&gameInfo);
14865     TagsPopUp(tags, CmailMsg());
14866     free(tags);
14867 }
14868
14869 /* end button procedures */
14870
14871 void
14872 PrintPosition (FILE *fp, int move)
14873 {
14874     int i, j;
14875
14876     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14877         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14878             char c = PieceToChar(boards[move][i][j]);
14879             fputc(c == 'x' ? '.' : c, fp);
14880             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14881         }
14882     }
14883     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14884       fprintf(fp, "white to play\n");
14885     else
14886       fprintf(fp, "black to play\n");
14887 }
14888
14889 void
14890 PrintOpponents (FILE *fp)
14891 {
14892     if (gameInfo.white != NULL) {
14893         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14894     } else {
14895         fprintf(fp, "\n");
14896     }
14897 }
14898
14899 /* Find last component of program's own name, using some heuristics */
14900 void
14901 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14902 {
14903     char *p, *q, c;
14904     int local = (strcmp(host, "localhost") == 0);
14905     while (!local && (p = strchr(prog, ';')) != NULL) {
14906         p++;
14907         while (*p == ' ') p++;
14908         prog = p;
14909     }
14910     if (*prog == '"' || *prog == '\'') {
14911         q = strchr(prog + 1, *prog);
14912     } else {
14913         q = strchr(prog, ' ');
14914     }
14915     if (q == NULL) q = prog + strlen(prog);
14916     p = q;
14917     while (p >= prog && *p != '/' && *p != '\\') p--;
14918     p++;
14919     if(p == prog && *p == '"') p++;
14920     c = *q; *q = 0;
14921     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14922     memcpy(buf, p, q - p);
14923     buf[q - p] = NULLCHAR;
14924     if (!local) {
14925         strcat(buf, "@");
14926         strcat(buf, host);
14927     }
14928 }
14929
14930 char *
14931 TimeControlTagValue ()
14932 {
14933     char buf[MSG_SIZ];
14934     if (!appData.clockMode) {
14935       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14936     } else if (movesPerSession > 0) {
14937       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14938     } else if (timeIncrement == 0) {
14939       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14940     } else {
14941       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14942     }
14943     return StrSave(buf);
14944 }
14945
14946 void
14947 SetGameInfo ()
14948 {
14949     /* This routine is used only for certain modes */
14950     VariantClass v = gameInfo.variant;
14951     ChessMove r = GameUnfinished;
14952     char *p = NULL;
14953
14954     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14955         r = gameInfo.result;
14956         p = gameInfo.resultDetails;
14957         gameInfo.resultDetails = NULL;
14958     }
14959     ClearGameInfo(&gameInfo);
14960     gameInfo.variant = v;
14961
14962     switch (gameMode) {
14963       case MachinePlaysWhite:
14964         gameInfo.event = StrSave( appData.pgnEventHeader );
14965         gameInfo.site = StrSave(HostName());
14966         gameInfo.date = PGNDate();
14967         gameInfo.round = StrSave("-");
14968         gameInfo.white = StrSave(first.tidy);
14969         gameInfo.black = StrSave(UserName());
14970         gameInfo.timeControl = TimeControlTagValue();
14971         break;
14972
14973       case MachinePlaysBlack:
14974         gameInfo.event = StrSave( appData.pgnEventHeader );
14975         gameInfo.site = StrSave(HostName());
14976         gameInfo.date = PGNDate();
14977         gameInfo.round = StrSave("-");
14978         gameInfo.white = StrSave(UserName());
14979         gameInfo.black = StrSave(first.tidy);
14980         gameInfo.timeControl = TimeControlTagValue();
14981         break;
14982
14983       case TwoMachinesPlay:
14984         gameInfo.event = StrSave( appData.pgnEventHeader );
14985         gameInfo.site = StrSave(HostName());
14986         gameInfo.date = PGNDate();
14987         if (roundNr > 0) {
14988             char buf[MSG_SIZ];
14989             snprintf(buf, MSG_SIZ, "%d", roundNr);
14990             gameInfo.round = StrSave(buf);
14991         } else {
14992             gameInfo.round = StrSave("-");
14993         }
14994         if (first.twoMachinesColor[0] == 'w') {
14995             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14996             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14997         } else {
14998             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14999             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15000         }
15001         gameInfo.timeControl = TimeControlTagValue();
15002         break;
15003
15004       case EditGame:
15005         gameInfo.event = StrSave("Edited game");
15006         gameInfo.site = StrSave(HostName());
15007         gameInfo.date = PGNDate();
15008         gameInfo.round = StrSave("-");
15009         gameInfo.white = StrSave("-");
15010         gameInfo.black = StrSave("-");
15011         gameInfo.result = r;
15012         gameInfo.resultDetails = p;
15013         break;
15014
15015       case EditPosition:
15016         gameInfo.event = StrSave("Edited position");
15017         gameInfo.site = StrSave(HostName());
15018         gameInfo.date = PGNDate();
15019         gameInfo.round = StrSave("-");
15020         gameInfo.white = StrSave("-");
15021         gameInfo.black = StrSave("-");
15022         break;
15023
15024       case IcsPlayingWhite:
15025       case IcsPlayingBlack:
15026       case IcsObserving:
15027       case IcsExamining:
15028         break;
15029
15030       case PlayFromGameFile:
15031         gameInfo.event = StrSave("Game from non-PGN file");
15032         gameInfo.site = StrSave(HostName());
15033         gameInfo.date = PGNDate();
15034         gameInfo.round = StrSave("-");
15035         gameInfo.white = StrSave("?");
15036         gameInfo.black = StrSave("?");
15037         break;
15038
15039       default:
15040         break;
15041     }
15042 }
15043
15044 void
15045 ReplaceComment (int index, char *text)
15046 {
15047     int len;
15048     char *p;
15049     float score;
15050
15051     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15052        pvInfoList[index-1].depth == len &&
15053        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15054        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15055     while (*text == '\n') text++;
15056     len = strlen(text);
15057     while (len > 0 && text[len - 1] == '\n') len--;
15058
15059     if (commentList[index] != NULL)
15060       free(commentList[index]);
15061
15062     if (len == 0) {
15063         commentList[index] = NULL;
15064         return;
15065     }
15066   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15067       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15068       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15069     commentList[index] = (char *) malloc(len + 2);
15070     strncpy(commentList[index], text, len);
15071     commentList[index][len] = '\n';
15072     commentList[index][len + 1] = NULLCHAR;
15073   } else {
15074     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15075     char *p;
15076     commentList[index] = (char *) malloc(len + 7);
15077     safeStrCpy(commentList[index], "{\n", 3);
15078     safeStrCpy(commentList[index]+2, text, len+1);
15079     commentList[index][len+2] = NULLCHAR;
15080     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15081     strcat(commentList[index], "\n}\n");
15082   }
15083 }
15084
15085 void
15086 CrushCRs (char *text)
15087 {
15088   char *p = text;
15089   char *q = text;
15090   char ch;
15091
15092   do {
15093     ch = *p++;
15094     if (ch == '\r') continue;
15095     *q++ = ch;
15096   } while (ch != '\0');
15097 }
15098
15099 void
15100 AppendComment (int index, char *text, Boolean addBraces)
15101 /* addBraces  tells if we should add {} */
15102 {
15103     int oldlen, len;
15104     char *old;
15105
15106 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15107     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15108
15109     CrushCRs(text);
15110     while (*text == '\n') text++;
15111     len = strlen(text);
15112     while (len > 0 && text[len - 1] == '\n') len--;
15113     text[len] = NULLCHAR;
15114
15115     if (len == 0) return;
15116
15117     if (commentList[index] != NULL) {
15118       Boolean addClosingBrace = addBraces;
15119         old = commentList[index];
15120         oldlen = strlen(old);
15121         while(commentList[index][oldlen-1] ==  '\n')
15122           commentList[index][--oldlen] = NULLCHAR;
15123         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15124         safeStrCpy(commentList[index], old, oldlen + len + 6);
15125         free(old);
15126         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15127         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15128           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15129           while (*text == '\n') { text++; len--; }
15130           commentList[index][--oldlen] = NULLCHAR;
15131       }
15132         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15133         else          strcat(commentList[index], "\n");
15134         strcat(commentList[index], text);
15135         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15136         else          strcat(commentList[index], "\n");
15137     } else {
15138         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15139         if(addBraces)
15140           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15141         else commentList[index][0] = NULLCHAR;
15142         strcat(commentList[index], text);
15143         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15144         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15145     }
15146 }
15147
15148 static char *
15149 FindStr (char * text, char * sub_text)
15150 {
15151     char * result = strstr( text, sub_text );
15152
15153     if( result != NULL ) {
15154         result += strlen( sub_text );
15155     }
15156
15157     return result;
15158 }
15159
15160 /* [AS] Try to extract PV info from PGN comment */
15161 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15162 char *
15163 GetInfoFromComment (int index, char * text)
15164 {
15165     char * sep = text, *p;
15166
15167     if( text != NULL && index > 0 ) {
15168         int score = 0;
15169         int depth = 0;
15170         int time = -1, sec = 0, deci;
15171         char * s_eval = FindStr( text, "[%eval " );
15172         char * s_emt = FindStr( text, "[%emt " );
15173
15174         if( s_eval != NULL || s_emt != NULL ) {
15175             /* New style */
15176             char delim;
15177
15178             if( s_eval != NULL ) {
15179                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15180                     return text;
15181                 }
15182
15183                 if( delim != ']' ) {
15184                     return text;
15185                 }
15186             }
15187
15188             if( s_emt != NULL ) {
15189             }
15190                 return text;
15191         }
15192         else {
15193             /* We expect something like: [+|-]nnn.nn/dd */
15194             int score_lo = 0;
15195
15196             if(*text != '{') return text; // [HGM] braces: must be normal comment
15197
15198             sep = strchr( text, '/' );
15199             if( sep == NULL || sep < (text+4) ) {
15200                 return text;
15201             }
15202
15203             p = text;
15204             if(p[1] == '(') { // comment starts with PV
15205                p = strchr(p, ')'); // locate end of PV
15206                if(p == NULL || sep < p+5) return text;
15207                // at this point we have something like "{(.*) +0.23/6 ..."
15208                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15209                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15210                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15211             }
15212             time = -1; sec = -1; deci = -1;
15213             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15214                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15215                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15216                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15217                 return text;
15218             }
15219
15220             if( score_lo < 0 || score_lo >= 100 ) {
15221                 return text;
15222             }
15223
15224             if(sec >= 0) time = 600*time + 10*sec; else
15225             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15226
15227             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15228
15229             /* [HGM] PV time: now locate end of PV info */
15230             while( *++sep >= '0' && *sep <= '9'); // strip depth
15231             if(time >= 0)
15232             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15233             if(sec >= 0)
15234             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15235             if(deci >= 0)
15236             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15237             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15238         }
15239
15240         if( depth <= 0 ) {
15241             return text;
15242         }
15243
15244         if( time < 0 ) {
15245             time = -1;
15246         }
15247
15248         pvInfoList[index-1].depth = depth;
15249         pvInfoList[index-1].score = score;
15250         pvInfoList[index-1].time  = 10*time; // centi-sec
15251         if(*sep == '}') *sep = 0; else *--sep = '{';
15252         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15253     }
15254     return sep;
15255 }
15256
15257 void
15258 SendToProgram (char *message, ChessProgramState *cps)
15259 {
15260     int count, outCount, error;
15261     char buf[MSG_SIZ];
15262
15263     if (cps->pr == NoProc) return;
15264     Attention(cps);
15265
15266     if (appData.debugMode) {
15267         TimeMark now;
15268         GetTimeMark(&now);
15269         fprintf(debugFP, "%ld >%-6s: %s",
15270                 SubtractTimeMarks(&now, &programStartTime),
15271                 cps->which, message);
15272         if(serverFP)
15273             fprintf(serverFP, "%ld >%-6s: %s",
15274                 SubtractTimeMarks(&now, &programStartTime),
15275                 cps->which, message), fflush(serverFP);
15276     }
15277
15278     count = strlen(message);
15279     outCount = OutputToProcess(cps->pr, message, count, &error);
15280     if (outCount < count && !exiting
15281                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15282       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15283       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15284         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15285             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15286                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15287                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15288                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15289             } else {
15290                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15291                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15292                 gameInfo.result = res;
15293             }
15294             gameInfo.resultDetails = StrSave(buf);
15295         }
15296         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15297         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15298     }
15299 }
15300
15301 void
15302 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15303 {
15304     char *end_str;
15305     char buf[MSG_SIZ];
15306     ChessProgramState *cps = (ChessProgramState *)closure;
15307
15308     if (isr != cps->isr) return; /* Killed intentionally */
15309     if (count <= 0) {
15310         if (count == 0) {
15311             RemoveInputSource(cps->isr);
15312             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15313                     _(cps->which), cps->program);
15314             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15315             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15316                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15317                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15318                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15319                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15320                 } else {
15321                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15322                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15323                     gameInfo.result = res;
15324                 }
15325                 gameInfo.resultDetails = StrSave(buf);
15326             }
15327             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15328             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15329         } else {
15330             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15331                     _(cps->which), cps->program);
15332             RemoveInputSource(cps->isr);
15333
15334             /* [AS] Program is misbehaving badly... kill it */
15335             if( count == -2 ) {
15336                 DestroyChildProcess( cps->pr, 9 );
15337                 cps->pr = NoProc;
15338             }
15339
15340             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15341         }
15342         return;
15343     }
15344
15345     if ((end_str = strchr(message, '\r')) != NULL)
15346       *end_str = NULLCHAR;
15347     if ((end_str = strchr(message, '\n')) != NULL)
15348       *end_str = NULLCHAR;
15349
15350     if (appData.debugMode) {
15351         TimeMark now; int print = 1;
15352         char *quote = ""; char c; int i;
15353
15354         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15355                 char start = message[0];
15356                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15357                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15358                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15359                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15360                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15361                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15362                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15363                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15364                    sscanf(message, "hint: %c", &c)!=1 && 
15365                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15366                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15367                     print = (appData.engineComments >= 2);
15368                 }
15369                 message[0] = start; // restore original message
15370         }
15371         if(print) {
15372                 GetTimeMark(&now);
15373                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15374                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15375                         quote,
15376                         message);
15377                 if(serverFP)
15378                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15379                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15380                         quote,
15381                         message), fflush(serverFP);
15382         }
15383     }
15384
15385     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15386     if (appData.icsEngineAnalyze) {
15387         if (strstr(message, "whisper") != NULL ||
15388              strstr(message, "kibitz") != NULL ||
15389             strstr(message, "tellics") != NULL) return;
15390     }
15391
15392     HandleMachineMove(message, cps);
15393 }
15394
15395
15396 void
15397 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15398 {
15399     char buf[MSG_SIZ];
15400     int seconds;
15401
15402     if( timeControl_2 > 0 ) {
15403         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15404             tc = timeControl_2;
15405         }
15406     }
15407     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15408     inc /= cps->timeOdds;
15409     st  /= cps->timeOdds;
15410
15411     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15412
15413     if (st > 0) {
15414       /* Set exact time per move, normally using st command */
15415       if (cps->stKludge) {
15416         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15417         seconds = st % 60;
15418         if (seconds == 0) {
15419           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15420         } else {
15421           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15422         }
15423       } else {
15424         snprintf(buf, MSG_SIZ, "st %d\n", st);
15425       }
15426     } else {
15427       /* Set conventional or incremental time control, using level command */
15428       if (seconds == 0) {
15429         /* Note old gnuchess bug -- minutes:seconds used to not work.
15430            Fixed in later versions, but still avoid :seconds
15431            when seconds is 0. */
15432         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15433       } else {
15434         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15435                  seconds, inc/1000.);
15436       }
15437     }
15438     SendToProgram(buf, cps);
15439
15440     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15441     /* Orthogonally, limit search to given depth */
15442     if (sd > 0) {
15443       if (cps->sdKludge) {
15444         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15445       } else {
15446         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15447       }
15448       SendToProgram(buf, cps);
15449     }
15450
15451     if(cps->nps >= 0) { /* [HGM] nps */
15452         if(cps->supportsNPS == FALSE)
15453           cps->nps = -1; // don't use if engine explicitly says not supported!
15454         else {
15455           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15456           SendToProgram(buf, cps);
15457         }
15458     }
15459 }
15460
15461 ChessProgramState *
15462 WhitePlayer ()
15463 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15464 {
15465     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15466        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15467         return &second;
15468     return &first;
15469 }
15470
15471 void
15472 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15473 {
15474     char message[MSG_SIZ];
15475     long time, otime;
15476
15477     /* Note: this routine must be called when the clocks are stopped
15478        or when they have *just* been set or switched; otherwise
15479        it will be off by the time since the current tick started.
15480     */
15481     if (machineWhite) {
15482         time = whiteTimeRemaining / 10;
15483         otime = blackTimeRemaining / 10;
15484     } else {
15485         time = blackTimeRemaining / 10;
15486         otime = whiteTimeRemaining / 10;
15487     }
15488     /* [HGM] translate opponent's time by time-odds factor */
15489     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15490
15491     if (time <= 0) time = 1;
15492     if (otime <= 0) otime = 1;
15493
15494     snprintf(message, MSG_SIZ, "time %ld\n", time);
15495     SendToProgram(message, cps);
15496
15497     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15498     SendToProgram(message, cps);
15499 }
15500
15501 int
15502 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15503 {
15504   char buf[MSG_SIZ];
15505   int len = strlen(name);
15506   int val;
15507
15508   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15509     (*p) += len + 1;
15510     sscanf(*p, "%d", &val);
15511     *loc = (val != 0);
15512     while (**p && **p != ' ')
15513       (*p)++;
15514     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15515     SendToProgram(buf, cps);
15516     return TRUE;
15517   }
15518   return FALSE;
15519 }
15520
15521 int
15522 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15523 {
15524   char buf[MSG_SIZ];
15525   int len = strlen(name);
15526   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15527     (*p) += len + 1;
15528     sscanf(*p, "%d", loc);
15529     while (**p && **p != ' ') (*p)++;
15530     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15531     SendToProgram(buf, cps);
15532     return TRUE;
15533   }
15534   return FALSE;
15535 }
15536
15537 int
15538 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15539 {
15540   char buf[MSG_SIZ];
15541   int len = strlen(name);
15542   if (strncmp((*p), name, len) == 0
15543       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15544     (*p) += len + 2;
15545     sscanf(*p, "%[^\"]", loc);
15546     while (**p && **p != '\"') (*p)++;
15547     if (**p == '\"') (*p)++;
15548     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15549     SendToProgram(buf, cps);
15550     return TRUE;
15551   }
15552   return FALSE;
15553 }
15554
15555 int
15556 ParseOption (Option *opt, ChessProgramState *cps)
15557 // [HGM] options: process the string that defines an engine option, and determine
15558 // name, type, default value, and allowed value range
15559 {
15560         char *p, *q, buf[MSG_SIZ];
15561         int n, min = (-1)<<31, max = 1<<31, def;
15562
15563         if(p = strstr(opt->name, " -spin ")) {
15564             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15565             if(max < min) max = min; // enforce consistency
15566             if(def < min) def = min;
15567             if(def > max) def = max;
15568             opt->value = def;
15569             opt->min = min;
15570             opt->max = max;
15571             opt->type = Spin;
15572         } else if((p = strstr(opt->name, " -slider "))) {
15573             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15574             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15575             if(max < min) max = min; // enforce consistency
15576             if(def < min) def = min;
15577             if(def > max) def = max;
15578             opt->value = def;
15579             opt->min = min;
15580             opt->max = max;
15581             opt->type = Spin; // Slider;
15582         } else if((p = strstr(opt->name, " -string "))) {
15583             opt->textValue = p+9;
15584             opt->type = TextBox;
15585         } else if((p = strstr(opt->name, " -file "))) {
15586             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15587             opt->textValue = p+7;
15588             opt->type = FileName; // FileName;
15589         } else if((p = strstr(opt->name, " -path "))) {
15590             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15591             opt->textValue = p+7;
15592             opt->type = PathName; // PathName;
15593         } else if(p = strstr(opt->name, " -check ")) {
15594             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15595             opt->value = (def != 0);
15596             opt->type = CheckBox;
15597         } else if(p = strstr(opt->name, " -combo ")) {
15598             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15599             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15600             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15601             opt->value = n = 0;
15602             while(q = StrStr(q, " /// ")) {
15603                 n++; *q = 0;    // count choices, and null-terminate each of them
15604                 q += 5;
15605                 if(*q == '*') { // remember default, which is marked with * prefix
15606                     q++;
15607                     opt->value = n;
15608                 }
15609                 cps->comboList[cps->comboCnt++] = q;
15610             }
15611             cps->comboList[cps->comboCnt++] = NULL;
15612             opt->max = n + 1;
15613             opt->type = ComboBox;
15614         } else if(p = strstr(opt->name, " -button")) {
15615             opt->type = Button;
15616         } else if(p = strstr(opt->name, " -save")) {
15617             opt->type = SaveButton;
15618         } else return FALSE;
15619         *p = 0; // terminate option name
15620         // now look if the command-line options define a setting for this engine option.
15621         if(cps->optionSettings && cps->optionSettings[0])
15622             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15623         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15624           snprintf(buf, MSG_SIZ, "option %s", p);
15625                 if(p = strstr(buf, ",")) *p = 0;
15626                 if(q = strchr(buf, '=')) switch(opt->type) {
15627                     case ComboBox:
15628                         for(n=0; n<opt->max; n++)
15629                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15630                         break;
15631                     case TextBox:
15632                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15633                         break;
15634                     case Spin:
15635                     case CheckBox:
15636                         opt->value = atoi(q+1);
15637                     default:
15638                         break;
15639                 }
15640                 strcat(buf, "\n");
15641                 SendToProgram(buf, cps);
15642         }
15643         return TRUE;
15644 }
15645
15646 void
15647 FeatureDone (ChessProgramState *cps, int val)
15648 {
15649   DelayedEventCallback cb = GetDelayedEvent();
15650   if ((cb == InitBackEnd3 && cps == &first) ||
15651       (cb == SettingsMenuIfReady && cps == &second) ||
15652       (cb == LoadEngine) ||
15653       (cb == TwoMachinesEventIfReady)) {
15654     CancelDelayedEvent();
15655     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15656   }
15657   cps->initDone = val;
15658 }
15659
15660 /* Parse feature command from engine */
15661 void
15662 ParseFeatures (char *args, ChessProgramState *cps)
15663 {
15664   char *p = args;
15665   char *q;
15666   int val;
15667   char buf[MSG_SIZ];
15668
15669   for (;;) {
15670     while (*p == ' ') p++;
15671     if (*p == NULLCHAR) return;
15672
15673     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15674     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15675     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15676     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15677     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15678     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15679     if (BoolFeature(&p, "reuse", &val, cps)) {
15680       /* Engine can disable reuse, but can't enable it if user said no */
15681       if (!val) cps->reuse = FALSE;
15682       continue;
15683     }
15684     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15685     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15686       if (gameMode == TwoMachinesPlay) {
15687         DisplayTwoMachinesTitle();
15688       } else {
15689         DisplayTitle("");
15690       }
15691       continue;
15692     }
15693     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15694     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15695     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15696     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15697     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15698     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15699     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15700     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15701     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15702     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15703     if (IntFeature(&p, "done", &val, cps)) {
15704       FeatureDone(cps, val);
15705       continue;
15706     }
15707     /* Added by Tord: */
15708     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15709     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15710     /* End of additions by Tord */
15711
15712     /* [HGM] added features: */
15713     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15714     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15715     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15716     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15717     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15718     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15719     if (StringFeature(&p, "option", buf, cps)) {
15720         FREE(cps->option[cps->nrOptions].name);
15721         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15722         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15723         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15724           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15725             SendToProgram(buf, cps);
15726             continue;
15727         }
15728         if(cps->nrOptions >= MAX_OPTIONS) {
15729             cps->nrOptions--;
15730             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15731             DisplayError(buf, 0);
15732         }
15733         continue;
15734     }
15735     /* End of additions by HGM */
15736
15737     /* unknown feature: complain and skip */
15738     q = p;
15739     while (*q && *q != '=') q++;
15740     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15741     SendToProgram(buf, cps);
15742     p = q;
15743     if (*p == '=') {
15744       p++;
15745       if (*p == '\"') {
15746         p++;
15747         while (*p && *p != '\"') p++;
15748         if (*p == '\"') p++;
15749       } else {
15750         while (*p && *p != ' ') p++;
15751       }
15752     }
15753   }
15754
15755 }
15756
15757 void
15758 PeriodicUpdatesEvent (int newState)
15759 {
15760     if (newState == appData.periodicUpdates)
15761       return;
15762
15763     appData.periodicUpdates=newState;
15764
15765     /* Display type changes, so update it now */
15766 //    DisplayAnalysis();
15767
15768     /* Get the ball rolling again... */
15769     if (newState) {
15770         AnalysisPeriodicEvent(1);
15771         StartAnalysisClock();
15772     }
15773 }
15774
15775 void
15776 PonderNextMoveEvent (int newState)
15777 {
15778     if (newState == appData.ponderNextMove) return;
15779     if (gameMode == EditPosition) EditPositionDone(TRUE);
15780     if (newState) {
15781         SendToProgram("hard\n", &first);
15782         if (gameMode == TwoMachinesPlay) {
15783             SendToProgram("hard\n", &second);
15784         }
15785     } else {
15786         SendToProgram("easy\n", &first);
15787         thinkOutput[0] = NULLCHAR;
15788         if (gameMode == TwoMachinesPlay) {
15789             SendToProgram("easy\n", &second);
15790         }
15791     }
15792     appData.ponderNextMove = newState;
15793 }
15794
15795 void
15796 NewSettingEvent (int option, int *feature, char *command, int value)
15797 {
15798     char buf[MSG_SIZ];
15799
15800     if (gameMode == EditPosition) EditPositionDone(TRUE);
15801     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15802     if(feature == NULL || *feature) SendToProgram(buf, &first);
15803     if (gameMode == TwoMachinesPlay) {
15804         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15805     }
15806 }
15807
15808 void
15809 ShowThinkingEvent ()
15810 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15811 {
15812     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15813     int newState = appData.showThinking
15814         // [HGM] thinking: other features now need thinking output as well
15815         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15816
15817     if (oldState == newState) return;
15818     oldState = newState;
15819     if (gameMode == EditPosition) EditPositionDone(TRUE);
15820     if (oldState) {
15821         SendToProgram("post\n", &first);
15822         if (gameMode == TwoMachinesPlay) {
15823             SendToProgram("post\n", &second);
15824         }
15825     } else {
15826         SendToProgram("nopost\n", &first);
15827         thinkOutput[0] = NULLCHAR;
15828         if (gameMode == TwoMachinesPlay) {
15829             SendToProgram("nopost\n", &second);
15830         }
15831     }
15832 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15833 }
15834
15835 void
15836 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15837 {
15838   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15839   if (pr == NoProc) return;
15840   AskQuestion(title, question, replyPrefix, pr);
15841 }
15842
15843 void
15844 TypeInEvent (char firstChar)
15845 {
15846     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15847         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15848         gameMode == AnalyzeMode || gameMode == EditGame || 
15849         gameMode == EditPosition || gameMode == IcsExamining ||
15850         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15851         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15852                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15853                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15854         gameMode == Training) PopUpMoveDialog(firstChar);
15855 }
15856
15857 void
15858 TypeInDoneEvent (char *move)
15859 {
15860         Board board;
15861         int n, fromX, fromY, toX, toY;
15862         char promoChar;
15863         ChessMove moveType;
15864
15865         // [HGM] FENedit
15866         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15867                 EditPositionPasteFEN(move);
15868                 return;
15869         }
15870         // [HGM] movenum: allow move number to be typed in any mode
15871         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15872           ToNrEvent(2*n-1);
15873           return;
15874         }
15875         // undocumented kludge: allow command-line option to be typed in!
15876         // (potentially fatal, and does not implement the effect of the option.)
15877         // should only be used for options that are values on which future decisions will be made,
15878         // and definitely not on options that would be used during initialization.
15879         if(strstr(move, "!!! -") == move) {
15880             ParseArgsFromString(move+4);
15881             return;
15882         }
15883
15884       if (gameMode != EditGame && currentMove != forwardMostMove && 
15885         gameMode != Training) {
15886         DisplayMoveError(_("Displayed move is not current"));
15887       } else {
15888         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15889           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15890         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15891         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15892           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15893           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15894         } else {
15895           DisplayMoveError(_("Could not parse move"));
15896         }
15897       }
15898 }
15899
15900 void
15901 DisplayMove (int moveNumber)
15902 {
15903     char message[MSG_SIZ];
15904     char res[MSG_SIZ];
15905     char cpThinkOutput[MSG_SIZ];
15906
15907     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15908
15909     if (moveNumber == forwardMostMove - 1 ||
15910         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15911
15912         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15913
15914         if (strchr(cpThinkOutput, '\n')) {
15915             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15916         }
15917     } else {
15918         *cpThinkOutput = NULLCHAR;
15919     }
15920
15921     /* [AS] Hide thinking from human user */
15922     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15923         *cpThinkOutput = NULLCHAR;
15924         if( thinkOutput[0] != NULLCHAR ) {
15925             int i;
15926
15927             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15928                 cpThinkOutput[i] = '.';
15929             }
15930             cpThinkOutput[i] = NULLCHAR;
15931             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15932         }
15933     }
15934
15935     if (moveNumber == forwardMostMove - 1 &&
15936         gameInfo.resultDetails != NULL) {
15937         if (gameInfo.resultDetails[0] == NULLCHAR) {
15938           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15939         } else {
15940           snprintf(res, MSG_SIZ, " {%s} %s",
15941                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15942         }
15943     } else {
15944         res[0] = NULLCHAR;
15945     }
15946
15947     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15948         DisplayMessage(res, cpThinkOutput);
15949     } else {
15950       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15951                 WhiteOnMove(moveNumber) ? " " : ".. ",
15952                 parseList[moveNumber], res);
15953         DisplayMessage(message, cpThinkOutput);
15954     }
15955 }
15956
15957 void
15958 DisplayComment (int moveNumber, char *text)
15959 {
15960     char title[MSG_SIZ];
15961
15962     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15963       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15964     } else {
15965       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15966               WhiteOnMove(moveNumber) ? " " : ".. ",
15967               parseList[moveNumber]);
15968     }
15969     if (text != NULL && (appData.autoDisplayComment || commentUp))
15970         CommentPopUp(title, text);
15971 }
15972
15973 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15974  * might be busy thinking or pondering.  It can be omitted if your
15975  * gnuchess is configured to stop thinking immediately on any user
15976  * input.  However, that gnuchess feature depends on the FIONREAD
15977  * ioctl, which does not work properly on some flavors of Unix.
15978  */
15979 void
15980 Attention (ChessProgramState *cps)
15981 {
15982 #if ATTENTION
15983     if (!cps->useSigint) return;
15984     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15985     switch (gameMode) {
15986       case MachinePlaysWhite:
15987       case MachinePlaysBlack:
15988       case TwoMachinesPlay:
15989       case IcsPlayingWhite:
15990       case IcsPlayingBlack:
15991       case AnalyzeMode:
15992       case AnalyzeFile:
15993         /* Skip if we know it isn't thinking */
15994         if (!cps->maybeThinking) return;
15995         if (appData.debugMode)
15996           fprintf(debugFP, "Interrupting %s\n", cps->which);
15997         InterruptChildProcess(cps->pr);
15998         cps->maybeThinking = FALSE;
15999         break;
16000       default:
16001         break;
16002     }
16003 #endif /*ATTENTION*/
16004 }
16005
16006 int
16007 CheckFlags ()
16008 {
16009     if (whiteTimeRemaining <= 0) {
16010         if (!whiteFlag) {
16011             whiteFlag = TRUE;
16012             if (appData.icsActive) {
16013                 if (appData.autoCallFlag &&
16014                     gameMode == IcsPlayingBlack && !blackFlag) {
16015                   SendToICS(ics_prefix);
16016                   SendToICS("flag\n");
16017                 }
16018             } else {
16019                 if (blackFlag) {
16020                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16021                 } else {
16022                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16023                     if (appData.autoCallFlag) {
16024                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16025                         return TRUE;
16026                     }
16027                 }
16028             }
16029         }
16030     }
16031     if (blackTimeRemaining <= 0) {
16032         if (!blackFlag) {
16033             blackFlag = TRUE;
16034             if (appData.icsActive) {
16035                 if (appData.autoCallFlag &&
16036                     gameMode == IcsPlayingWhite && !whiteFlag) {
16037                   SendToICS(ics_prefix);
16038                   SendToICS("flag\n");
16039                 }
16040             } else {
16041                 if (whiteFlag) {
16042                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16043                 } else {
16044                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16045                     if (appData.autoCallFlag) {
16046                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16047                         return TRUE;
16048                     }
16049                 }
16050             }
16051         }
16052     }
16053     return FALSE;
16054 }
16055
16056 void
16057 CheckTimeControl ()
16058 {
16059     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16060         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16061
16062     /*
16063      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16064      */
16065     if ( !WhiteOnMove(forwardMostMove) ) {
16066         /* White made time control */
16067         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16068         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16069         /* [HGM] time odds: correct new time quota for time odds! */
16070                                             / WhitePlayer()->timeOdds;
16071         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16072     } else {
16073         lastBlack -= blackTimeRemaining;
16074         /* Black made time control */
16075         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16076                                             / WhitePlayer()->other->timeOdds;
16077         lastWhite = whiteTimeRemaining;
16078     }
16079 }
16080
16081 void
16082 DisplayBothClocks ()
16083 {
16084     int wom = gameMode == EditPosition ?
16085       !blackPlaysFirst : WhiteOnMove(currentMove);
16086     DisplayWhiteClock(whiteTimeRemaining, wom);
16087     DisplayBlackClock(blackTimeRemaining, !wom);
16088 }
16089
16090
16091 /* Timekeeping seems to be a portability nightmare.  I think everyone
16092    has ftime(), but I'm really not sure, so I'm including some ifdefs
16093    to use other calls if you don't.  Clocks will be less accurate if
16094    you have neither ftime nor gettimeofday.
16095 */
16096
16097 /* VS 2008 requires the #include outside of the function */
16098 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16099 #include <sys/timeb.h>
16100 #endif
16101
16102 /* Get the current time as a TimeMark */
16103 void
16104 GetTimeMark (TimeMark *tm)
16105 {
16106 #if HAVE_GETTIMEOFDAY
16107
16108     struct timeval timeVal;
16109     struct timezone timeZone;
16110
16111     gettimeofday(&timeVal, &timeZone);
16112     tm->sec = (long) timeVal.tv_sec;
16113     tm->ms = (int) (timeVal.tv_usec / 1000L);
16114
16115 #else /*!HAVE_GETTIMEOFDAY*/
16116 #if HAVE_FTIME
16117
16118 // include <sys/timeb.h> / moved to just above start of function
16119     struct timeb timeB;
16120
16121     ftime(&timeB);
16122     tm->sec = (long) timeB.time;
16123     tm->ms = (int) timeB.millitm;
16124
16125 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16126     tm->sec = (long) time(NULL);
16127     tm->ms = 0;
16128 #endif
16129 #endif
16130 }
16131
16132 /* Return the difference in milliseconds between two
16133    time marks.  We assume the difference will fit in a long!
16134 */
16135 long
16136 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16137 {
16138     return 1000L*(tm2->sec - tm1->sec) +
16139            (long) (tm2->ms - tm1->ms);
16140 }
16141
16142
16143 /*
16144  * Code to manage the game clocks.
16145  *
16146  * In tournament play, black starts the clock and then white makes a move.
16147  * We give the human user a slight advantage if he is playing white---the
16148  * clocks don't run until he makes his first move, so it takes zero time.
16149  * Also, we don't account for network lag, so we could get out of sync
16150  * with GNU Chess's clock -- but then, referees are always right.
16151  */
16152
16153 static TimeMark tickStartTM;
16154 static long intendedTickLength;
16155
16156 long
16157 NextTickLength (long timeRemaining)
16158 {
16159     long nominalTickLength, nextTickLength;
16160
16161     if (timeRemaining > 0L && timeRemaining <= 10000L)
16162       nominalTickLength = 100L;
16163     else
16164       nominalTickLength = 1000L;
16165     nextTickLength = timeRemaining % nominalTickLength;
16166     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16167
16168     return nextTickLength;
16169 }
16170
16171 /* Adjust clock one minute up or down */
16172 void
16173 AdjustClock (Boolean which, int dir)
16174 {
16175     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16176     if(which) blackTimeRemaining += 60000*dir;
16177     else      whiteTimeRemaining += 60000*dir;
16178     DisplayBothClocks();
16179     adjustedClock = TRUE;
16180 }
16181
16182 /* Stop clocks and reset to a fresh time control */
16183 void
16184 ResetClocks ()
16185 {
16186     (void) StopClockTimer();
16187     if (appData.icsActive) {
16188         whiteTimeRemaining = blackTimeRemaining = 0;
16189     } else if (searchTime) {
16190         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16191         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16192     } else { /* [HGM] correct new time quote for time odds */
16193         whiteTC = blackTC = fullTimeControlString;
16194         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16195         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16196     }
16197     if (whiteFlag || blackFlag) {
16198         DisplayTitle("");
16199         whiteFlag = blackFlag = FALSE;
16200     }
16201     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16202     DisplayBothClocks();
16203     adjustedClock = FALSE;
16204 }
16205
16206 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16207
16208 /* Decrement running clock by amount of time that has passed */
16209 void
16210 DecrementClocks ()
16211 {
16212     long timeRemaining;
16213     long lastTickLength, fudge;
16214     TimeMark now;
16215
16216     if (!appData.clockMode) return;
16217     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16218
16219     GetTimeMark(&now);
16220
16221     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16222
16223     /* Fudge if we woke up a little too soon */
16224     fudge = intendedTickLength - lastTickLength;
16225     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16226
16227     if (WhiteOnMove(forwardMostMove)) {
16228         if(whiteNPS >= 0) lastTickLength = 0;
16229         timeRemaining = whiteTimeRemaining -= lastTickLength;
16230         if(timeRemaining < 0 && !appData.icsActive) {
16231             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16232             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16233                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16234                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16235             }
16236         }
16237         DisplayWhiteClock(whiteTimeRemaining - fudge,
16238                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16239     } else {
16240         if(blackNPS >= 0) lastTickLength = 0;
16241         timeRemaining = blackTimeRemaining -= lastTickLength;
16242         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16243             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16244             if(suddenDeath) {
16245                 blackStartMove = forwardMostMove;
16246                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16247             }
16248         }
16249         DisplayBlackClock(blackTimeRemaining - fudge,
16250                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16251     }
16252     if (CheckFlags()) return;
16253
16254     if(twoBoards) { // count down secondary board's clocks as well
16255         activePartnerTime -= lastTickLength;
16256         partnerUp = 1;
16257         if(activePartner == 'W')
16258             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16259         else
16260             DisplayBlackClock(activePartnerTime, TRUE);
16261         partnerUp = 0;
16262     }
16263
16264     tickStartTM = now;
16265     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16266     StartClockTimer(intendedTickLength);
16267
16268     /* if the time remaining has fallen below the alarm threshold, sound the
16269      * alarm. if the alarm has sounded and (due to a takeback or time control
16270      * with increment) the time remaining has increased to a level above the
16271      * threshold, reset the alarm so it can sound again.
16272      */
16273
16274     if (appData.icsActive && appData.icsAlarm) {
16275
16276         /* make sure we are dealing with the user's clock */
16277         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16278                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16279            )) return;
16280
16281         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16282             alarmSounded = FALSE;
16283         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16284             PlayAlarmSound();
16285             alarmSounded = TRUE;
16286         }
16287     }
16288 }
16289
16290
16291 /* A player has just moved, so stop the previously running
16292    clock and (if in clock mode) start the other one.
16293    We redisplay both clocks in case we're in ICS mode, because
16294    ICS gives us an update to both clocks after every move.
16295    Note that this routine is called *after* forwardMostMove
16296    is updated, so the last fractional tick must be subtracted
16297    from the color that is *not* on move now.
16298 */
16299 void
16300 SwitchClocks (int newMoveNr)
16301 {
16302     long lastTickLength;
16303     TimeMark now;
16304     int flagged = FALSE;
16305
16306     GetTimeMark(&now);
16307
16308     if (StopClockTimer() && appData.clockMode) {
16309         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16310         if (!WhiteOnMove(forwardMostMove)) {
16311             if(blackNPS >= 0) lastTickLength = 0;
16312             blackTimeRemaining -= lastTickLength;
16313            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16314 //         if(pvInfoList[forwardMostMove].time == -1)
16315                  pvInfoList[forwardMostMove].time =               // use GUI time
16316                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16317         } else {
16318            if(whiteNPS >= 0) lastTickLength = 0;
16319            whiteTimeRemaining -= lastTickLength;
16320            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16321 //         if(pvInfoList[forwardMostMove].time == -1)
16322                  pvInfoList[forwardMostMove].time =
16323                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16324         }
16325         flagged = CheckFlags();
16326     }
16327     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16328     CheckTimeControl();
16329
16330     if (flagged || !appData.clockMode) return;
16331
16332     switch (gameMode) {
16333       case MachinePlaysBlack:
16334       case MachinePlaysWhite:
16335       case BeginningOfGame:
16336         if (pausing) return;
16337         break;
16338
16339       case EditGame:
16340       case PlayFromGameFile:
16341       case IcsExamining:
16342         return;
16343
16344       default:
16345         break;
16346     }
16347
16348     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16349         if(WhiteOnMove(forwardMostMove))
16350              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16351         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16352     }
16353
16354     tickStartTM = now;
16355     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16356       whiteTimeRemaining : blackTimeRemaining);
16357     StartClockTimer(intendedTickLength);
16358 }
16359
16360
16361 /* Stop both clocks */
16362 void
16363 StopClocks ()
16364 {
16365     long lastTickLength;
16366     TimeMark now;
16367
16368     if (!StopClockTimer()) return;
16369     if (!appData.clockMode) return;
16370
16371     GetTimeMark(&now);
16372
16373     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16374     if (WhiteOnMove(forwardMostMove)) {
16375         if(whiteNPS >= 0) lastTickLength = 0;
16376         whiteTimeRemaining -= lastTickLength;
16377         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16378     } else {
16379         if(blackNPS >= 0) lastTickLength = 0;
16380         blackTimeRemaining -= lastTickLength;
16381         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16382     }
16383     CheckFlags();
16384 }
16385
16386 /* Start clock of player on move.  Time may have been reset, so
16387    if clock is already running, stop and restart it. */
16388 void
16389 StartClocks ()
16390 {
16391     (void) StopClockTimer(); /* in case it was running already */
16392     DisplayBothClocks();
16393     if (CheckFlags()) return;
16394
16395     if (!appData.clockMode) return;
16396     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16397
16398     GetTimeMark(&tickStartTM);
16399     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16400       whiteTimeRemaining : blackTimeRemaining);
16401
16402    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16403     whiteNPS = blackNPS = -1;
16404     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16405        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16406         whiteNPS = first.nps;
16407     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16408        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16409         blackNPS = first.nps;
16410     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16411         whiteNPS = second.nps;
16412     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16413         blackNPS = second.nps;
16414     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16415
16416     StartClockTimer(intendedTickLength);
16417 }
16418
16419 char *
16420 TimeString (long ms)
16421 {
16422     long second, minute, hour, day;
16423     char *sign = "";
16424     static char buf[32];
16425
16426     if (ms > 0 && ms <= 9900) {
16427       /* convert milliseconds to tenths, rounding up */
16428       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16429
16430       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16431       return buf;
16432     }
16433
16434     /* convert milliseconds to seconds, rounding up */
16435     /* use floating point to avoid strangeness of integer division
16436        with negative dividends on many machines */
16437     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16438
16439     if (second < 0) {
16440         sign = "-";
16441         second = -second;
16442     }
16443
16444     day = second / (60 * 60 * 24);
16445     second = second % (60 * 60 * 24);
16446     hour = second / (60 * 60);
16447     second = second % (60 * 60);
16448     minute = second / 60;
16449     second = second % 60;
16450
16451     if (day > 0)
16452       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16453               sign, day, hour, minute, second);
16454     else if (hour > 0)
16455       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16456     else
16457       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16458
16459     return buf;
16460 }
16461
16462
16463 /*
16464  * This is necessary because some C libraries aren't ANSI C compliant yet.
16465  */
16466 char *
16467 StrStr (char *string, char *match)
16468 {
16469     int i, length;
16470
16471     length = strlen(match);
16472
16473     for (i = strlen(string) - length; i >= 0; i--, string++)
16474       if (!strncmp(match, string, length))
16475         return string;
16476
16477     return NULL;
16478 }
16479
16480 char *
16481 StrCaseStr (char *string, char *match)
16482 {
16483     int i, j, length;
16484
16485     length = strlen(match);
16486
16487     for (i = strlen(string) - length; i >= 0; i--, string++) {
16488         for (j = 0; j < length; j++) {
16489             if (ToLower(match[j]) != ToLower(string[j]))
16490               break;
16491         }
16492         if (j == length) return string;
16493     }
16494
16495     return NULL;
16496 }
16497
16498 #ifndef _amigados
16499 int
16500 StrCaseCmp (char *s1, char *s2)
16501 {
16502     char c1, c2;
16503
16504     for (;;) {
16505         c1 = ToLower(*s1++);
16506         c2 = ToLower(*s2++);
16507         if (c1 > c2) return 1;
16508         if (c1 < c2) return -1;
16509         if (c1 == NULLCHAR) return 0;
16510     }
16511 }
16512
16513
16514 int
16515 ToLower (int c)
16516 {
16517     return isupper(c) ? tolower(c) : c;
16518 }
16519
16520
16521 int
16522 ToUpper (int c)
16523 {
16524     return islower(c) ? toupper(c) : c;
16525 }
16526 #endif /* !_amigados    */
16527
16528 char *
16529 StrSave (char *s)
16530 {
16531   char *ret;
16532
16533   if ((ret = (char *) malloc(strlen(s) + 1)))
16534     {
16535       safeStrCpy(ret, s, strlen(s)+1);
16536     }
16537   return ret;
16538 }
16539
16540 char *
16541 StrSavePtr (char *s, char **savePtr)
16542 {
16543     if (*savePtr) {
16544         free(*savePtr);
16545     }
16546     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16547       safeStrCpy(*savePtr, s, strlen(s)+1);
16548     }
16549     return(*savePtr);
16550 }
16551
16552 char *
16553 PGNDate ()
16554 {
16555     time_t clock;
16556     struct tm *tm;
16557     char buf[MSG_SIZ];
16558
16559     clock = time((time_t *)NULL);
16560     tm = localtime(&clock);
16561     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16562             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16563     return StrSave(buf);
16564 }
16565
16566
16567 char *
16568 PositionToFEN (int move, char *overrideCastling)
16569 {
16570     int i, j, fromX, fromY, toX, toY;
16571     int whiteToPlay;
16572     char buf[MSG_SIZ];
16573     char *p, *q;
16574     int emptycount;
16575     ChessSquare piece;
16576
16577     whiteToPlay = (gameMode == EditPosition) ?
16578       !blackPlaysFirst : (move % 2 == 0);
16579     p = buf;
16580
16581     /* Piece placement data */
16582     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16583         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16584         emptycount = 0;
16585         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16586             if (boards[move][i][j] == EmptySquare) {
16587                 emptycount++;
16588             } else { ChessSquare piece = boards[move][i][j];
16589                 if (emptycount > 0) {
16590                     if(emptycount<10) /* [HGM] can be >= 10 */
16591                         *p++ = '0' + emptycount;
16592                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16593                     emptycount = 0;
16594                 }
16595                 if(PieceToChar(piece) == '+') {
16596                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16597                     *p++ = '+';
16598                     piece = (ChessSquare)(DEMOTED piece);
16599                 }
16600                 *p++ = PieceToChar(piece);
16601                 if(p[-1] == '~') {
16602                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16603                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16604                     *p++ = '~';
16605                 }
16606             }
16607         }
16608         if (emptycount > 0) {
16609             if(emptycount<10) /* [HGM] can be >= 10 */
16610                 *p++ = '0' + emptycount;
16611             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16612             emptycount = 0;
16613         }
16614         *p++ = '/';
16615     }
16616     *(p - 1) = ' ';
16617
16618     /* [HGM] print Crazyhouse or Shogi holdings */
16619     if( gameInfo.holdingsWidth ) {
16620         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16621         q = p;
16622         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16623             piece = boards[move][i][BOARD_WIDTH-1];
16624             if( piece != EmptySquare )
16625               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16626                   *p++ = PieceToChar(piece);
16627         }
16628         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16629             piece = boards[move][BOARD_HEIGHT-i-1][0];
16630             if( piece != EmptySquare )
16631               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16632                   *p++ = PieceToChar(piece);
16633         }
16634
16635         if( q == p ) *p++ = '-';
16636         *p++ = ']';
16637         *p++ = ' ';
16638     }
16639
16640     /* Active color */
16641     *p++ = whiteToPlay ? 'w' : 'b';
16642     *p++ = ' ';
16643
16644   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16645     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16646   } else {
16647   if(nrCastlingRights) {
16648      q = p;
16649      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16650        /* [HGM] write directly from rights */
16651            if(boards[move][CASTLING][2] != NoRights &&
16652               boards[move][CASTLING][0] != NoRights   )
16653                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16654            if(boards[move][CASTLING][2] != NoRights &&
16655               boards[move][CASTLING][1] != NoRights   )
16656                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16657            if(boards[move][CASTLING][5] != NoRights &&
16658               boards[move][CASTLING][3] != NoRights   )
16659                 *p++ = boards[move][CASTLING][3] + AAA;
16660            if(boards[move][CASTLING][5] != NoRights &&
16661               boards[move][CASTLING][4] != NoRights   )
16662                 *p++ = boards[move][CASTLING][4] + AAA;
16663      } else {
16664
16665         /* [HGM] write true castling rights */
16666         if( nrCastlingRights == 6 ) {
16667             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16668                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16669             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16670                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16671             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16672                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16673             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16674                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16675         }
16676      }
16677      if (q == p) *p++ = '-'; /* No castling rights */
16678      *p++ = ' ';
16679   }
16680
16681   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16682      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16683     /* En passant target square */
16684     if (move > backwardMostMove) {
16685         fromX = moveList[move - 1][0] - AAA;
16686         fromY = moveList[move - 1][1] - ONE;
16687         toX = moveList[move - 1][2] - AAA;
16688         toY = moveList[move - 1][3] - ONE;
16689         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16690             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16691             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16692             fromX == toX) {
16693             /* 2-square pawn move just happened */
16694             *p++ = toX + AAA;
16695             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16696         } else {
16697             *p++ = '-';
16698         }
16699     } else if(move == backwardMostMove) {
16700         // [HGM] perhaps we should always do it like this, and forget the above?
16701         if((signed char)boards[move][EP_STATUS] >= 0) {
16702             *p++ = boards[move][EP_STATUS] + AAA;
16703             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16704         } else {
16705             *p++ = '-';
16706         }
16707     } else {
16708         *p++ = '-';
16709     }
16710     *p++ = ' ';
16711   }
16712   }
16713
16714     /* [HGM] find reversible plies */
16715     {   int i = 0, j=move;
16716
16717         if (appData.debugMode) { int k;
16718             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16719             for(k=backwardMostMove; k<=forwardMostMove; k++)
16720                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16721
16722         }
16723
16724         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16725         if( j == backwardMostMove ) i += initialRulePlies;
16726         sprintf(p, "%d ", i);
16727         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16728     }
16729     /* Fullmove number */
16730     sprintf(p, "%d", (move / 2) + 1);
16731
16732     return StrSave(buf);
16733 }
16734
16735 Boolean
16736 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16737 {
16738     int i, j;
16739     char *p, c;
16740     int emptycount;
16741     ChessSquare piece;
16742
16743     p = fen;
16744
16745     /* [HGM] by default clear Crazyhouse holdings, if present */
16746     if(gameInfo.holdingsWidth) {
16747        for(i=0; i<BOARD_HEIGHT; i++) {
16748            board[i][0]             = EmptySquare; /* black holdings */
16749            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16750            board[i][1]             = (ChessSquare) 0; /* black counts */
16751            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16752        }
16753     }
16754
16755     /* Piece placement data */
16756     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16757         j = 0;
16758         for (;;) {
16759             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16760                 if (*p == '/') p++;
16761                 emptycount = gameInfo.boardWidth - j;
16762                 while (emptycount--)
16763                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16764                 break;
16765 #if(BOARD_FILES >= 10)
16766             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16767                 p++; emptycount=10;
16768                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16769                 while (emptycount--)
16770                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16771 #endif
16772             } else if (isdigit(*p)) {
16773                 emptycount = *p++ - '0';
16774                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16775                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16776                 while (emptycount--)
16777                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16778             } else if (*p == '+' || isalpha(*p)) {
16779                 if (j >= gameInfo.boardWidth) return FALSE;
16780                 if(*p=='+') {
16781                     piece = CharToPiece(*++p);
16782                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16783                     piece = (ChessSquare) (PROMOTED piece ); p++;
16784                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16785                 } else piece = CharToPiece(*p++);
16786
16787                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16788                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16789                     piece = (ChessSquare) (PROMOTED piece);
16790                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16791                     p++;
16792                 }
16793                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16794             } else {
16795                 return FALSE;
16796             }
16797         }
16798     }
16799     while (*p == '/' || *p == ' ') p++;
16800
16801     /* [HGM] look for Crazyhouse holdings here */
16802     while(*p==' ') p++;
16803     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16804         if(*p == '[') p++;
16805         if(*p == '-' ) p++; /* empty holdings */ else {
16806             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16807             /* if we would allow FEN reading to set board size, we would   */
16808             /* have to add holdings and shift the board read so far here   */
16809             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16810                 p++;
16811                 if((int) piece >= (int) BlackPawn ) {
16812                     i = (int)piece - (int)BlackPawn;
16813                     i = PieceToNumber((ChessSquare)i);
16814                     if( i >= gameInfo.holdingsSize ) return FALSE;
16815                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16816                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16817                 } else {
16818                     i = (int)piece - (int)WhitePawn;
16819                     i = PieceToNumber((ChessSquare)i);
16820                     if( i >= gameInfo.holdingsSize ) return FALSE;
16821                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16822                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16823                 }
16824             }
16825         }
16826         if(*p == ']') p++;
16827     }
16828
16829     while(*p == ' ') p++;
16830
16831     /* Active color */
16832     c = *p++;
16833     if(appData.colorNickNames) {
16834       if( c == appData.colorNickNames[0] ) c = 'w'; else
16835       if( c == appData.colorNickNames[1] ) c = 'b';
16836     }
16837     switch (c) {
16838       case 'w':
16839         *blackPlaysFirst = FALSE;
16840         break;
16841       case 'b':
16842         *blackPlaysFirst = TRUE;
16843         break;
16844       default:
16845         return FALSE;
16846     }
16847
16848     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16849     /* return the extra info in global variiables             */
16850
16851     /* set defaults in case FEN is incomplete */
16852     board[EP_STATUS] = EP_UNKNOWN;
16853     for(i=0; i<nrCastlingRights; i++ ) {
16854         board[CASTLING][i] =
16855             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16856     }   /* assume possible unless obviously impossible */
16857     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16858     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16859     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16860                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16861     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16862     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16863     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16864                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16865     FENrulePlies = 0;
16866
16867     while(*p==' ') p++;
16868     if(nrCastlingRights) {
16869       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16870           /* castling indicator present, so default becomes no castlings */
16871           for(i=0; i<nrCastlingRights; i++ ) {
16872                  board[CASTLING][i] = NoRights;
16873           }
16874       }
16875       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16876              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16877              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16878              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16879         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16880
16881         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16882             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16883             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16884         }
16885         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16886             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16887         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16888                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16889         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16890                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16891         switch(c) {
16892           case'K':
16893               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16894               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16895               board[CASTLING][2] = whiteKingFile;
16896               break;
16897           case'Q':
16898               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16899               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16900               board[CASTLING][2] = whiteKingFile;
16901               break;
16902           case'k':
16903               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16904               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16905               board[CASTLING][5] = blackKingFile;
16906               break;
16907           case'q':
16908               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16909               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16910               board[CASTLING][5] = blackKingFile;
16911           case '-':
16912               break;
16913           default: /* FRC castlings */
16914               if(c >= 'a') { /* black rights */
16915                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16916                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16917                   if(i == BOARD_RGHT) break;
16918                   board[CASTLING][5] = i;
16919                   c -= AAA;
16920                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16921                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16922                   if(c > i)
16923                       board[CASTLING][3] = c;
16924                   else
16925                       board[CASTLING][4] = c;
16926               } else { /* white rights */
16927                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16928                     if(board[0][i] == WhiteKing) break;
16929                   if(i == BOARD_RGHT) break;
16930                   board[CASTLING][2] = i;
16931                   c -= AAA - 'a' + 'A';
16932                   if(board[0][c] >= WhiteKing) break;
16933                   if(c > i)
16934                       board[CASTLING][0] = c;
16935                   else
16936                       board[CASTLING][1] = c;
16937               }
16938         }
16939       }
16940       for(i=0; i<nrCastlingRights; i++)
16941         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16942     if (appData.debugMode) {
16943         fprintf(debugFP, "FEN castling rights:");
16944         for(i=0; i<nrCastlingRights; i++)
16945         fprintf(debugFP, " %d", board[CASTLING][i]);
16946         fprintf(debugFP, "\n");
16947     }
16948
16949       while(*p==' ') p++;
16950     }
16951
16952     /* read e.p. field in games that know e.p. capture */
16953     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16954        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16955       if(*p=='-') {
16956         p++; board[EP_STATUS] = EP_NONE;
16957       } else {
16958          char c = *p++ - AAA;
16959
16960          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16961          if(*p >= '0' && *p <='9') p++;
16962          board[EP_STATUS] = c;
16963       }
16964     }
16965
16966
16967     if(sscanf(p, "%d", &i) == 1) {
16968         FENrulePlies = i; /* 50-move ply counter */
16969         /* (The move number is still ignored)    */
16970     }
16971
16972     return TRUE;
16973 }
16974
16975 void
16976 EditPositionPasteFEN (char *fen)
16977 {
16978   if (fen != NULL) {
16979     Board initial_position;
16980
16981     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16982       DisplayError(_("Bad FEN position in clipboard"), 0);
16983       return ;
16984     } else {
16985       int savedBlackPlaysFirst = blackPlaysFirst;
16986       EditPositionEvent();
16987       blackPlaysFirst = savedBlackPlaysFirst;
16988       CopyBoard(boards[0], initial_position);
16989       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16990       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16991       DisplayBothClocks();
16992       DrawPosition(FALSE, boards[currentMove]);
16993     }
16994   }
16995 }
16996
16997 static char cseq[12] = "\\   ";
16998
16999 Boolean
17000 set_cont_sequence (char *new_seq)
17001 {
17002     int len;
17003     Boolean ret;
17004
17005     // handle bad attempts to set the sequence
17006         if (!new_seq)
17007                 return 0; // acceptable error - no debug
17008
17009     len = strlen(new_seq);
17010     ret = (len > 0) && (len < sizeof(cseq));
17011     if (ret)
17012       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17013     else if (appData.debugMode)
17014       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17015     return ret;
17016 }
17017
17018 /*
17019     reformat a source message so words don't cross the width boundary.  internal
17020     newlines are not removed.  returns the wrapped size (no null character unless
17021     included in source message).  If dest is NULL, only calculate the size required
17022     for the dest buffer.  lp argument indicats line position upon entry, and it's
17023     passed back upon exit.
17024 */
17025 int
17026 wrap (char *dest, char *src, int count, int width, int *lp)
17027 {
17028     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17029
17030     cseq_len = strlen(cseq);
17031     old_line = line = *lp;
17032     ansi = len = clen = 0;
17033
17034     for (i=0; i < count; i++)
17035     {
17036         if (src[i] == '\033')
17037             ansi = 1;
17038
17039         // if we hit the width, back up
17040         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17041         {
17042             // store i & len in case the word is too long
17043             old_i = i, old_len = len;
17044
17045             // find the end of the last word
17046             while (i && src[i] != ' ' && src[i] != '\n')
17047             {
17048                 i--;
17049                 len--;
17050             }
17051
17052             // word too long?  restore i & len before splitting it
17053             if ((old_i-i+clen) >= width)
17054             {
17055                 i = old_i;
17056                 len = old_len;
17057             }
17058
17059             // extra space?
17060             if (i && src[i-1] == ' ')
17061                 len--;
17062
17063             if (src[i] != ' ' && src[i] != '\n')
17064             {
17065                 i--;
17066                 if (len)
17067                     len--;
17068             }
17069
17070             // now append the newline and continuation sequence
17071             if (dest)
17072                 dest[len] = '\n';
17073             len++;
17074             if (dest)
17075                 strncpy(dest+len, cseq, cseq_len);
17076             len += cseq_len;
17077             line = cseq_len;
17078             clen = cseq_len;
17079             continue;
17080         }
17081
17082         if (dest)
17083             dest[len] = src[i];
17084         len++;
17085         if (!ansi)
17086             line++;
17087         if (src[i] == '\n')
17088             line = 0;
17089         if (src[i] == 'm')
17090             ansi = 0;
17091     }
17092     if (dest && appData.debugMode)
17093     {
17094         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17095             count, width, line, len, *lp);
17096         show_bytes(debugFP, src, count);
17097         fprintf(debugFP, "\ndest: ");
17098         show_bytes(debugFP, dest, len);
17099         fprintf(debugFP, "\n");
17100     }
17101     *lp = dest ? line : old_line;
17102
17103     return len;
17104 }
17105
17106 // [HGM] vari: routines for shelving variations
17107 Boolean modeRestore = FALSE;
17108
17109 void
17110 PushInner (int firstMove, int lastMove)
17111 {
17112         int i, j, nrMoves = lastMove - firstMove;
17113
17114         // push current tail of game on stack
17115         savedResult[storedGames] = gameInfo.result;
17116         savedDetails[storedGames] = gameInfo.resultDetails;
17117         gameInfo.resultDetails = NULL;
17118         savedFirst[storedGames] = firstMove;
17119         savedLast [storedGames] = lastMove;
17120         savedFramePtr[storedGames] = framePtr;
17121         framePtr -= nrMoves; // reserve space for the boards
17122         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17123             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17124             for(j=0; j<MOVE_LEN; j++)
17125                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17126             for(j=0; j<2*MOVE_LEN; j++)
17127                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17128             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17129             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17130             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17131             pvInfoList[firstMove+i-1].depth = 0;
17132             commentList[framePtr+i] = commentList[firstMove+i];
17133             commentList[firstMove+i] = NULL;
17134         }
17135
17136         storedGames++;
17137         forwardMostMove = firstMove; // truncate game so we can start variation
17138 }
17139
17140 void
17141 PushTail (int firstMove, int lastMove)
17142 {
17143         if(appData.icsActive) { // only in local mode
17144                 forwardMostMove = currentMove; // mimic old ICS behavior
17145                 return;
17146         }
17147         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17148
17149         PushInner(firstMove, lastMove);
17150         if(storedGames == 1) GreyRevert(FALSE);
17151         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17152 }
17153
17154 void
17155 PopInner (Boolean annotate)
17156 {
17157         int i, j, nrMoves;
17158         char buf[8000], moveBuf[20];
17159
17160         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17161         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17162         nrMoves = savedLast[storedGames] - currentMove;
17163         if(annotate) {
17164                 int cnt = 10;
17165                 if(!WhiteOnMove(currentMove))
17166                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17167                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17168                 for(i=currentMove; i<forwardMostMove; i++) {
17169                         if(WhiteOnMove(i))
17170                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17171                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17172                         strcat(buf, moveBuf);
17173                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17174                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17175                 }
17176                 strcat(buf, ")");
17177         }
17178         for(i=1; i<=nrMoves; i++) { // copy last variation back
17179             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17180             for(j=0; j<MOVE_LEN; j++)
17181                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17182             for(j=0; j<2*MOVE_LEN; j++)
17183                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17184             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17185             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17186             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17187             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17188             commentList[currentMove+i] = commentList[framePtr+i];
17189             commentList[framePtr+i] = NULL;
17190         }
17191         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17192         framePtr = savedFramePtr[storedGames];
17193         gameInfo.result = savedResult[storedGames];
17194         if(gameInfo.resultDetails != NULL) {
17195             free(gameInfo.resultDetails);
17196       }
17197         gameInfo.resultDetails = savedDetails[storedGames];
17198         forwardMostMove = currentMove + nrMoves;
17199 }
17200
17201 Boolean
17202 PopTail (Boolean annotate)
17203 {
17204         if(appData.icsActive) return FALSE; // only in local mode
17205         if(!storedGames) return FALSE; // sanity
17206         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17207
17208         PopInner(annotate);
17209         if(currentMove < forwardMostMove) ForwardEvent(); else
17210         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17211
17212         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17213         return TRUE;
17214 }
17215
17216 void
17217 CleanupTail ()
17218 {       // remove all shelved variations
17219         int i;
17220         for(i=0; i<storedGames; i++) {
17221             if(savedDetails[i])
17222                 free(savedDetails[i]);
17223             savedDetails[i] = NULL;
17224         }
17225         for(i=framePtr; i<MAX_MOVES; i++) {
17226                 if(commentList[i]) free(commentList[i]);
17227                 commentList[i] = NULL;
17228         }
17229         framePtr = MAX_MOVES-1;
17230         storedGames = 0;
17231 }
17232
17233 void
17234 LoadVariation (int index, char *text)
17235 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17236         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17237         int level = 0, move;
17238
17239         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17240         // first find outermost bracketing variation
17241         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17242             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17243                 if(*p == '{') wait = '}'; else
17244                 if(*p == '[') wait = ']'; else
17245                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17246                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17247             }
17248             if(*p == wait) wait = NULLCHAR; // closing ]} found
17249             p++;
17250         }
17251         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17252         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17253         end[1] = NULLCHAR; // clip off comment beyond variation
17254         ToNrEvent(currentMove-1);
17255         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17256         // kludge: use ParsePV() to append variation to game
17257         move = currentMove;
17258         ParsePV(start, TRUE, TRUE);
17259         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17260         ClearPremoveHighlights();
17261         CommentPopDown();
17262         ToNrEvent(currentMove+1);
17263 }
17264