20e5efce5011ebb0cd9e56bd53101dc5a974471d
[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 SendToICS P((char *s));
156 void SendToICSDelayed P((char *s, long msdelay));
157 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
158 void HandleMachineMove P((char *message, ChessProgramState *cps));
159 int AutoPlayOneMove P((void));
160 int LoadGameOneMove P((ChessMove readAhead));
161 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
162 int LoadPositionFromFile P((char *filename, int n, char *title));
163 int SavePositionToFile P((char *filename));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179                            char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181                         int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 int ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
188
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((int nr));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 int RegisterMove P((void));
199 void MakeRegisteredMove P((void));
200 void TruncateGame P((void));
201 int looking_at P((char *, int *, char *));
202 void CopyPlayerNameIntoFileName P((char **, char *));
203 char *SavePart P((char *));
204 int SaveGameOldStyle P((FILE *));
205 int SaveGamePGN P((FILE *));
206 int CheckFlags P((void));
207 long NextTickLength P((long));
208 void CheckTimeControl P((void));
209 void show_bytes P((FILE *, char *, int));
210 int string_to_rating P((char *str));
211 void ParseFeatures P((char* args, ChessProgramState *cps));
212 void InitBackEnd3 P((void));
213 void FeatureDone P((ChessProgramState* cps, int val));
214 void InitChessProgram P((ChessProgramState *cps, int setup));
215 void OutputKibitz(int window, char *text);
216 int PerpetualChase(int first, int last);
217 int EngineOutputIsUp();
218 void InitDrawingSizes(int x, int y);
219 void NextMatchGame P((void));
220 int NextTourneyGame P((int nr, int *swap));
221 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
222 FILE *WriteTourneyFile P((char *results, FILE *f));
223 void DisplayTwoMachinesTitle P(());
224 static void ExcludeClick P((int index));
225
226 #ifdef WIN32
227        extern void ConsoleCreate();
228 #endif
229
230 ChessProgramState *WhitePlayer();
231 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
232 int VerifyDisplayMode P(());
233
234 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
235 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
236 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
237 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
238 void ics_update_width P((int new_width));
239 extern char installDir[MSG_SIZ];
240 VariantClass startVariant; /* [HGM] nicks: initial variant */
241 Boolean abortMatch;
242
243 extern int tinyLayout, smallLayout;
244 ChessProgramStats programStats;
245 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
246 int endPV = -1;
247 static int exiting = 0; /* [HGM] moved to top */
248 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
249 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
250 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
251 int partnerHighlight[2];
252 Boolean partnerBoardValid = 0;
253 char partnerStatus[MSG_SIZ];
254 Boolean partnerUp;
255 Boolean originalFlip;
256 Boolean twoBoards = 0;
257 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
258 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
259 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
260 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
261 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
262 int opponentKibitzes;
263 int lastSavedGame; /* [HGM] save: ID of game */
264 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
265 extern int chatCount;
266 int chattingPartner;
267 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
268 char lastMsg[MSG_SIZ];
269 ChessSquare pieceSweep = EmptySquare;
270 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
271 int promoDefaultAltered;
272
273 /* States for ics_getting_history */
274 #define H_FALSE 0
275 #define H_REQUESTED 1
276 #define H_GOT_REQ_HEADER 2
277 #define H_GOT_UNREQ_HEADER 3
278 #define H_GETTING_MOVES 4
279 #define H_GOT_UNWANTED_HEADER 5
280
281 /* whosays values for GameEnds */
282 #define GE_ICS 0
283 #define GE_ENGINE 1
284 #define GE_PLAYER 2
285 #define GE_FILE 3
286 #define GE_XBOARD 4
287 #define GE_ENGINE1 5
288 #define GE_ENGINE2 6
289
290 /* Maximum number of games in a cmail message */
291 #define CMAIL_MAX_GAMES 20
292
293 /* Different types of move when calling RegisterMove */
294 #define CMAIL_MOVE   0
295 #define CMAIL_RESIGN 1
296 #define CMAIL_DRAW   2
297 #define CMAIL_ACCEPT 3
298
299 /* Different types of result to remember for each game */
300 #define CMAIL_NOT_RESULT 0
301 #define CMAIL_OLD_RESULT 1
302 #define CMAIL_NEW_RESULT 2
303
304 /* Telnet protocol constants */
305 #define TN_WILL 0373
306 #define TN_WONT 0374
307 #define TN_DO   0375
308 #define TN_DONT 0376
309 #define TN_IAC  0377
310 #define TN_ECHO 0001
311 #define TN_SGA  0003
312 #define TN_PORT 23
313
314 char*
315 safeStrCpy (char *dst, const char *src, size_t count)
316 { // [HGM] made safe
317   int i;
318   assert( dst != NULL );
319   assert( src != NULL );
320   assert( count > 0 );
321
322   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
323   if(  i == count && dst[count-1] != NULLCHAR)
324     {
325       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
326       if(appData.debugMode)
327       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
328     }
329
330   return dst;
331 }
332
333 /* Some compiler can't cast u64 to double
334  * This function do the job for us:
335
336  * We use the highest bit for cast, this only
337  * works if the highest bit is not
338  * in use (This should not happen)
339  *
340  * We used this for all compiler
341  */
342 double
343 u64ToDouble (u64 value)
344 {
345   double r;
346   u64 tmp = value & u64Const(0x7fffffffffffffff);
347   r = (double)(s64)tmp;
348   if (value & u64Const(0x8000000000000000))
349        r +=  9.2233720368547758080e18; /* 2^63 */
350  return r;
351 }
352
353 /* Fake up flags for now, as we aren't keeping track of castling
354    availability yet. [HGM] Change of logic: the flag now only
355    indicates the type of castlings allowed by the rule of the game.
356    The actual rights themselves are maintained in the array
357    castlingRights, as part of the game history, and are not probed
358    by this function.
359  */
360 int
361 PosFlags (index)
362 {
363   int flags = F_ALL_CASTLE_OK;
364   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
365   switch (gameInfo.variant) {
366   case VariantSuicide:
367     flags &= ~F_ALL_CASTLE_OK;
368   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
369     flags |= F_IGNORE_CHECK;
370   case VariantLosers:
371     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
372     break;
373   case VariantAtomic:
374     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
375     break;
376   case VariantKriegspiel:
377     flags |= F_KRIEGSPIEL_CAPTURE;
378     break;
379   case VariantCapaRandom:
380   case VariantFischeRandom:
381     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
382   case VariantNoCastle:
383   case VariantShatranj:
384   case VariantCourier:
385   case VariantMakruk:
386   case VariantGrand:
387     flags &= ~F_ALL_CASTLE_OK;
388     break;
389   default:
390     break;
391   }
392   return flags;
393 }
394
395 FILE *gameFileFP, *debugFP, *serverFP;
396 char *currentDebugFile; // [HGM] debug split: to remember name
397
398 /*
399     [AS] Note: sometimes, the sscanf() function is used to parse the input
400     into a fixed-size buffer. Because of this, we must be prepared to
401     receive strings as long as the size of the input buffer, which is currently
402     set to 4K for Windows and 8K for the rest.
403     So, we must either allocate sufficiently large buffers here, or
404     reduce the size of the input buffer in the input reading part.
405 */
406
407 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
408 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
409 char thinkOutput1[MSG_SIZ*10];
410
411 ChessProgramState first, second, pairing;
412
413 /* premove variables */
414 int premoveToX = 0;
415 int premoveToY = 0;
416 int premoveFromX = 0;
417 int premoveFromY = 0;
418 int premovePromoChar = 0;
419 int gotPremove = 0;
420 Boolean alarmSounded;
421 /* end premove variables */
422
423 char *ics_prefix = "$";
424 int ics_type = ICS_GENERIC;
425
426 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
427 int pauseExamForwardMostMove = 0;
428 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
429 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
430 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
431 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
432 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
433 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
434 int whiteFlag = FALSE, blackFlag = FALSE;
435 int userOfferedDraw = FALSE;
436 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
437 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
438 int cmailMoveType[CMAIL_MAX_GAMES];
439 long ics_clock_paused = 0;
440 ProcRef icsPR = NoProc, cmailPR = NoProc;
441 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
442 GameMode gameMode = BeginningOfGame;
443 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
444 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
445 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
446 int hiddenThinkOutputState = 0; /* [AS] */
447 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
448 int adjudicateLossPlies = 6;
449 char white_holding[64], black_holding[64];
450 TimeMark lastNodeCountTime;
451 long lastNodeCount=0;
452 int shiftKey, controlKey; // [HGM] set by mouse handler
453
454 int have_sent_ICS_logon = 0;
455 int movesPerSession;
456 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
457 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
458 Boolean adjustedClock;
459 long timeControl_2; /* [AS] Allow separate time controls */
460 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
461 long timeRemaining[2][MAX_MOVES];
462 int matchGame = 0, nextGame = 0, roundNr = 0;
463 Boolean waitingForGame = FALSE;
464 TimeMark programStartTime, pauseStart;
465 char ics_handle[MSG_SIZ];
466 int have_set_title = 0;
467
468 /* animateTraining preserves the state of appData.animate
469  * when Training mode is activated. This allows the
470  * response to be animated when appData.animate == TRUE and
471  * appData.animateDragging == TRUE.
472  */
473 Boolean animateTraining;
474
475 GameInfo gameInfo;
476
477 AppData appData;
478
479 Board boards[MAX_MOVES];
480 /* [HGM] Following 7 needed for accurate legality tests: */
481 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
482 signed char  initialRights[BOARD_FILES];
483 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
484 int   initialRulePlies, FENrulePlies;
485 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
486 int loadFlag = 0;
487 Boolean shuffleOpenings;
488 int mute; // mute all sounds
489
490 // [HGM] vari: next 12 to save and restore variations
491 #define MAX_VARIATIONS 10
492 int framePtr = MAX_MOVES-1; // points to free stack entry
493 int storedGames = 0;
494 int savedFirst[MAX_VARIATIONS];
495 int savedLast[MAX_VARIATIONS];
496 int savedFramePtr[MAX_VARIATIONS];
497 char *savedDetails[MAX_VARIATIONS];
498 ChessMove savedResult[MAX_VARIATIONS];
499
500 void PushTail P((int firstMove, int lastMove));
501 Boolean PopTail P((Boolean annotate));
502 void PushInner P((int firstMove, int lastMove));
503 void PopInner P((Boolean annotate));
504 void CleanupTail P((void));
505
506 ChessSquare  FIDEArray[2][BOARD_FILES] = {
507     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
508         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
509     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
510         BlackKing, BlackBishop, BlackKnight, BlackRook }
511 };
512
513 ChessSquare twoKingsArray[2][BOARD_FILES] = {
514     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
516     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517         BlackKing, BlackKing, BlackKnight, BlackRook }
518 };
519
520 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
521     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
522         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
523     { BlackRook, BlackMan, BlackBishop, BlackQueen,
524         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
525 };
526
527 ChessSquare SpartanArray[2][BOARD_FILES] = {
528     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
529         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
530     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
531         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
532 };
533
534 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
535     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
538         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
539 };
540
541 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
542     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
543         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
544     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
545         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
546 };
547
548 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
550         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
551     { BlackRook, BlackKnight, BlackMan, BlackFerz,
552         BlackKing, BlackMan, BlackKnight, BlackRook }
553 };
554
555
556 #if (BOARD_FILES>=10)
557 ChessSquare ShogiArray[2][BOARD_FILES] = {
558     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
559         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
560     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
561         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
562 };
563
564 ChessSquare XiangqiArray[2][BOARD_FILES] = {
565     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
566         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
567     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
568         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
569 };
570
571 ChessSquare CapablancaArray[2][BOARD_FILES] = {
572     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
573         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
574     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
575         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
576 };
577
578 ChessSquare GreatArray[2][BOARD_FILES] = {
579     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
580         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
581     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
582         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
583 };
584
585 ChessSquare JanusArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
587         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
588     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
589         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
590 };
591
592 ChessSquare GrandArray[2][BOARD_FILES] = {
593     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
594         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
595     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
596         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
597 };
598
599 #ifdef GOTHIC
600 ChessSquare GothicArray[2][BOARD_FILES] = {
601     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
602         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
603     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
604         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
605 };
606 #else // !GOTHIC
607 #define GothicArray CapablancaArray
608 #endif // !GOTHIC
609
610 #ifdef FALCON
611 ChessSquare FalconArray[2][BOARD_FILES] = {
612     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
613         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
614     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
615         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
616 };
617 #else // !FALCON
618 #define FalconArray CapablancaArray
619 #endif // !FALCON
620
621 #else // !(BOARD_FILES>=10)
622 #define XiangqiPosition FIDEArray
623 #define CapablancaArray FIDEArray
624 #define GothicArray FIDEArray
625 #define GreatArray FIDEArray
626 #endif // !(BOARD_FILES>=10)
627
628 #if (BOARD_FILES>=12)
629 ChessSquare CourierArray[2][BOARD_FILES] = {
630     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
631         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
632     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
633         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
634 };
635 #else // !(BOARD_FILES>=12)
636 #define CourierArray CapablancaArray
637 #endif // !(BOARD_FILES>=12)
638
639
640 Board initialPosition;
641
642
643 /* Convert str to a rating. Checks for special cases of "----",
644
645    "++++", etc. Also strips ()'s */
646 int
647 string_to_rating (char *str)
648 {
649   while(*str && !isdigit(*str)) ++str;
650   if (!*str)
651     return 0;   /* One of the special "no rating" cases */
652   else
653     return atoi(str);
654 }
655
656 void
657 ClearProgramStats ()
658 {
659     /* Init programStats */
660     programStats.movelist[0] = 0;
661     programStats.depth = 0;
662     programStats.nr_moves = 0;
663     programStats.moves_left = 0;
664     programStats.nodes = 0;
665     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
666     programStats.score = 0;
667     programStats.got_only_move = 0;
668     programStats.got_fail = 0;
669     programStats.line_is_book = 0;
670 }
671
672 void
673 CommonEngineInit ()
674 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
675     if (appData.firstPlaysBlack) {
676         first.twoMachinesColor = "black\n";
677         second.twoMachinesColor = "white\n";
678     } else {
679         first.twoMachinesColor = "white\n";
680         second.twoMachinesColor = "black\n";
681     }
682
683     first.other = &second;
684     second.other = &first;
685
686     { float norm = 1;
687         if(appData.timeOddsMode) {
688             norm = appData.timeOdds[0];
689             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
690         }
691         first.timeOdds  = appData.timeOdds[0]/norm;
692         second.timeOdds = appData.timeOdds[1]/norm;
693     }
694
695     if(programVersion) free(programVersion);
696     if (appData.noChessProgram) {
697         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
698         sprintf(programVersion, "%s", PACKAGE_STRING);
699     } else {
700       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
701       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
702       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
703     }
704 }
705
706 void
707 UnloadEngine (ChessProgramState *cps)
708 {
709         /* Kill off first chess program */
710         if (cps->isr != NULL)
711           RemoveInputSource(cps->isr);
712         cps->isr = NULL;
713
714         if (cps->pr != NoProc) {
715             ExitAnalyzeMode();
716             DoSleep( appData.delayBeforeQuit );
717             SendToProgram("quit\n", cps);
718             DoSleep( appData.delayAfterQuit );
719             DestroyChildProcess(cps->pr, cps->useSigterm);
720         }
721         cps->pr = NoProc;
722         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
723 }
724
725 void
726 ClearOptions (ChessProgramState *cps)
727 {
728     int i;
729     cps->nrOptions = cps->comboCnt = 0;
730     for(i=0; i<MAX_OPTIONS; i++) {
731         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
732         cps->option[i].textValue = 0;
733     }
734 }
735
736 char *engineNames[] = {
737   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
738      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
739 N_("first"),
740   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
741      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
742 N_("second")
743 };
744
745 void
746 InitEngine (ChessProgramState *cps, int n)
747 {   // [HGM] all engine initialiation put in a function that does one engine
748
749     ClearOptions(cps);
750
751     cps->which = engineNames[n];
752     cps->maybeThinking = FALSE;
753     cps->pr = NoProc;
754     cps->isr = NULL;
755     cps->sendTime = 2;
756     cps->sendDrawOffers = 1;
757
758     cps->program = appData.chessProgram[n];
759     cps->host = appData.host[n];
760     cps->dir = appData.directory[n];
761     cps->initString = appData.engInitString[n];
762     cps->computerString = appData.computerString[n];
763     cps->useSigint  = TRUE;
764     cps->useSigterm = TRUE;
765     cps->reuse = appData.reuse[n];
766     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
767     cps->useSetboard = FALSE;
768     cps->useSAN = FALSE;
769     cps->usePing = FALSE;
770     cps->lastPing = 0;
771     cps->lastPong = 0;
772     cps->usePlayother = FALSE;
773     cps->useColors = TRUE;
774     cps->useUsermove = FALSE;
775     cps->sendICS = FALSE;
776     cps->sendName = appData.icsActive;
777     cps->sdKludge = FALSE;
778     cps->stKludge = FALSE;
779     TidyProgramName(cps->program, cps->host, cps->tidy);
780     cps->matchWins = 0;
781     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
782     cps->analysisSupport = 2; /* detect */
783     cps->analyzing = FALSE;
784     cps->initDone = FALSE;
785
786     /* New features added by Tord: */
787     cps->useFEN960 = FALSE;
788     cps->useOOCastle = TRUE;
789     /* End of new features added by Tord. */
790     cps->fenOverride  = appData.fenOverride[n];
791
792     /* [HGM] time odds: set factor for each machine */
793     cps->timeOdds  = appData.timeOdds[n];
794
795     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
796     cps->accumulateTC = appData.accumulateTC[n];
797     cps->maxNrOfSessions = 1;
798
799     /* [HGM] debug */
800     cps->debug = FALSE;
801
802     cps->supportsNPS = UNKNOWN;
803     cps->memSize = FALSE;
804     cps->maxCores = FALSE;
805     cps->egtFormats[0] = NULLCHAR;
806
807     /* [HGM] options */
808     cps->optionSettings  = appData.engOptions[n];
809
810     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
811     cps->isUCI = appData.isUCI[n]; /* [AS] */
812     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
813
814     if (appData.protocolVersion[n] > PROTOVER
815         || appData.protocolVersion[n] < 1)
816       {
817         char buf[MSG_SIZ];
818         int len;
819
820         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
821                        appData.protocolVersion[n]);
822         if( (len >= MSG_SIZ) && appData.debugMode )
823           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
824
825         DisplayFatalError(buf, 0, 2);
826       }
827     else
828       {
829         cps->protocolVersion = appData.protocolVersion[n];
830       }
831
832     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
833     ParseFeatures(appData.featureDefaults, cps);
834 }
835
836 ChessProgramState *savCps;
837
838 void
839 LoadEngine ()
840 {
841     int i;
842     if(WaitForEngine(savCps, LoadEngine)) return;
843     CommonEngineInit(); // recalculate time odds
844     if(gameInfo.variant != StringToVariant(appData.variant)) {
845         // we changed variant when loading the engine; this forces us to reset
846         Reset(TRUE, savCps != &first);
847         EditGameEvent(); // for consistency with other path, as Reset changes mode
848     }
849     InitChessProgram(savCps, FALSE);
850     SendToProgram("force\n", savCps);
851     DisplayMessage("", "");
852     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
853     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
854     ThawUI();
855     SetGNUMode();
856 }
857
858 void
859 ReplaceEngine (ChessProgramState *cps, int n)
860 {
861     EditGameEvent();
862     UnloadEngine(cps);
863     appData.noChessProgram = FALSE;
864     appData.clockMode = TRUE;
865     InitEngine(cps, n);
866     UpdateLogos(TRUE);
867     if(n) return; // only startup first engine immediately; second can wait
868     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
869     LoadEngine();
870 }
871
872 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
873 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
874
875 static char resetOptions[] = 
876         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
877         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
878         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
879         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
880
881 void
882 FloatToFront(char **list, char *engineLine)
883 {
884     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
885     int i=0;
886     if(appData.recentEngines <= 0) return;
887     TidyProgramName(engineLine, "localhost", tidy+1);
888     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
889     strncpy(buf+1, *list, MSG_SIZ-50);
890     if(p = strstr(buf, tidy)) { // tidy name appears in list
891         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
892         while(*p++ = *++q); // squeeze out
893     }
894     strcat(tidy, buf+1); // put list behind tidy name
895     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
896     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
897     ASSIGN(*list, tidy+1);
898 }
899
900 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
901
902 void
903 Load (ChessProgramState *cps, int i)
904 {
905     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
906     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
907         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
908         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
909         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
910         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
911         appData.firstProtocolVersion = PROTOVER;
912         ParseArgsFromString(buf);
913         SwapEngines(i);
914         ReplaceEngine(cps, i);
915         FloatToFront(&appData.recentEngineList, engineLine);
916         return;
917     }
918     p = engineName;
919     while(q = strchr(p, SLASH)) p = q+1;
920     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
921     if(engineDir[0] != NULLCHAR) {
922         ASSIGN(appData.directory[i], engineDir); p = engineName;
923     } else if(p != engineName) { // derive directory from engine path, when not given
924         p[-1] = 0;
925         ASSIGN(appData.directory[i], engineName);
926         p[-1] = SLASH;
927         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
928     } else { ASSIGN(appData.directory[i], "."); }
929     if(params[0]) {
930         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
931         snprintf(command, MSG_SIZ, "%s %s", p, params);
932         p = command;
933     }
934     ASSIGN(appData.chessProgram[i], p);
935     appData.isUCI[i] = isUCI;
936     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
937     appData.hasOwnBookUCI[i] = hasBook;
938     if(!nickName[0]) useNick = FALSE;
939     if(useNick) ASSIGN(appData.pgnName[i], nickName);
940     if(addToList) {
941         int len;
942         char quote;
943         q = firstChessProgramNames;
944         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
945         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
946         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
947                         quote, p, quote, appData.directory[i], 
948                         useNick ? " -fn \"" : "",
949                         useNick ? nickName : "",
950                         useNick ? "\"" : "",
951                         v1 ? " -firstProtocolVersion 1" : "",
952                         hasBook ? "" : " -fNoOwnBookUCI",
953                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
954                         storeVariant ? " -variant " : "",
955                         storeVariant ? VariantName(gameInfo.variant) : "");
956         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
957         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
958         if(insert != q) insert[-1] = NULLCHAR;
959         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
960         if(q)   free(q);
961         FloatToFront(&appData.recentEngineList, buf);
962     }
963     ReplaceEngine(cps, i);
964 }
965
966 void
967 InitTimeControls ()
968 {
969     int matched, min, sec;
970     /*
971      * Parse timeControl resource
972      */
973     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
974                           appData.movesPerSession)) {
975         char buf[MSG_SIZ];
976         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
977         DisplayFatalError(buf, 0, 2);
978     }
979
980     /*
981      * Parse searchTime resource
982      */
983     if (*appData.searchTime != NULLCHAR) {
984         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
985         if (matched == 1) {
986             searchTime = min * 60;
987         } else if (matched == 2) {
988             searchTime = min * 60 + sec;
989         } else {
990             char buf[MSG_SIZ];
991             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
992             DisplayFatalError(buf, 0, 2);
993         }
994     }
995 }
996
997 void
998 InitBackEnd1 ()
999 {
1000
1001     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1002     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1003
1004     GetTimeMark(&programStartTime);
1005     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1006     appData.seedBase = random() + (random()<<15);
1007     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1008
1009     ClearProgramStats();
1010     programStats.ok_to_send = 1;
1011     programStats.seen_stat = 0;
1012
1013     /*
1014      * Initialize game list
1015      */
1016     ListNew(&gameList);
1017
1018
1019     /*
1020      * Internet chess server status
1021      */
1022     if (appData.icsActive) {
1023         appData.matchMode = FALSE;
1024         appData.matchGames = 0;
1025 #if ZIPPY
1026         appData.noChessProgram = !appData.zippyPlay;
1027 #else
1028         appData.zippyPlay = FALSE;
1029         appData.zippyTalk = FALSE;
1030         appData.noChessProgram = TRUE;
1031 #endif
1032         if (*appData.icsHelper != NULLCHAR) {
1033             appData.useTelnet = TRUE;
1034             appData.telnetProgram = appData.icsHelper;
1035         }
1036     } else {
1037         appData.zippyTalk = appData.zippyPlay = FALSE;
1038     }
1039
1040     /* [AS] Initialize pv info list [HGM] and game state */
1041     {
1042         int i, j;
1043
1044         for( i=0; i<=framePtr; i++ ) {
1045             pvInfoList[i].depth = -1;
1046             boards[i][EP_STATUS] = EP_NONE;
1047             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1048         }
1049     }
1050
1051     InitTimeControls();
1052
1053     /* [AS] Adjudication threshold */
1054     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1055
1056     InitEngine(&first, 0);
1057     InitEngine(&second, 1);
1058     CommonEngineInit();
1059
1060     pairing.which = "pairing"; // pairing engine
1061     pairing.pr = NoProc;
1062     pairing.isr = NULL;
1063     pairing.program = appData.pairingEngine;
1064     pairing.host = "localhost";
1065     pairing.dir = ".";
1066
1067     if (appData.icsActive) {
1068         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1069     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1070         appData.clockMode = FALSE;
1071         first.sendTime = second.sendTime = 0;
1072     }
1073
1074 #if ZIPPY
1075     /* Override some settings from environment variables, for backward
1076        compatibility.  Unfortunately it's not feasible to have the env
1077        vars just set defaults, at least in xboard.  Ugh.
1078     */
1079     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1080       ZippyInit();
1081     }
1082 #endif
1083
1084     if (!appData.icsActive) {
1085       char buf[MSG_SIZ];
1086       int len;
1087
1088       /* Check for variants that are supported only in ICS mode,
1089          or not at all.  Some that are accepted here nevertheless
1090          have bugs; see comments below.
1091       */
1092       VariantClass variant = StringToVariant(appData.variant);
1093       switch (variant) {
1094       case VariantBughouse:     /* need four players and two boards */
1095       case VariantKriegspiel:   /* need to hide pieces and move details */
1096         /* case VariantFischeRandom: (Fabien: moved below) */
1097         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1098         if( (len >= MSG_SIZ) && appData.debugMode )
1099           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1100
1101         DisplayFatalError(buf, 0, 2);
1102         return;
1103
1104       case VariantUnknown:
1105       case VariantLoadable:
1106       case Variant29:
1107       case Variant30:
1108       case Variant31:
1109       case Variant32:
1110       case Variant33:
1111       case Variant34:
1112       case Variant35:
1113       case Variant36:
1114       default:
1115         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1116         if( (len >= MSG_SIZ) && appData.debugMode )
1117           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1118
1119         DisplayFatalError(buf, 0, 2);
1120         return;
1121
1122       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1123       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1124       case VariantGothic:     /* [HGM] should work */
1125       case VariantCapablanca: /* [HGM] should work */
1126       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1127       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1128       case VariantKnightmate: /* [HGM] should work */
1129       case VariantCylinder:   /* [HGM] untested */
1130       case VariantFalcon:     /* [HGM] untested */
1131       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1132                                  offboard interposition not understood */
1133       case VariantNormal:     /* definitely works! */
1134       case VariantWildCastle: /* pieces not automatically shuffled */
1135       case VariantNoCastle:   /* pieces not automatically shuffled */
1136       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1137       case VariantLosers:     /* should work except for win condition,
1138                                  and doesn't know captures are mandatory */
1139       case VariantSuicide:    /* should work except for win condition,
1140                                  and doesn't know captures are mandatory */
1141       case VariantGiveaway:   /* should work except for win condition,
1142                                  and doesn't know captures are mandatory */
1143       case VariantTwoKings:   /* should work */
1144       case VariantAtomic:     /* should work except for win condition */
1145       case Variant3Check:     /* should work except for win condition */
1146       case VariantShatranj:   /* should work except for all win conditions */
1147       case VariantMakruk:     /* should work except for draw countdown */
1148       case VariantBerolina:   /* might work if TestLegality is off */
1149       case VariantCapaRandom: /* should work */
1150       case VariantJanus:      /* should work */
1151       case VariantSuper:      /* experimental */
1152       case VariantGreat:      /* experimental, requires legality testing to be off */
1153       case VariantSChess:     /* S-Chess, should work */
1154       case VariantGrand:      /* should work */
1155       case VariantSpartan:    /* should work */
1156         break;
1157       }
1158     }
1159
1160 }
1161
1162 int
1163 NextIntegerFromString (char ** str, long * value)
1164 {
1165     int result = -1;
1166     char * s = *str;
1167
1168     while( *s == ' ' || *s == '\t' ) {
1169         s++;
1170     }
1171
1172     *value = 0;
1173
1174     if( *s >= '0' && *s <= '9' ) {
1175         while( *s >= '0' && *s <= '9' ) {
1176             *value = *value * 10 + (*s - '0');
1177             s++;
1178         }
1179
1180         result = 0;
1181     }
1182
1183     *str = s;
1184
1185     return result;
1186 }
1187
1188 int
1189 NextTimeControlFromString (char ** str, long * value)
1190 {
1191     long temp;
1192     int result = NextIntegerFromString( str, &temp );
1193
1194     if( result == 0 ) {
1195         *value = temp * 60; /* Minutes */
1196         if( **str == ':' ) {
1197             (*str)++;
1198             result = NextIntegerFromString( str, &temp );
1199             *value += temp; /* Seconds */
1200         }
1201     }
1202
1203     return result;
1204 }
1205
1206 int
1207 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1208 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1209     int result = -1, type = 0; long temp, temp2;
1210
1211     if(**str != ':') return -1; // old params remain in force!
1212     (*str)++;
1213     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1214     if( NextIntegerFromString( str, &temp ) ) return -1;
1215     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1216
1217     if(**str != '/') {
1218         /* time only: incremental or sudden-death time control */
1219         if(**str == '+') { /* increment follows; read it */
1220             (*str)++;
1221             if(**str == '!') type = *(*str)++; // Bronstein TC
1222             if(result = NextIntegerFromString( str, &temp2)) return -1;
1223             *inc = temp2 * 1000;
1224             if(**str == '.') { // read fraction of increment
1225                 char *start = ++(*str);
1226                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1227                 temp2 *= 1000;
1228                 while(start++ < *str) temp2 /= 10;
1229                 *inc += temp2;
1230             }
1231         } else *inc = 0;
1232         *moves = 0; *tc = temp * 1000; *incType = type;
1233         return 0;
1234     }
1235
1236     (*str)++; /* classical time control */
1237     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1238
1239     if(result == 0) {
1240         *moves = temp;
1241         *tc    = temp2 * 1000;
1242         *inc   = 0;
1243         *incType = type;
1244     }
1245     return result;
1246 }
1247
1248 int
1249 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1250 {   /* [HGM] get time to add from the multi-session time-control string */
1251     int incType, moves=1; /* kludge to force reading of first session */
1252     long time, increment;
1253     char *s = tcString;
1254
1255     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1256     do {
1257         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1258         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1259         if(movenr == -1) return time;    /* last move before new session     */
1260         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1261         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1262         if(!moves) return increment;     /* current session is incremental   */
1263         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1264     } while(movenr >= -1);               /* try again for next session       */
1265
1266     return 0; // no new time quota on this move
1267 }
1268
1269 int
1270 ParseTimeControl (char *tc, float ti, int mps)
1271 {
1272   long tc1;
1273   long tc2;
1274   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1275   int min, sec=0;
1276
1277   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1278   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1279       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1280   if(ti > 0) {
1281
1282     if(mps)
1283       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1284     else 
1285       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1286   } else {
1287     if(mps)
1288       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1289     else 
1290       snprintf(buf, MSG_SIZ, ":%s", mytc);
1291   }
1292   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1293   
1294   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1295     return FALSE;
1296   }
1297
1298   if( *tc == '/' ) {
1299     /* Parse second time control */
1300     tc++;
1301
1302     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1303       return FALSE;
1304     }
1305
1306     if( tc2 == 0 ) {
1307       return FALSE;
1308     }
1309
1310     timeControl_2 = tc2 * 1000;
1311   }
1312   else {
1313     timeControl_2 = 0;
1314   }
1315
1316   if( tc1 == 0 ) {
1317     return FALSE;
1318   }
1319
1320   timeControl = tc1 * 1000;
1321
1322   if (ti >= 0) {
1323     timeIncrement = ti * 1000;  /* convert to ms */
1324     movesPerSession = 0;
1325   } else {
1326     timeIncrement = 0;
1327     movesPerSession = mps;
1328   }
1329   return TRUE;
1330 }
1331
1332 void
1333 InitBackEnd2 ()
1334 {
1335     if (appData.debugMode) {
1336         fprintf(debugFP, "%s\n", programVersion);
1337     }
1338     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1339
1340     set_cont_sequence(appData.wrapContSeq);
1341     if (appData.matchGames > 0) {
1342         appData.matchMode = TRUE;
1343     } else if (appData.matchMode) {
1344         appData.matchGames = 1;
1345     }
1346     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1347         appData.matchGames = appData.sameColorGames;
1348     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1349         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1350         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1351     }
1352     Reset(TRUE, FALSE);
1353     if (appData.noChessProgram || first.protocolVersion == 1) {
1354       InitBackEnd3();
1355     } else {
1356       /* kludge: allow timeout for initial "feature" commands */
1357       FreezeUI();
1358       DisplayMessage("", _("Starting chess program"));
1359       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1360     }
1361 }
1362
1363 int
1364 CalculateIndex (int index, int gameNr)
1365 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1366     int res;
1367     if(index > 0) return index; // fixed nmber
1368     if(index == 0) return 1;
1369     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1370     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1371     return res;
1372 }
1373
1374 int
1375 LoadGameOrPosition (int gameNr)
1376 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1377     if (*appData.loadGameFile != NULLCHAR) {
1378         if (!LoadGameFromFile(appData.loadGameFile,
1379                 CalculateIndex(appData.loadGameIndex, gameNr),
1380                               appData.loadGameFile, FALSE)) {
1381             DisplayFatalError(_("Bad game file"), 0, 1);
1382             return 0;
1383         }
1384     } else if (*appData.loadPositionFile != NULLCHAR) {
1385         if (!LoadPositionFromFile(appData.loadPositionFile,
1386                 CalculateIndex(appData.loadPositionIndex, gameNr),
1387                                   appData.loadPositionFile)) {
1388             DisplayFatalError(_("Bad position file"), 0, 1);
1389             return 0;
1390         }
1391     }
1392     return 1;
1393 }
1394
1395 void
1396 ReserveGame (int gameNr, char resChar)
1397 {
1398     FILE *tf = fopen(appData.tourneyFile, "r+");
1399     char *p, *q, c, buf[MSG_SIZ];
1400     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1401     safeStrCpy(buf, lastMsg, MSG_SIZ);
1402     DisplayMessage(_("Pick new game"), "");
1403     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1404     ParseArgsFromFile(tf);
1405     p = q = appData.results;
1406     if(appData.debugMode) {
1407       char *r = appData.participants;
1408       fprintf(debugFP, "results = '%s'\n", p);
1409       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1410       fprintf(debugFP, "\n");
1411     }
1412     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1413     nextGame = q - p;
1414     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1415     safeStrCpy(q, p, strlen(p) + 2);
1416     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1417     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1418     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1419         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1420         q[nextGame] = '*';
1421     }
1422     fseek(tf, -(strlen(p)+4), SEEK_END);
1423     c = fgetc(tf);
1424     if(c != '"') // depending on DOS or Unix line endings we can be one off
1425          fseek(tf, -(strlen(p)+2), SEEK_END);
1426     else fseek(tf, -(strlen(p)+3), SEEK_END);
1427     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1428     DisplayMessage(buf, "");
1429     free(p); appData.results = q;
1430     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1431        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1432       int round = appData.defaultMatchGames * appData.tourneyType;
1433       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1434          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1435         UnloadEngine(&first);  // next game belongs to other pairing;
1436         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1437     }
1438     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1439 }
1440
1441 void
1442 MatchEvent (int mode)
1443 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1444         int dummy;
1445         if(matchMode) { // already in match mode: switch it off
1446             abortMatch = TRUE;
1447             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1448             return;
1449         }
1450 //      if(gameMode != BeginningOfGame) {
1451 //          DisplayError(_("You can only start a match from the initial position."), 0);
1452 //          return;
1453 //      }
1454         abortMatch = FALSE;
1455         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1456         /* Set up machine vs. machine match */
1457         nextGame = 0;
1458         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1459         if(appData.tourneyFile[0]) {
1460             ReserveGame(-1, 0);
1461             if(nextGame > appData.matchGames) {
1462                 char buf[MSG_SIZ];
1463                 if(strchr(appData.results, '*') == NULL) {
1464                     FILE *f;
1465                     appData.tourneyCycles++;
1466                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1467                         fclose(f);
1468                         NextTourneyGame(-1, &dummy);
1469                         ReserveGame(-1, 0);
1470                         if(nextGame <= appData.matchGames) {
1471                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1472                             matchMode = mode;
1473                             ScheduleDelayedEvent(NextMatchGame, 10000);
1474                             return;
1475                         }
1476                     }
1477                 }
1478                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1479                 DisplayError(buf, 0);
1480                 appData.tourneyFile[0] = 0;
1481                 return;
1482             }
1483         } else
1484         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1485             DisplayFatalError(_("Can't have a match with no chess programs"),
1486                               0, 2);
1487             return;
1488         }
1489         matchMode = mode;
1490         matchGame = roundNr = 1;
1491         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1492         NextMatchGame();
1493 }
1494
1495 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1496
1497 void
1498 InitBackEnd3 P((void))
1499 {
1500     GameMode initialMode;
1501     char buf[MSG_SIZ];
1502     int err, len;
1503
1504     InitChessProgram(&first, startedFromSetupPosition);
1505
1506     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1507         free(programVersion);
1508         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1509         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1510         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1511     }
1512
1513     if (appData.icsActive) {
1514 #ifdef WIN32
1515         /* [DM] Make a console window if needed [HGM] merged ifs */
1516         ConsoleCreate();
1517 #endif
1518         err = establish();
1519         if (err != 0)
1520           {
1521             if (*appData.icsCommPort != NULLCHAR)
1522               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1523                              appData.icsCommPort);
1524             else
1525               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1526                         appData.icsHost, appData.icsPort);
1527
1528             if( (len >= MSG_SIZ) && appData.debugMode )
1529               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1530
1531             DisplayFatalError(buf, err, 1);
1532             return;
1533         }
1534         SetICSMode();
1535         telnetISR =
1536           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1537         fromUserISR =
1538           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1539         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1540             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1541     } else if (appData.noChessProgram) {
1542         SetNCPMode();
1543     } else {
1544         SetGNUMode();
1545     }
1546
1547     if (*appData.cmailGameName != NULLCHAR) {
1548         SetCmailMode();
1549         OpenLoopback(&cmailPR);
1550         cmailISR =
1551           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1552     }
1553
1554     ThawUI();
1555     DisplayMessage("", "");
1556     if (StrCaseCmp(appData.initialMode, "") == 0) {
1557       initialMode = BeginningOfGame;
1558       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1559         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1560         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1561         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1562         ModeHighlight();
1563       }
1564     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1565       initialMode = TwoMachinesPlay;
1566     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1567       initialMode = AnalyzeFile;
1568     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1569       initialMode = AnalyzeMode;
1570     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1571       initialMode = MachinePlaysWhite;
1572     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1573       initialMode = MachinePlaysBlack;
1574     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1575       initialMode = EditGame;
1576     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1577       initialMode = EditPosition;
1578     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1579       initialMode = Training;
1580     } else {
1581       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1582       if( (len >= MSG_SIZ) && appData.debugMode )
1583         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1584
1585       DisplayFatalError(buf, 0, 2);
1586       return;
1587     }
1588
1589     if (appData.matchMode) {
1590         if(appData.tourneyFile[0]) { // start tourney from command line
1591             FILE *f;
1592             if(f = fopen(appData.tourneyFile, "r")) {
1593                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1594                 fclose(f);
1595                 appData.clockMode = TRUE;
1596                 SetGNUMode();
1597             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1598         }
1599         MatchEvent(TRUE);
1600     } else if (*appData.cmailGameName != NULLCHAR) {
1601         /* Set up cmail mode */
1602         ReloadCmailMsgEvent(TRUE);
1603     } else {
1604         /* Set up other modes */
1605         if (initialMode == AnalyzeFile) {
1606           if (*appData.loadGameFile == NULLCHAR) {
1607             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1608             return;
1609           }
1610         }
1611         if (*appData.loadGameFile != NULLCHAR) {
1612             (void) LoadGameFromFile(appData.loadGameFile,
1613                                     appData.loadGameIndex,
1614                                     appData.loadGameFile, TRUE);
1615         } else if (*appData.loadPositionFile != NULLCHAR) {
1616             (void) LoadPositionFromFile(appData.loadPositionFile,
1617                                         appData.loadPositionIndex,
1618                                         appData.loadPositionFile);
1619             /* [HGM] try to make self-starting even after FEN load */
1620             /* to allow automatic setup of fairy variants with wtm */
1621             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1622                 gameMode = BeginningOfGame;
1623                 setboardSpoiledMachineBlack = 1;
1624             }
1625             /* [HGM] loadPos: make that every new game uses the setup */
1626             /* from file as long as we do not switch variant          */
1627             if(!blackPlaysFirst) {
1628                 startedFromPositionFile = TRUE;
1629                 CopyBoard(filePosition, boards[0]);
1630             }
1631         }
1632         if (initialMode == AnalyzeMode) {
1633           if (appData.noChessProgram) {
1634             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1635             return;
1636           }
1637           if (appData.icsActive) {
1638             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1639             return;
1640           }
1641           AnalyzeModeEvent();
1642         } else if (initialMode == AnalyzeFile) {
1643           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1644           ShowThinkingEvent();
1645           AnalyzeFileEvent();
1646           AnalysisPeriodicEvent(1);
1647         } else if (initialMode == MachinePlaysWhite) {
1648           if (appData.noChessProgram) {
1649             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1650                               0, 2);
1651             return;
1652           }
1653           if (appData.icsActive) {
1654             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1655                               0, 2);
1656             return;
1657           }
1658           MachineWhiteEvent();
1659         } else if (initialMode == MachinePlaysBlack) {
1660           if (appData.noChessProgram) {
1661             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1662                               0, 2);
1663             return;
1664           }
1665           if (appData.icsActive) {
1666             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1667                               0, 2);
1668             return;
1669           }
1670           MachineBlackEvent();
1671         } else if (initialMode == TwoMachinesPlay) {
1672           if (appData.noChessProgram) {
1673             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1674                               0, 2);
1675             return;
1676           }
1677           if (appData.icsActive) {
1678             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1679                               0, 2);
1680             return;
1681           }
1682           TwoMachinesEvent();
1683         } else if (initialMode == EditGame) {
1684           EditGameEvent();
1685         } else if (initialMode == EditPosition) {
1686           EditPositionEvent();
1687         } else if (initialMode == Training) {
1688           if (*appData.loadGameFile == NULLCHAR) {
1689             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1690             return;
1691           }
1692           TrainingEvent();
1693         }
1694     }
1695 }
1696
1697 void
1698 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1699 {
1700     DisplayBook(current+1);
1701
1702     MoveHistorySet( movelist, first, last, current, pvInfoList );
1703
1704     EvalGraphSet( first, last, current, pvInfoList );
1705
1706     MakeEngineOutputTitle();
1707 }
1708
1709 /*
1710  * Establish will establish a contact to a remote host.port.
1711  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1712  *  used to talk to the host.
1713  * Returns 0 if okay, error code if not.
1714  */
1715 int
1716 establish ()
1717 {
1718     char buf[MSG_SIZ];
1719
1720     if (*appData.icsCommPort != NULLCHAR) {
1721         /* Talk to the host through a serial comm port */
1722         return OpenCommPort(appData.icsCommPort, &icsPR);
1723
1724     } else if (*appData.gateway != NULLCHAR) {
1725         if (*appData.remoteShell == NULLCHAR) {
1726             /* Use the rcmd protocol to run telnet program on a gateway host */
1727             snprintf(buf, sizeof(buf), "%s %s %s",
1728                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1729             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1730
1731         } else {
1732             /* Use the rsh program to run telnet program on a gateway host */
1733             if (*appData.remoteUser == NULLCHAR) {
1734                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1735                         appData.gateway, appData.telnetProgram,
1736                         appData.icsHost, appData.icsPort);
1737             } else {
1738                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1739                         appData.remoteShell, appData.gateway,
1740                         appData.remoteUser, appData.telnetProgram,
1741                         appData.icsHost, appData.icsPort);
1742             }
1743             return StartChildProcess(buf, "", &icsPR);
1744
1745         }
1746     } else if (appData.useTelnet) {
1747         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1748
1749     } else {
1750         /* TCP socket interface differs somewhat between
1751            Unix and NT; handle details in the front end.
1752            */
1753         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1754     }
1755 }
1756
1757 void
1758 EscapeExpand (char *p, char *q)
1759 {       // [HGM] initstring: routine to shape up string arguments
1760         while(*p++ = *q++) if(p[-1] == '\\')
1761             switch(*q++) {
1762                 case 'n': p[-1] = '\n'; break;
1763                 case 'r': p[-1] = '\r'; break;
1764                 case 't': p[-1] = '\t'; break;
1765                 case '\\': p[-1] = '\\'; break;
1766                 case 0: *p = 0; return;
1767                 default: p[-1] = q[-1]; break;
1768             }
1769 }
1770
1771 void
1772 show_bytes (FILE *fp, char *buf, int count)
1773 {
1774     while (count--) {
1775         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1776             fprintf(fp, "\\%03o", *buf & 0xff);
1777         } else {
1778             putc(*buf, fp);
1779         }
1780         buf++;
1781     }
1782     fflush(fp);
1783 }
1784
1785 /* Returns an errno value */
1786 int
1787 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1788 {
1789     char buf[8192], *p, *q, *buflim;
1790     int left, newcount, outcount;
1791
1792     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1793         *appData.gateway != NULLCHAR) {
1794         if (appData.debugMode) {
1795             fprintf(debugFP, ">ICS: ");
1796             show_bytes(debugFP, message, count);
1797             fprintf(debugFP, "\n");
1798         }
1799         return OutputToProcess(pr, message, count, outError);
1800     }
1801
1802     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1803     p = message;
1804     q = buf;
1805     left = count;
1806     newcount = 0;
1807     while (left) {
1808         if (q >= buflim) {
1809             if (appData.debugMode) {
1810                 fprintf(debugFP, ">ICS: ");
1811                 show_bytes(debugFP, buf, newcount);
1812                 fprintf(debugFP, "\n");
1813             }
1814             outcount = OutputToProcess(pr, buf, newcount, outError);
1815             if (outcount < newcount) return -1; /* to be sure */
1816             q = buf;
1817             newcount = 0;
1818         }
1819         if (*p == '\n') {
1820             *q++ = '\r';
1821             newcount++;
1822         } else if (((unsigned char) *p) == TN_IAC) {
1823             *q++ = (char) TN_IAC;
1824             newcount ++;
1825         }
1826         *q++ = *p++;
1827         newcount++;
1828         left--;
1829     }
1830     if (appData.debugMode) {
1831         fprintf(debugFP, ">ICS: ");
1832         show_bytes(debugFP, buf, newcount);
1833         fprintf(debugFP, "\n");
1834     }
1835     outcount = OutputToProcess(pr, buf, newcount, outError);
1836     if (outcount < newcount) return -1; /* to be sure */
1837     return count;
1838 }
1839
1840 void
1841 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1842 {
1843     int outError, outCount;
1844     static int gotEof = 0;
1845
1846     /* Pass data read from player on to ICS */
1847     if (count > 0) {
1848         gotEof = 0;
1849         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1850         if (outCount < count) {
1851             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1852         }
1853     } else if (count < 0) {
1854         RemoveInputSource(isr);
1855         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1856     } else if (gotEof++ > 0) {
1857         RemoveInputSource(isr);
1858         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1859     }
1860 }
1861
1862 void
1863 KeepAlive ()
1864 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1865     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1866     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1867     SendToICS("date\n");
1868     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1869 }
1870
1871 /* added routine for printf style output to ics */
1872 void
1873 ics_printf (char *format, ...)
1874 {
1875     char buffer[MSG_SIZ];
1876     va_list args;
1877
1878     va_start(args, format);
1879     vsnprintf(buffer, sizeof(buffer), format, args);
1880     buffer[sizeof(buffer)-1] = '\0';
1881     SendToICS(buffer);
1882     va_end(args);
1883 }
1884
1885 void
1886 SendToICS (char *s)
1887 {
1888     int count, outCount, outError;
1889
1890     if (icsPR == NoProc) return;
1891
1892     count = strlen(s);
1893     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1894     if (outCount < count) {
1895         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1896     }
1897 }
1898
1899 /* This is used for sending logon scripts to the ICS. Sending
1900    without a delay causes problems when using timestamp on ICC
1901    (at least on my machine). */
1902 void
1903 SendToICSDelayed (char *s, long msdelay)
1904 {
1905     int count, outCount, outError;
1906
1907     if (icsPR == NoProc) return;
1908
1909     count = strlen(s);
1910     if (appData.debugMode) {
1911         fprintf(debugFP, ">ICS: ");
1912         show_bytes(debugFP, s, count);
1913         fprintf(debugFP, "\n");
1914     }
1915     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1916                                       msdelay);
1917     if (outCount < count) {
1918         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1919     }
1920 }
1921
1922
1923 /* Remove all highlighting escape sequences in s
1924    Also deletes any suffix starting with '('
1925    */
1926 char *
1927 StripHighlightAndTitle (char *s)
1928 {
1929     static char retbuf[MSG_SIZ];
1930     char *p = retbuf;
1931
1932     while (*s != NULLCHAR) {
1933         while (*s == '\033') {
1934             while (*s != NULLCHAR && !isalpha(*s)) s++;
1935             if (*s != NULLCHAR) s++;
1936         }
1937         while (*s != NULLCHAR && *s != '\033') {
1938             if (*s == '(' || *s == '[') {
1939                 *p = NULLCHAR;
1940                 return retbuf;
1941             }
1942             *p++ = *s++;
1943         }
1944     }
1945     *p = NULLCHAR;
1946     return retbuf;
1947 }
1948
1949 /* Remove all highlighting escape sequences in s */
1950 char *
1951 StripHighlight (char *s)
1952 {
1953     static char retbuf[MSG_SIZ];
1954     char *p = retbuf;
1955
1956     while (*s != NULLCHAR) {
1957         while (*s == '\033') {
1958             while (*s != NULLCHAR && !isalpha(*s)) s++;
1959             if (*s != NULLCHAR) s++;
1960         }
1961         while (*s != NULLCHAR && *s != '\033') {
1962             *p++ = *s++;
1963         }
1964     }
1965     *p = NULLCHAR;
1966     return retbuf;
1967 }
1968
1969 char *variantNames[] = VARIANT_NAMES;
1970 char *
1971 VariantName (VariantClass v)
1972 {
1973     return variantNames[v];
1974 }
1975
1976
1977 /* Identify a variant from the strings the chess servers use or the
1978    PGN Variant tag names we use. */
1979 VariantClass
1980 StringToVariant (char *e)
1981 {
1982     char *p;
1983     int wnum = -1;
1984     VariantClass v = VariantNormal;
1985     int i, found = FALSE;
1986     char buf[MSG_SIZ];
1987     int len;
1988
1989     if (!e) return v;
1990
1991     /* [HGM] skip over optional board-size prefixes */
1992     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1993         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1994         while( *e++ != '_');
1995     }
1996
1997     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1998         v = VariantNormal;
1999         found = TRUE;
2000     } else
2001     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2002       if (StrCaseStr(e, variantNames[i])) {
2003         v = (VariantClass) i;
2004         found = TRUE;
2005         break;
2006       }
2007     }
2008
2009     if (!found) {
2010       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2011           || StrCaseStr(e, "wild/fr")
2012           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2013         v = VariantFischeRandom;
2014       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2015                  (i = 1, p = StrCaseStr(e, "w"))) {
2016         p += i;
2017         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2018         if (isdigit(*p)) {
2019           wnum = atoi(p);
2020         } else {
2021           wnum = -1;
2022         }
2023         switch (wnum) {
2024         case 0: /* FICS only, actually */
2025         case 1:
2026           /* Castling legal even if K starts on d-file */
2027           v = VariantWildCastle;
2028           break;
2029         case 2:
2030         case 3:
2031         case 4:
2032           /* Castling illegal even if K & R happen to start in
2033              normal positions. */
2034           v = VariantNoCastle;
2035           break;
2036         case 5:
2037         case 7:
2038         case 8:
2039         case 10:
2040         case 11:
2041         case 12:
2042         case 13:
2043         case 14:
2044         case 15:
2045         case 18:
2046         case 19:
2047           /* Castling legal iff K & R start in normal positions */
2048           v = VariantNormal;
2049           break;
2050         case 6:
2051         case 20:
2052         case 21:
2053           /* Special wilds for position setup; unclear what to do here */
2054           v = VariantLoadable;
2055           break;
2056         case 9:
2057           /* Bizarre ICC game */
2058           v = VariantTwoKings;
2059           break;
2060         case 16:
2061           v = VariantKriegspiel;
2062           break;
2063         case 17:
2064           v = VariantLosers;
2065           break;
2066         case 22:
2067           v = VariantFischeRandom;
2068           break;
2069         case 23:
2070           v = VariantCrazyhouse;
2071           break;
2072         case 24:
2073           v = VariantBughouse;
2074           break;
2075         case 25:
2076           v = Variant3Check;
2077           break;
2078         case 26:
2079           /* Not quite the same as FICS suicide! */
2080           v = VariantGiveaway;
2081           break;
2082         case 27:
2083           v = VariantAtomic;
2084           break;
2085         case 28:
2086           v = VariantShatranj;
2087           break;
2088
2089         /* Temporary names for future ICC types.  The name *will* change in
2090            the next xboard/WinBoard release after ICC defines it. */
2091         case 29:
2092           v = Variant29;
2093           break;
2094         case 30:
2095           v = Variant30;
2096           break;
2097         case 31:
2098           v = Variant31;
2099           break;
2100         case 32:
2101           v = Variant32;
2102           break;
2103         case 33:
2104           v = Variant33;
2105           break;
2106         case 34:
2107           v = Variant34;
2108           break;
2109         case 35:
2110           v = Variant35;
2111           break;
2112         case 36:
2113           v = Variant36;
2114           break;
2115         case 37:
2116           v = VariantShogi;
2117           break;
2118         case 38:
2119           v = VariantXiangqi;
2120           break;
2121         case 39:
2122           v = VariantCourier;
2123           break;
2124         case 40:
2125           v = VariantGothic;
2126           break;
2127         case 41:
2128           v = VariantCapablanca;
2129           break;
2130         case 42:
2131           v = VariantKnightmate;
2132           break;
2133         case 43:
2134           v = VariantFairy;
2135           break;
2136         case 44:
2137           v = VariantCylinder;
2138           break;
2139         case 45:
2140           v = VariantFalcon;
2141           break;
2142         case 46:
2143           v = VariantCapaRandom;
2144           break;
2145         case 47:
2146           v = VariantBerolina;
2147           break;
2148         case 48:
2149           v = VariantJanus;
2150           break;
2151         case 49:
2152           v = VariantSuper;
2153           break;
2154         case 50:
2155           v = VariantGreat;
2156           break;
2157         case -1:
2158           /* Found "wild" or "w" in the string but no number;
2159              must assume it's normal chess. */
2160           v = VariantNormal;
2161           break;
2162         default:
2163           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2164           if( (len >= MSG_SIZ) && appData.debugMode )
2165             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2166
2167           DisplayError(buf, 0);
2168           v = VariantUnknown;
2169           break;
2170         }
2171       }
2172     }
2173     if (appData.debugMode) {
2174       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2175               e, wnum, VariantName(v));
2176     }
2177     return v;
2178 }
2179
2180 static int leftover_start = 0, leftover_len = 0;
2181 char star_match[STAR_MATCH_N][MSG_SIZ];
2182
2183 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2184    advance *index beyond it, and set leftover_start to the new value of
2185    *index; else return FALSE.  If pattern contains the character '*', it
2186    matches any sequence of characters not containing '\r', '\n', or the
2187    character following the '*' (if any), and the matched sequence(s) are
2188    copied into star_match.
2189    */
2190 int
2191 looking_at ( char *buf, int *index, char *pattern)
2192 {
2193     char *bufp = &buf[*index], *patternp = pattern;
2194     int star_count = 0;
2195     char *matchp = star_match[0];
2196
2197     for (;;) {
2198         if (*patternp == NULLCHAR) {
2199             *index = leftover_start = bufp - buf;
2200             *matchp = NULLCHAR;
2201             return TRUE;
2202         }
2203         if (*bufp == NULLCHAR) return FALSE;
2204         if (*patternp == '*') {
2205             if (*bufp == *(patternp + 1)) {
2206                 *matchp = NULLCHAR;
2207                 matchp = star_match[++star_count];
2208                 patternp += 2;
2209                 bufp++;
2210                 continue;
2211             } else if (*bufp == '\n' || *bufp == '\r') {
2212                 patternp++;
2213                 if (*patternp == NULLCHAR)
2214                   continue;
2215                 else
2216                   return FALSE;
2217             } else {
2218                 *matchp++ = *bufp++;
2219                 continue;
2220             }
2221         }
2222         if (*patternp != *bufp) return FALSE;
2223         patternp++;
2224         bufp++;
2225     }
2226 }
2227
2228 void
2229 SendToPlayer (char *data, int length)
2230 {
2231     int error, outCount;
2232     outCount = OutputToProcess(NoProc, data, length, &error);
2233     if (outCount < length) {
2234         DisplayFatalError(_("Error writing to display"), error, 1);
2235     }
2236 }
2237
2238 void
2239 PackHolding (char packed[], char *holding)
2240 {
2241     char *p = holding;
2242     char *q = packed;
2243     int runlength = 0;
2244     int curr = 9999;
2245     do {
2246         if (*p == curr) {
2247             runlength++;
2248         } else {
2249             switch (runlength) {
2250               case 0:
2251                 break;
2252               case 1:
2253                 *q++ = curr;
2254                 break;
2255               case 2:
2256                 *q++ = curr;
2257                 *q++ = curr;
2258                 break;
2259               default:
2260                 sprintf(q, "%d", runlength);
2261                 while (*q) q++;
2262                 *q++ = curr;
2263                 break;
2264             }
2265             runlength = 1;
2266             curr = *p;
2267         }
2268     } while (*p++);
2269     *q = NULLCHAR;
2270 }
2271
2272 /* Telnet protocol requests from the front end */
2273 void
2274 TelnetRequest (unsigned char ddww, unsigned char option)
2275 {
2276     unsigned char msg[3];
2277     int outCount, outError;
2278
2279     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2280
2281     if (appData.debugMode) {
2282         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2283         switch (ddww) {
2284           case TN_DO:
2285             ddwwStr = "DO";
2286             break;
2287           case TN_DONT:
2288             ddwwStr = "DONT";
2289             break;
2290           case TN_WILL:
2291             ddwwStr = "WILL";
2292             break;
2293           case TN_WONT:
2294             ddwwStr = "WONT";
2295             break;
2296           default:
2297             ddwwStr = buf1;
2298             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2299             break;
2300         }
2301         switch (option) {
2302           case TN_ECHO:
2303             optionStr = "ECHO";
2304             break;
2305           default:
2306             optionStr = buf2;
2307             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2308             break;
2309         }
2310         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2311     }
2312     msg[0] = TN_IAC;
2313     msg[1] = ddww;
2314     msg[2] = option;
2315     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2316     if (outCount < 3) {
2317         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2318     }
2319 }
2320
2321 void
2322 DoEcho ()
2323 {
2324     if (!appData.icsActive) return;
2325     TelnetRequest(TN_DO, TN_ECHO);
2326 }
2327
2328 void
2329 DontEcho ()
2330 {
2331     if (!appData.icsActive) return;
2332     TelnetRequest(TN_DONT, TN_ECHO);
2333 }
2334
2335 void
2336 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2337 {
2338     /* put the holdings sent to us by the server on the board holdings area */
2339     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2340     char p;
2341     ChessSquare piece;
2342
2343     if(gameInfo.holdingsWidth < 2)  return;
2344     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2345         return; // prevent overwriting by pre-board holdings
2346
2347     if( (int)lowestPiece >= BlackPawn ) {
2348         holdingsColumn = 0;
2349         countsColumn = 1;
2350         holdingsStartRow = BOARD_HEIGHT-1;
2351         direction = -1;
2352     } else {
2353         holdingsColumn = BOARD_WIDTH-1;
2354         countsColumn = BOARD_WIDTH-2;
2355         holdingsStartRow = 0;
2356         direction = 1;
2357     }
2358
2359     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2360         board[i][holdingsColumn] = EmptySquare;
2361         board[i][countsColumn]   = (ChessSquare) 0;
2362     }
2363     while( (p=*holdings++) != NULLCHAR ) {
2364         piece = CharToPiece( ToUpper(p) );
2365         if(piece == EmptySquare) continue;
2366         /*j = (int) piece - (int) WhitePawn;*/
2367         j = PieceToNumber(piece);
2368         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2369         if(j < 0) continue;               /* should not happen */
2370         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2371         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2372         board[holdingsStartRow+j*direction][countsColumn]++;
2373     }
2374 }
2375
2376
2377 void
2378 VariantSwitch (Board board, VariantClass newVariant)
2379 {
2380    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2381    static Board oldBoard;
2382
2383    startedFromPositionFile = FALSE;
2384    if(gameInfo.variant == newVariant) return;
2385
2386    /* [HGM] This routine is called each time an assignment is made to
2387     * gameInfo.variant during a game, to make sure the board sizes
2388     * are set to match the new variant. If that means adding or deleting
2389     * holdings, we shift the playing board accordingly
2390     * This kludge is needed because in ICS observe mode, we get boards
2391     * of an ongoing game without knowing the variant, and learn about the
2392     * latter only later. This can be because of the move list we requested,
2393     * in which case the game history is refilled from the beginning anyway,
2394     * but also when receiving holdings of a crazyhouse game. In the latter
2395     * case we want to add those holdings to the already received position.
2396     */
2397
2398
2399    if (appData.debugMode) {
2400      fprintf(debugFP, "Switch board from %s to %s\n",
2401              VariantName(gameInfo.variant), VariantName(newVariant));
2402      setbuf(debugFP, NULL);
2403    }
2404    shuffleOpenings = 0;       /* [HGM] shuffle */
2405    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2406    switch(newVariant)
2407      {
2408      case VariantShogi:
2409        newWidth = 9;  newHeight = 9;
2410        gameInfo.holdingsSize = 7;
2411      case VariantBughouse:
2412      case VariantCrazyhouse:
2413        newHoldingsWidth = 2; break;
2414      case VariantGreat:
2415        newWidth = 10;
2416      case VariantSuper:
2417        newHoldingsWidth = 2;
2418        gameInfo.holdingsSize = 8;
2419        break;
2420      case VariantGothic:
2421      case VariantCapablanca:
2422      case VariantCapaRandom:
2423        newWidth = 10;
2424      default:
2425        newHoldingsWidth = gameInfo.holdingsSize = 0;
2426      };
2427
2428    if(newWidth  != gameInfo.boardWidth  ||
2429       newHeight != gameInfo.boardHeight ||
2430       newHoldingsWidth != gameInfo.holdingsWidth ) {
2431
2432      /* shift position to new playing area, if needed */
2433      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2434        for(i=0; i<BOARD_HEIGHT; i++)
2435          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2436            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2437              board[i][j];
2438        for(i=0; i<newHeight; i++) {
2439          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2440          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2441        }
2442      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2443        for(i=0; i<BOARD_HEIGHT; i++)
2444          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2445            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2446              board[i][j];
2447      }
2448      board[HOLDINGS_SET] = 0;
2449      gameInfo.boardWidth  = newWidth;
2450      gameInfo.boardHeight = newHeight;
2451      gameInfo.holdingsWidth = newHoldingsWidth;
2452      gameInfo.variant = newVariant;
2453      InitDrawingSizes(-2, 0);
2454    } else gameInfo.variant = newVariant;
2455    CopyBoard(oldBoard, board);   // remember correctly formatted board
2456      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2457    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2458 }
2459
2460 static int loggedOn = FALSE;
2461
2462 /*-- Game start info cache: --*/
2463 int gs_gamenum;
2464 char gs_kind[MSG_SIZ];
2465 static char player1Name[128] = "";
2466 static char player2Name[128] = "";
2467 static char cont_seq[] = "\n\\   ";
2468 static int player1Rating = -1;
2469 static int player2Rating = -1;
2470 /*----------------------------*/
2471
2472 ColorClass curColor = ColorNormal;
2473 int suppressKibitz = 0;
2474
2475 // [HGM] seekgraph
2476 Boolean soughtPending = FALSE;
2477 Boolean seekGraphUp;
2478 #define MAX_SEEK_ADS 200
2479 #define SQUARE 0x80
2480 char *seekAdList[MAX_SEEK_ADS];
2481 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2482 float tcList[MAX_SEEK_ADS];
2483 char colorList[MAX_SEEK_ADS];
2484 int nrOfSeekAds = 0;
2485 int minRating = 1010, maxRating = 2800;
2486 int hMargin = 10, vMargin = 20, h, w;
2487 extern int squareSize, lineGap;
2488
2489 void
2490 PlotSeekAd (int i)
2491 {
2492         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2493         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2494         if(r < minRating+100 && r >=0 ) r = minRating+100;
2495         if(r > maxRating) r = maxRating;
2496         if(tc < 1.f) tc = 1.f;
2497         if(tc > 95.f) tc = 95.f;
2498         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2499         y = ((double)r - minRating)/(maxRating - minRating)
2500             * (h-vMargin-squareSize/8-1) + vMargin;
2501         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2502         if(strstr(seekAdList[i], " u ")) color = 1;
2503         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2504            !strstr(seekAdList[i], "bullet") &&
2505            !strstr(seekAdList[i], "blitz") &&
2506            !strstr(seekAdList[i], "standard") ) color = 2;
2507         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2508         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2509 }
2510
2511 void
2512 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2513 {
2514         char buf[MSG_SIZ], *ext = "";
2515         VariantClass v = StringToVariant(type);
2516         if(strstr(type, "wild")) {
2517             ext = type + 4; // append wild number
2518             if(v == VariantFischeRandom) type = "chess960"; else
2519             if(v == VariantLoadable) type = "setup"; else
2520             type = VariantName(v);
2521         }
2522         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2523         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2524             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2525             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2526             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2527             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2528             seekNrList[nrOfSeekAds] = nr;
2529             zList[nrOfSeekAds] = 0;
2530             seekAdList[nrOfSeekAds++] = StrSave(buf);
2531             if(plot) PlotSeekAd(nrOfSeekAds-1);
2532         }
2533 }
2534
2535 void
2536 EraseSeekDot (int i)
2537 {
2538     int x = xList[i], y = yList[i], d=squareSize/4, k;
2539     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2540     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2541     // now replot every dot that overlapped
2542     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2543         int xx = xList[k], yy = yList[k];
2544         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2545             DrawSeekDot(xx, yy, colorList[k]);
2546     }
2547 }
2548
2549 void
2550 RemoveSeekAd (int nr)
2551 {
2552         int i;
2553         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2554             EraseSeekDot(i);
2555             if(seekAdList[i]) free(seekAdList[i]);
2556             seekAdList[i] = seekAdList[--nrOfSeekAds];
2557             seekNrList[i] = seekNrList[nrOfSeekAds];
2558             ratingList[i] = ratingList[nrOfSeekAds];
2559             colorList[i]  = colorList[nrOfSeekAds];
2560             tcList[i] = tcList[nrOfSeekAds];
2561             xList[i]  = xList[nrOfSeekAds];
2562             yList[i]  = yList[nrOfSeekAds];
2563             zList[i]  = zList[nrOfSeekAds];
2564             seekAdList[nrOfSeekAds] = NULL;
2565             break;
2566         }
2567 }
2568
2569 Boolean
2570 MatchSoughtLine (char *line)
2571 {
2572     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2573     int nr, base, inc, u=0; char dummy;
2574
2575     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2576        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2577        (u=1) &&
2578        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2579         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2580         // match: compact and save the line
2581         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2582         return TRUE;
2583     }
2584     return FALSE;
2585 }
2586
2587 int
2588 DrawSeekGraph ()
2589 {
2590     int i;
2591     if(!seekGraphUp) return FALSE;
2592     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2593     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2594
2595     DrawSeekBackground(0, 0, w, h);
2596     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2597     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2598     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2599         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2600         yy = h-1-yy;
2601         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2602         if(i%500 == 0) {
2603             char buf[MSG_SIZ];
2604             snprintf(buf, MSG_SIZ, "%d", i);
2605             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2606         }
2607     }
2608     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2609     for(i=1; i<100; i+=(i<10?1:5)) {
2610         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2611         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2612         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2613             char buf[MSG_SIZ];
2614             snprintf(buf, MSG_SIZ, "%d", i);
2615             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2616         }
2617     }
2618     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2619     return TRUE;
2620 }
2621
2622 int
2623 SeekGraphClick (ClickType click, int x, int y, int moving)
2624 {
2625     static int lastDown = 0, displayed = 0, lastSecond;
2626     if(y < 0) return FALSE;
2627     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2628         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2629         if(!seekGraphUp) return FALSE;
2630         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2631         DrawPosition(TRUE, NULL);
2632         return TRUE;
2633     }
2634     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2635         if(click == Release || moving) return FALSE;
2636         nrOfSeekAds = 0;
2637         soughtPending = TRUE;
2638         SendToICS(ics_prefix);
2639         SendToICS("sought\n"); // should this be "sought all"?
2640     } else { // issue challenge based on clicked ad
2641         int dist = 10000; int i, closest = 0, second = 0;
2642         for(i=0; i<nrOfSeekAds; i++) {
2643             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2644             if(d < dist) { dist = d; closest = i; }
2645             second += (d - zList[i] < 120); // count in-range ads
2646             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2647         }
2648         if(dist < 120) {
2649             char buf[MSG_SIZ];
2650             second = (second > 1);
2651             if(displayed != closest || second != lastSecond) {
2652                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2653                 lastSecond = second; displayed = closest;
2654             }
2655             if(click == Press) {
2656                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2657                 lastDown = closest;
2658                 return TRUE;
2659             } // on press 'hit', only show info
2660             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2661             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2662             SendToICS(ics_prefix);
2663             SendToICS(buf);
2664             return TRUE; // let incoming board of started game pop down the graph
2665         } else if(click == Release) { // release 'miss' is ignored
2666             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2667             if(moving == 2) { // right up-click
2668                 nrOfSeekAds = 0; // refresh graph
2669                 soughtPending = TRUE;
2670                 SendToICS(ics_prefix);
2671                 SendToICS("sought\n"); // should this be "sought all"?
2672             }
2673             return TRUE;
2674         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2675         // press miss or release hit 'pop down' seek graph
2676         seekGraphUp = FALSE;
2677         DrawPosition(TRUE, NULL);
2678     }
2679     return TRUE;
2680 }
2681
2682 void
2683 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2684 {
2685 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2686 #define STARTED_NONE 0
2687 #define STARTED_MOVES 1
2688 #define STARTED_BOARD 2
2689 #define STARTED_OBSERVE 3
2690 #define STARTED_HOLDINGS 4
2691 #define STARTED_CHATTER 5
2692 #define STARTED_COMMENT 6
2693 #define STARTED_MOVES_NOHIDE 7
2694
2695     static int started = STARTED_NONE;
2696     static char parse[20000];
2697     static int parse_pos = 0;
2698     static char buf[BUF_SIZE + 1];
2699     static int firstTime = TRUE, intfSet = FALSE;
2700     static ColorClass prevColor = ColorNormal;
2701     static int savingComment = FALSE;
2702     static int cmatch = 0; // continuation sequence match
2703     char *bp;
2704     char str[MSG_SIZ];
2705     int i, oldi;
2706     int buf_len;
2707     int next_out;
2708     int tkind;
2709     int backup;    /* [DM] For zippy color lines */
2710     char *p;
2711     char talker[MSG_SIZ]; // [HGM] chat
2712     int channel;
2713
2714     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2715
2716     if (appData.debugMode) {
2717       if (!error) {
2718         fprintf(debugFP, "<ICS: ");
2719         show_bytes(debugFP, data, count);
2720         fprintf(debugFP, "\n");
2721       }
2722     }
2723
2724     if (appData.debugMode) { int f = forwardMostMove;
2725         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2726                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2727                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2728     }
2729     if (count > 0) {
2730         /* If last read ended with a partial line that we couldn't parse,
2731            prepend it to the new read and try again. */
2732         if (leftover_len > 0) {
2733             for (i=0; i<leftover_len; i++)
2734               buf[i] = buf[leftover_start + i];
2735         }
2736
2737     /* copy new characters into the buffer */
2738     bp = buf + leftover_len;
2739     buf_len=leftover_len;
2740     for (i=0; i<count; i++)
2741     {
2742         // ignore these
2743         if (data[i] == '\r')
2744             continue;
2745
2746         // join lines split by ICS?
2747         if (!appData.noJoin)
2748         {
2749             /*
2750                 Joining just consists of finding matches against the
2751                 continuation sequence, and discarding that sequence
2752                 if found instead of copying it.  So, until a match
2753                 fails, there's nothing to do since it might be the
2754                 complete sequence, and thus, something we don't want
2755                 copied.
2756             */
2757             if (data[i] == cont_seq[cmatch])
2758             {
2759                 cmatch++;
2760                 if (cmatch == strlen(cont_seq))
2761                 {
2762                     cmatch = 0; // complete match.  just reset the counter
2763
2764                     /*
2765                         it's possible for the ICS to not include the space
2766                         at the end of the last word, making our [correct]
2767                         join operation fuse two separate words.  the server
2768                         does this when the space occurs at the width setting.
2769                     */
2770                     if (!buf_len || buf[buf_len-1] != ' ')
2771                     {
2772                         *bp++ = ' ';
2773                         buf_len++;
2774                     }
2775                 }
2776                 continue;
2777             }
2778             else if (cmatch)
2779             {
2780                 /*
2781                     match failed, so we have to copy what matched before
2782                     falling through and copying this character.  In reality,
2783                     this will only ever be just the newline character, but
2784                     it doesn't hurt to be precise.
2785                 */
2786                 strncpy(bp, cont_seq, cmatch);
2787                 bp += cmatch;
2788                 buf_len += cmatch;
2789                 cmatch = 0;
2790             }
2791         }
2792
2793         // copy this char
2794         *bp++ = data[i];
2795         buf_len++;
2796     }
2797
2798         buf[buf_len] = NULLCHAR;
2799 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2800         next_out = 0;
2801         leftover_start = 0;
2802
2803         i = 0;
2804         while (i < buf_len) {
2805             /* Deal with part of the TELNET option negotiation
2806                protocol.  We refuse to do anything beyond the
2807                defaults, except that we allow the WILL ECHO option,
2808                which ICS uses to turn off password echoing when we are
2809                directly connected to it.  We reject this option
2810                if localLineEditing mode is on (always on in xboard)
2811                and we are talking to port 23, which might be a real
2812                telnet server that will try to keep WILL ECHO on permanently.
2813              */
2814             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2815                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2816                 unsigned char option;
2817                 oldi = i;
2818                 switch ((unsigned char) buf[++i]) {
2819                   case TN_WILL:
2820                     if (appData.debugMode)
2821                       fprintf(debugFP, "\n<WILL ");
2822                     switch (option = (unsigned char) buf[++i]) {
2823                       case TN_ECHO:
2824                         if (appData.debugMode)
2825                           fprintf(debugFP, "ECHO ");
2826                         /* Reply only if this is a change, according
2827                            to the protocol rules. */
2828                         if (remoteEchoOption) break;
2829                         if (appData.localLineEditing &&
2830                             atoi(appData.icsPort) == TN_PORT) {
2831                             TelnetRequest(TN_DONT, TN_ECHO);
2832                         } else {
2833                             EchoOff();
2834                             TelnetRequest(TN_DO, TN_ECHO);
2835                             remoteEchoOption = TRUE;
2836                         }
2837                         break;
2838                       default:
2839                         if (appData.debugMode)
2840                           fprintf(debugFP, "%d ", option);
2841                         /* Whatever this is, we don't want it. */
2842                         TelnetRequest(TN_DONT, option);
2843                         break;
2844                     }
2845                     break;
2846                   case TN_WONT:
2847                     if (appData.debugMode)
2848                       fprintf(debugFP, "\n<WONT ");
2849                     switch (option = (unsigned char) buf[++i]) {
2850                       case TN_ECHO:
2851                         if (appData.debugMode)
2852                           fprintf(debugFP, "ECHO ");
2853                         /* Reply only if this is a change, according
2854                            to the protocol rules. */
2855                         if (!remoteEchoOption) break;
2856                         EchoOn();
2857                         TelnetRequest(TN_DONT, TN_ECHO);
2858                         remoteEchoOption = FALSE;
2859                         break;
2860                       default:
2861                         if (appData.debugMode)
2862                           fprintf(debugFP, "%d ", (unsigned char) option);
2863                         /* Whatever this is, it must already be turned
2864                            off, because we never agree to turn on
2865                            anything non-default, so according to the
2866                            protocol rules, we don't reply. */
2867                         break;
2868                     }
2869                     break;
2870                   case TN_DO:
2871                     if (appData.debugMode)
2872                       fprintf(debugFP, "\n<DO ");
2873                     switch (option = (unsigned char) buf[++i]) {
2874                       default:
2875                         /* Whatever this is, we refuse to do it. */
2876                         if (appData.debugMode)
2877                           fprintf(debugFP, "%d ", option);
2878                         TelnetRequest(TN_WONT, option);
2879                         break;
2880                     }
2881                     break;
2882                   case TN_DONT:
2883                     if (appData.debugMode)
2884                       fprintf(debugFP, "\n<DONT ");
2885                     switch (option = (unsigned char) buf[++i]) {
2886                       default:
2887                         if (appData.debugMode)
2888                           fprintf(debugFP, "%d ", option);
2889                         /* Whatever this is, we are already not doing
2890                            it, because we never agree to do anything
2891                            non-default, so according to the protocol
2892                            rules, we don't reply. */
2893                         break;
2894                     }
2895                     break;
2896                   case TN_IAC:
2897                     if (appData.debugMode)
2898                       fprintf(debugFP, "\n<IAC ");
2899                     /* Doubled IAC; pass it through */
2900                     i--;
2901                     break;
2902                   default:
2903                     if (appData.debugMode)
2904                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2905                     /* Drop all other telnet commands on the floor */
2906                     break;
2907                 }
2908                 if (oldi > next_out)
2909                   SendToPlayer(&buf[next_out], oldi - next_out);
2910                 if (++i > next_out)
2911                   next_out = i;
2912                 continue;
2913             }
2914
2915             /* OK, this at least will *usually* work */
2916             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2917                 loggedOn = TRUE;
2918             }
2919
2920             if (loggedOn && !intfSet) {
2921                 if (ics_type == ICS_ICC) {
2922                   snprintf(str, MSG_SIZ,
2923                           "/set-quietly interface %s\n/set-quietly style 12\n",
2924                           programVersion);
2925                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2926                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2927                 } else if (ics_type == ICS_CHESSNET) {
2928                   snprintf(str, MSG_SIZ, "/style 12\n");
2929                 } else {
2930                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2931                   strcat(str, programVersion);
2932                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2933                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2934                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2935 #ifdef WIN32
2936                   strcat(str, "$iset nohighlight 1\n");
2937 #endif
2938                   strcat(str, "$iset lock 1\n$style 12\n");
2939                 }
2940                 SendToICS(str);
2941                 NotifyFrontendLogin();
2942                 intfSet = TRUE;
2943             }
2944
2945             if (started == STARTED_COMMENT) {
2946                 /* Accumulate characters in comment */
2947                 parse[parse_pos++] = buf[i];
2948                 if (buf[i] == '\n') {
2949                     parse[parse_pos] = NULLCHAR;
2950                     if(chattingPartner>=0) {
2951                         char mess[MSG_SIZ];
2952                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2953                         OutputChatMessage(chattingPartner, mess);
2954                         chattingPartner = -1;
2955                         next_out = i+1; // [HGM] suppress printing in ICS window
2956                     } else
2957                     if(!suppressKibitz) // [HGM] kibitz
2958                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2959                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2960                         int nrDigit = 0, nrAlph = 0, j;
2961                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2962                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2963                         parse[parse_pos] = NULLCHAR;
2964                         // try to be smart: if it does not look like search info, it should go to
2965                         // ICS interaction window after all, not to engine-output window.
2966                         for(j=0; j<parse_pos; j++) { // count letters and digits
2967                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2968                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2969                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2970                         }
2971                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2972                             int depth=0; float score;
2973                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2974                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2975                                 pvInfoList[forwardMostMove-1].depth = depth;
2976                                 pvInfoList[forwardMostMove-1].score = 100*score;
2977                             }
2978                             OutputKibitz(suppressKibitz, parse);
2979                         } else {
2980                             char tmp[MSG_SIZ];
2981                             if(gameMode == IcsObserving) // restore original ICS messages
2982                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2983                             else
2984                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2985                             SendToPlayer(tmp, strlen(tmp));
2986                         }
2987                         next_out = i+1; // [HGM] suppress printing in ICS window
2988                     }
2989                     started = STARTED_NONE;
2990                 } else {
2991                     /* Don't match patterns against characters in comment */
2992                     i++;
2993                     continue;
2994                 }
2995             }
2996             if (started == STARTED_CHATTER) {
2997                 if (buf[i] != '\n') {
2998                     /* Don't match patterns against characters in chatter */
2999                     i++;
3000                     continue;
3001                 }
3002                 started = STARTED_NONE;
3003                 if(suppressKibitz) next_out = i+1;
3004             }
3005
3006             /* Kludge to deal with rcmd protocol */
3007             if (firstTime && looking_at(buf, &i, "\001*")) {
3008                 DisplayFatalError(&buf[1], 0, 1);
3009                 continue;
3010             } else {
3011                 firstTime = FALSE;
3012             }
3013
3014             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3015                 ics_type = ICS_ICC;
3016                 ics_prefix = "/";
3017                 if (appData.debugMode)
3018                   fprintf(debugFP, "ics_type %d\n", ics_type);
3019                 continue;
3020             }
3021             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3022                 ics_type = ICS_FICS;
3023                 ics_prefix = "$";
3024                 if (appData.debugMode)
3025                   fprintf(debugFP, "ics_type %d\n", ics_type);
3026                 continue;
3027             }
3028             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3029                 ics_type = ICS_CHESSNET;
3030                 ics_prefix = "/";
3031                 if (appData.debugMode)
3032                   fprintf(debugFP, "ics_type %d\n", ics_type);
3033                 continue;
3034             }
3035
3036             if (!loggedOn &&
3037                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3038                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3039                  looking_at(buf, &i, "will be \"*\""))) {
3040               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3041               continue;
3042             }
3043
3044             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3045               char buf[MSG_SIZ];
3046               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3047               DisplayIcsInteractionTitle(buf);
3048               have_set_title = TRUE;
3049             }
3050
3051             /* skip finger notes */
3052             if (started == STARTED_NONE &&
3053                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3054                  (buf[i] == '1' && buf[i+1] == '0')) &&
3055                 buf[i+2] == ':' && buf[i+3] == ' ') {
3056               started = STARTED_CHATTER;
3057               i += 3;
3058               continue;
3059             }
3060
3061             oldi = i;
3062             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3063             if(appData.seekGraph) {
3064                 if(soughtPending && MatchSoughtLine(buf+i)) {
3065                     i = strstr(buf+i, "rated") - buf;
3066                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3067                     next_out = leftover_start = i;
3068                     started = STARTED_CHATTER;
3069                     suppressKibitz = TRUE;
3070                     continue;
3071                 }
3072                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3073                         && looking_at(buf, &i, "* ads displayed")) {
3074                     soughtPending = FALSE;
3075                     seekGraphUp = TRUE;
3076                     DrawSeekGraph();
3077                     continue;
3078                 }
3079                 if(appData.autoRefresh) {
3080                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3081                         int s = (ics_type == ICS_ICC); // ICC format differs
3082                         if(seekGraphUp)
3083                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3084                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3085                         looking_at(buf, &i, "*% "); // eat prompt
3086                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3087                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3088                         next_out = i; // suppress
3089                         continue;
3090                     }
3091                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3092                         char *p = star_match[0];
3093                         while(*p) {
3094                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3095                             while(*p && *p++ != ' '); // next
3096                         }
3097                         looking_at(buf, &i, "*% "); // eat prompt
3098                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3099                         next_out = i;
3100                         continue;
3101                     }
3102                 }
3103             }
3104
3105             /* skip formula vars */
3106             if (started == STARTED_NONE &&
3107                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3108               started = STARTED_CHATTER;
3109               i += 3;
3110               continue;
3111             }
3112
3113             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3114             if (appData.autoKibitz && started == STARTED_NONE &&
3115                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3116                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3117                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3118                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3119                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3120                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3121                         suppressKibitz = TRUE;
3122                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3123                         next_out = i;
3124                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3125                                 && (gameMode == IcsPlayingWhite)) ||
3126                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3127                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3128                             started = STARTED_CHATTER; // own kibitz we simply discard
3129                         else {
3130                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3131                             parse_pos = 0; parse[0] = NULLCHAR;
3132                             savingComment = TRUE;
3133                             suppressKibitz = gameMode != IcsObserving ? 2 :
3134                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3135                         }
3136                         continue;
3137                 } else
3138                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3139                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3140                          && atoi(star_match[0])) {
3141                     // suppress the acknowledgements of our own autoKibitz
3142                     char *p;
3143                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3144                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3145                     SendToPlayer(star_match[0], strlen(star_match[0]));
3146                     if(looking_at(buf, &i, "*% ")) // eat prompt
3147                         suppressKibitz = FALSE;
3148                     next_out = i;
3149                     continue;
3150                 }
3151             } // [HGM] kibitz: end of patch
3152
3153             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3154
3155             // [HGM] chat: intercept tells by users for which we have an open chat window
3156             channel = -1;
3157             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3158                                            looking_at(buf, &i, "* whispers:") ||
3159                                            looking_at(buf, &i, "* kibitzes:") ||
3160                                            looking_at(buf, &i, "* shouts:") ||
3161                                            looking_at(buf, &i, "* c-shouts:") ||
3162                                            looking_at(buf, &i, "--> * ") ||
3163                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3164                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3165                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3166                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3167                 int p;
3168                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3169                 chattingPartner = -1;
3170
3171                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3172                 for(p=0; p<MAX_CHAT; p++) {
3173                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3174                     talker[0] = '['; strcat(talker, "] ");
3175                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3176                     chattingPartner = p; break;
3177                     }
3178                 } else
3179                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3180                 for(p=0; p<MAX_CHAT; p++) {
3181                     if(!strcmp("kibitzes", chatPartner[p])) {
3182                         talker[0] = '['; strcat(talker, "] ");
3183                         chattingPartner = p; break;
3184                     }
3185                 } else
3186                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3187                 for(p=0; p<MAX_CHAT; p++) {
3188                     if(!strcmp("whispers", chatPartner[p])) {
3189                         talker[0] = '['; strcat(talker, "] ");
3190                         chattingPartner = p; break;
3191                     }
3192                 } else
3193                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3194                   if(buf[i-8] == '-' && buf[i-3] == 't')
3195                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3196                     if(!strcmp("c-shouts", chatPartner[p])) {
3197                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3198                         chattingPartner = p; break;
3199                     }
3200                   }
3201                   if(chattingPartner < 0)
3202                   for(p=0; p<MAX_CHAT; p++) {
3203                     if(!strcmp("shouts", chatPartner[p])) {
3204                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3205                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3206                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3207                         chattingPartner = p; break;
3208                     }
3209                   }
3210                 }
3211                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3212                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3213                     talker[0] = 0; Colorize(ColorTell, FALSE);
3214                     chattingPartner = p; break;
3215                 }
3216                 if(chattingPartner<0) i = oldi; else {
3217                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3218                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3219                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3220                     started = STARTED_COMMENT;
3221                     parse_pos = 0; parse[0] = NULLCHAR;
3222                     savingComment = 3 + chattingPartner; // counts as TRUE
3223                     suppressKibitz = TRUE;
3224                     continue;
3225                 }
3226             } // [HGM] chat: end of patch
3227
3228           backup = i;
3229             if (appData.zippyTalk || appData.zippyPlay) {
3230                 /* [DM] Backup address for color zippy lines */
3231 #if ZIPPY
3232                if (loggedOn == TRUE)
3233                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3234                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3235 #endif
3236             } // [DM] 'else { ' deleted
3237                 if (
3238                     /* Regular tells and says */
3239                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3240                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3241                     looking_at(buf, &i, "* says: ") ||
3242                     /* Don't color "message" or "messages" output */
3243                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3244                     looking_at(buf, &i, "*. * at *:*: ") ||
3245                     looking_at(buf, &i, "--* (*:*): ") ||
3246                     /* Message notifications (same color as tells) */
3247                     looking_at(buf, &i, "* has left a message ") ||
3248                     looking_at(buf, &i, "* just sent you a message:\n") ||
3249                     /* Whispers and kibitzes */
3250                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3251                     looking_at(buf, &i, "* kibitzes: ") ||
3252                     /* Channel tells */
3253                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3254
3255                   if (tkind == 1 && strchr(star_match[0], ':')) {
3256                       /* Avoid "tells you:" spoofs in channels */
3257                      tkind = 3;
3258                   }
3259                   if (star_match[0][0] == NULLCHAR ||
3260                       strchr(star_match[0], ' ') ||
3261                       (tkind == 3 && strchr(star_match[1], ' '))) {
3262                     /* Reject bogus matches */
3263                     i = oldi;
3264                   } else {
3265                     if (appData.colorize) {
3266                       if (oldi > next_out) {
3267                         SendToPlayer(&buf[next_out], oldi - next_out);
3268                         next_out = oldi;
3269                       }
3270                       switch (tkind) {
3271                       case 1:
3272                         Colorize(ColorTell, FALSE);
3273                         curColor = ColorTell;
3274                         break;
3275                       case 2:
3276                         Colorize(ColorKibitz, FALSE);
3277                         curColor = ColorKibitz;
3278                         break;
3279                       case 3:
3280                         p = strrchr(star_match[1], '(');
3281                         if (p == NULL) {
3282                           p = star_match[1];
3283                         } else {
3284                           p++;
3285                         }
3286                         if (atoi(p) == 1) {
3287                           Colorize(ColorChannel1, FALSE);
3288                           curColor = ColorChannel1;
3289                         } else {
3290                           Colorize(ColorChannel, FALSE);
3291                           curColor = ColorChannel;
3292                         }
3293                         break;
3294                       case 5:
3295                         curColor = ColorNormal;
3296                         break;
3297                       }
3298                     }
3299                     if (started == STARTED_NONE && appData.autoComment &&
3300                         (gameMode == IcsObserving ||
3301                          gameMode == IcsPlayingWhite ||
3302                          gameMode == IcsPlayingBlack)) {
3303                       parse_pos = i - oldi;
3304                       memcpy(parse, &buf[oldi], parse_pos);
3305                       parse[parse_pos] = NULLCHAR;
3306                       started = STARTED_COMMENT;
3307                       savingComment = TRUE;
3308                     } else {
3309                       started = STARTED_CHATTER;
3310                       savingComment = FALSE;
3311                     }
3312                     loggedOn = TRUE;
3313                     continue;
3314                   }
3315                 }
3316
3317                 if (looking_at(buf, &i, "* s-shouts: ") ||
3318                     looking_at(buf, &i, "* c-shouts: ")) {
3319                     if (appData.colorize) {
3320                         if (oldi > next_out) {
3321                             SendToPlayer(&buf[next_out], oldi - next_out);
3322                             next_out = oldi;
3323                         }
3324                         Colorize(ColorSShout, FALSE);
3325                         curColor = ColorSShout;
3326                     }
3327                     loggedOn = TRUE;
3328                     started = STARTED_CHATTER;
3329                     continue;
3330                 }
3331
3332                 if (looking_at(buf, &i, "--->")) {
3333                     loggedOn = TRUE;
3334                     continue;
3335                 }
3336
3337                 if (looking_at(buf, &i, "* shouts: ") ||
3338                     looking_at(buf, &i, "--> ")) {
3339                     if (appData.colorize) {
3340                         if (oldi > next_out) {
3341                             SendToPlayer(&buf[next_out], oldi - next_out);
3342                             next_out = oldi;
3343                         }
3344                         Colorize(ColorShout, FALSE);
3345                         curColor = ColorShout;
3346                     }
3347                     loggedOn = TRUE;
3348                     started = STARTED_CHATTER;
3349                     continue;
3350                 }
3351
3352                 if (looking_at( buf, &i, "Challenge:")) {
3353                     if (appData.colorize) {
3354                         if (oldi > next_out) {
3355                             SendToPlayer(&buf[next_out], oldi - next_out);
3356                             next_out = oldi;
3357                         }
3358                         Colorize(ColorChallenge, FALSE);
3359                         curColor = ColorChallenge;
3360                     }
3361                     loggedOn = TRUE;
3362                     continue;
3363                 }
3364
3365                 if (looking_at(buf, &i, "* offers you") ||
3366                     looking_at(buf, &i, "* offers to be") ||
3367                     looking_at(buf, &i, "* would like to") ||
3368                     looking_at(buf, &i, "* requests to") ||
3369                     looking_at(buf, &i, "Your opponent offers") ||
3370                     looking_at(buf, &i, "Your opponent requests")) {
3371
3372                     if (appData.colorize) {
3373                         if (oldi > next_out) {
3374                             SendToPlayer(&buf[next_out], oldi - next_out);
3375                             next_out = oldi;
3376                         }
3377                         Colorize(ColorRequest, FALSE);
3378                         curColor = ColorRequest;
3379                     }
3380                     continue;
3381                 }
3382
3383                 if (looking_at(buf, &i, "* (*) seeking")) {
3384                     if (appData.colorize) {
3385                         if (oldi > next_out) {
3386                             SendToPlayer(&buf[next_out], oldi - next_out);
3387                             next_out = oldi;
3388                         }
3389                         Colorize(ColorSeek, FALSE);
3390                         curColor = ColorSeek;
3391                     }
3392                     continue;
3393             }
3394
3395           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3396
3397             if (looking_at(buf, &i, "\\   ")) {
3398                 if (prevColor != ColorNormal) {
3399                     if (oldi > next_out) {
3400                         SendToPlayer(&buf[next_out], oldi - next_out);
3401                         next_out = oldi;
3402                     }
3403                     Colorize(prevColor, TRUE);
3404                     curColor = prevColor;
3405                 }
3406                 if (savingComment) {
3407                     parse_pos = i - oldi;
3408                     memcpy(parse, &buf[oldi], parse_pos);
3409                     parse[parse_pos] = NULLCHAR;
3410                     started = STARTED_COMMENT;
3411                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3412                         chattingPartner = savingComment - 3; // kludge to remember the box
3413                 } else {
3414                     started = STARTED_CHATTER;
3415                 }
3416                 continue;
3417             }
3418
3419             if (looking_at(buf, &i, "Black Strength :") ||
3420                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3421                 looking_at(buf, &i, "<10>") ||
3422                 looking_at(buf, &i, "#@#")) {
3423                 /* Wrong board style */
3424                 loggedOn = TRUE;
3425                 SendToICS(ics_prefix);
3426                 SendToICS("set style 12\n");
3427                 SendToICS(ics_prefix);
3428                 SendToICS("refresh\n");
3429                 continue;
3430             }
3431
3432             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3433                 ICSInitScript();
3434                 have_sent_ICS_logon = 1;
3435                 continue;
3436             }
3437
3438             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3439                 (looking_at(buf, &i, "\n<12> ") ||
3440                  looking_at(buf, &i, "<12> "))) {
3441                 loggedOn = TRUE;
3442                 if (oldi > next_out) {
3443                     SendToPlayer(&buf[next_out], oldi - next_out);
3444                 }
3445                 next_out = i;
3446                 started = STARTED_BOARD;
3447                 parse_pos = 0;
3448                 continue;
3449             }
3450
3451             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3452                 looking_at(buf, &i, "<b1> ")) {
3453                 if (oldi > next_out) {
3454                     SendToPlayer(&buf[next_out], oldi - next_out);
3455                 }
3456                 next_out = i;
3457                 started = STARTED_HOLDINGS;
3458                 parse_pos = 0;
3459                 continue;
3460             }
3461
3462             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3463                 loggedOn = TRUE;
3464                 /* Header for a move list -- first line */
3465
3466                 switch (ics_getting_history) {
3467                   case H_FALSE:
3468                     switch (gameMode) {
3469                       case IcsIdle:
3470                       case BeginningOfGame:
3471                         /* User typed "moves" or "oldmoves" while we
3472                            were idle.  Pretend we asked for these
3473                            moves and soak them up so user can step
3474                            through them and/or save them.
3475                            */
3476                         Reset(FALSE, TRUE);
3477                         gameMode = IcsObserving;
3478                         ModeHighlight();
3479                         ics_gamenum = -1;
3480                         ics_getting_history = H_GOT_UNREQ_HEADER;
3481                         break;
3482                       case EditGame: /*?*/
3483                       case EditPosition: /*?*/
3484                         /* Should above feature work in these modes too? */
3485                         /* For now it doesn't */
3486                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3487                         break;
3488                       default:
3489                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3490                         break;
3491                     }
3492                     break;
3493                   case H_REQUESTED:
3494                     /* Is this the right one? */
3495                     if (gameInfo.white && gameInfo.black &&
3496                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3497                         strcmp(gameInfo.black, star_match[2]) == 0) {
3498                         /* All is well */
3499                         ics_getting_history = H_GOT_REQ_HEADER;
3500                     }
3501                     break;
3502                   case H_GOT_REQ_HEADER:
3503                   case H_GOT_UNREQ_HEADER:
3504                   case H_GOT_UNWANTED_HEADER:
3505                   case H_GETTING_MOVES:
3506                     /* Should not happen */
3507                     DisplayError(_("Error gathering move list: two headers"), 0);
3508                     ics_getting_history = H_FALSE;
3509                     break;
3510                 }
3511
3512                 /* Save player ratings into gameInfo if needed */
3513                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3514                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3515                     (gameInfo.whiteRating == -1 ||
3516                      gameInfo.blackRating == -1)) {
3517
3518                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3519                     gameInfo.blackRating = string_to_rating(star_match[3]);
3520                     if (appData.debugMode)
3521                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3522                               gameInfo.whiteRating, gameInfo.blackRating);
3523                 }
3524                 continue;
3525             }
3526
3527             if (looking_at(buf, &i,
3528               "* * match, initial time: * minute*, increment: * second")) {
3529                 /* Header for a move list -- second line */
3530                 /* Initial board will follow if this is a wild game */
3531                 if (gameInfo.event != NULL) free(gameInfo.event);
3532                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3533                 gameInfo.event = StrSave(str);
3534                 /* [HGM] we switched variant. Translate boards if needed. */
3535                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3536                 continue;
3537             }
3538
3539             if (looking_at(buf, &i, "Move  ")) {
3540                 /* Beginning of a move list */
3541                 switch (ics_getting_history) {
3542                   case H_FALSE:
3543                     /* Normally should not happen */
3544                     /* Maybe user hit reset while we were parsing */
3545                     break;
3546                   case H_REQUESTED:
3547                     /* Happens if we are ignoring a move list that is not
3548                      * the one we just requested.  Common if the user
3549                      * tries to observe two games without turning off
3550                      * getMoveList */
3551                     break;
3552                   case H_GETTING_MOVES:
3553                     /* Should not happen */
3554                     DisplayError(_("Error gathering move list: nested"), 0);
3555                     ics_getting_history = H_FALSE;
3556                     break;
3557                   case H_GOT_REQ_HEADER:
3558                     ics_getting_history = H_GETTING_MOVES;
3559                     started = STARTED_MOVES;
3560                     parse_pos = 0;
3561                     if (oldi > next_out) {
3562                         SendToPlayer(&buf[next_out], oldi - next_out);
3563                     }
3564                     break;
3565                   case H_GOT_UNREQ_HEADER:
3566                     ics_getting_history = H_GETTING_MOVES;
3567                     started = STARTED_MOVES_NOHIDE;
3568                     parse_pos = 0;
3569                     break;
3570                   case H_GOT_UNWANTED_HEADER:
3571                     ics_getting_history = H_FALSE;
3572                     break;
3573                 }
3574                 continue;
3575             }
3576
3577             if (looking_at(buf, &i, "% ") ||
3578                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3579                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3580                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3581                     soughtPending = FALSE;
3582                     seekGraphUp = TRUE;
3583                     DrawSeekGraph();
3584                 }
3585                 if(suppressKibitz) next_out = i;
3586                 savingComment = FALSE;
3587                 suppressKibitz = 0;
3588                 switch (started) {
3589                   case STARTED_MOVES:
3590                   case STARTED_MOVES_NOHIDE:
3591                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3592                     parse[parse_pos + i - oldi] = NULLCHAR;
3593                     ParseGameHistory(parse);
3594 #if ZIPPY
3595                     if (appData.zippyPlay && first.initDone) {
3596                         FeedMovesToProgram(&first, forwardMostMove);
3597                         if (gameMode == IcsPlayingWhite) {
3598                             if (WhiteOnMove(forwardMostMove)) {
3599                                 if (first.sendTime) {
3600                                   if (first.useColors) {
3601                                     SendToProgram("black\n", &first);
3602                                   }
3603                                   SendTimeRemaining(&first, TRUE);
3604                                 }
3605                                 if (first.useColors) {
3606                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3607                                 }
3608                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3609                                 first.maybeThinking = TRUE;
3610                             } else {
3611                                 if (first.usePlayother) {
3612                                   if (first.sendTime) {
3613                                     SendTimeRemaining(&first, TRUE);
3614                                   }
3615                                   SendToProgram("playother\n", &first);
3616                                   firstMove = FALSE;
3617                                 } else {
3618                                   firstMove = TRUE;
3619                                 }
3620                             }
3621                         } else if (gameMode == IcsPlayingBlack) {
3622                             if (!WhiteOnMove(forwardMostMove)) {
3623                                 if (first.sendTime) {
3624                                   if (first.useColors) {
3625                                     SendToProgram("white\n", &first);
3626                                   }
3627                                   SendTimeRemaining(&first, FALSE);
3628                                 }
3629                                 if (first.useColors) {
3630                                   SendToProgram("black\n", &first);
3631                                 }
3632                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3633                                 first.maybeThinking = TRUE;
3634                             } else {
3635                                 if (first.usePlayother) {
3636                                   if (first.sendTime) {
3637                                     SendTimeRemaining(&first, FALSE);
3638                                   }
3639                                   SendToProgram("playother\n", &first);
3640                                   firstMove = FALSE;
3641                                 } else {
3642                                   firstMove = TRUE;
3643                                 }
3644                             }
3645                         }
3646                     }
3647 #endif
3648                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3649                         /* Moves came from oldmoves or moves command
3650                            while we weren't doing anything else.
3651                            */
3652                         currentMove = forwardMostMove;
3653                         ClearHighlights();/*!!could figure this out*/
3654                         flipView = appData.flipView;
3655                         DrawPosition(TRUE, boards[currentMove]);
3656                         DisplayBothClocks();
3657                         snprintf(str, MSG_SIZ, "%s %s %s",
3658                                 gameInfo.white, _("vs."),  gameInfo.black);
3659                         DisplayTitle(str);
3660                         gameMode = IcsIdle;
3661                     } else {
3662                         /* Moves were history of an active game */
3663                         if (gameInfo.resultDetails != NULL) {
3664                             free(gameInfo.resultDetails);
3665                             gameInfo.resultDetails = NULL;
3666                         }
3667                     }
3668                     HistorySet(parseList, backwardMostMove,
3669                                forwardMostMove, currentMove-1);
3670                     DisplayMove(currentMove - 1);
3671                     if (started == STARTED_MOVES) next_out = i;
3672                     started = STARTED_NONE;
3673                     ics_getting_history = H_FALSE;
3674                     break;
3675
3676                   case STARTED_OBSERVE:
3677                     started = STARTED_NONE;
3678                     SendToICS(ics_prefix);
3679                     SendToICS("refresh\n");
3680                     break;
3681
3682                   default:
3683                     break;
3684                 }
3685                 if(bookHit) { // [HGM] book: simulate book reply
3686                     static char bookMove[MSG_SIZ]; // a bit generous?
3687
3688                     programStats.nodes = programStats.depth = programStats.time =
3689                     programStats.score = programStats.got_only_move = 0;
3690                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3691
3692                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3693                     strcat(bookMove, bookHit);
3694                     HandleMachineMove(bookMove, &first);
3695                 }
3696                 continue;
3697             }
3698
3699             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3700                  started == STARTED_HOLDINGS ||
3701                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3702                 /* Accumulate characters in move list or board */
3703                 parse[parse_pos++] = buf[i];
3704             }
3705
3706             /* Start of game messages.  Mostly we detect start of game
3707                when the first board image arrives.  On some versions
3708                of the ICS, though, we need to do a "refresh" after starting
3709                to observe in order to get the current board right away. */
3710             if (looking_at(buf, &i, "Adding game * to observation list")) {
3711                 started = STARTED_OBSERVE;
3712                 continue;
3713             }
3714
3715             /* Handle auto-observe */
3716             if (appData.autoObserve &&
3717                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3718                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3719                 char *player;
3720                 /* Choose the player that was highlighted, if any. */
3721                 if (star_match[0][0] == '\033' ||
3722                     star_match[1][0] != '\033') {
3723                     player = star_match[0];
3724                 } else {
3725                     player = star_match[2];
3726                 }
3727                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3728                         ics_prefix, StripHighlightAndTitle(player));
3729                 SendToICS(str);
3730
3731                 /* Save ratings from notify string */
3732                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3733                 player1Rating = string_to_rating(star_match[1]);
3734                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3735                 player2Rating = string_to_rating(star_match[3]);
3736
3737                 if (appData.debugMode)
3738                   fprintf(debugFP,
3739                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3740                           player1Name, player1Rating,
3741                           player2Name, player2Rating);
3742
3743                 continue;
3744             }
3745
3746             /* Deal with automatic examine mode after a game,
3747                and with IcsObserving -> IcsExamining transition */
3748             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3749                 looking_at(buf, &i, "has made you an examiner of game *")) {
3750
3751                 int gamenum = atoi(star_match[0]);
3752                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3753                     gamenum == ics_gamenum) {
3754                     /* We were already playing or observing this game;
3755                        no need to refetch history */
3756                     gameMode = IcsExamining;
3757                     if (pausing) {
3758                         pauseExamForwardMostMove = forwardMostMove;
3759                     } else if (currentMove < forwardMostMove) {
3760                         ForwardInner(forwardMostMove);
3761                     }
3762                 } else {
3763                     /* I don't think this case really can happen */
3764                     SendToICS(ics_prefix);
3765                     SendToICS("refresh\n");
3766                 }
3767                 continue;
3768             }
3769
3770             /* Error messages */
3771 //          if (ics_user_moved) {
3772             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3773                 if (looking_at(buf, &i, "Illegal move") ||
3774                     looking_at(buf, &i, "Not a legal move") ||
3775                     looking_at(buf, &i, "Your king is in check") ||
3776                     looking_at(buf, &i, "It isn't your turn") ||
3777                     looking_at(buf, &i, "It is not your move")) {
3778                     /* Illegal move */
3779                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3780                         currentMove = forwardMostMove-1;
3781                         DisplayMove(currentMove - 1); /* before DMError */
3782                         DrawPosition(FALSE, boards[currentMove]);
3783                         SwitchClocks(forwardMostMove-1); // [HGM] race
3784                         DisplayBothClocks();
3785                     }
3786                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3787                     ics_user_moved = 0;
3788                     continue;
3789                 }
3790             }
3791
3792             if (looking_at(buf, &i, "still have time") ||
3793                 looking_at(buf, &i, "not out of time") ||
3794                 looking_at(buf, &i, "either player is out of time") ||
3795                 looking_at(buf, &i, "has timeseal; checking")) {
3796                 /* We must have called his flag a little too soon */
3797                 whiteFlag = blackFlag = FALSE;
3798                 continue;
3799             }
3800
3801             if (looking_at(buf, &i, "added * seconds to") ||
3802                 looking_at(buf, &i, "seconds were added to")) {
3803                 /* Update the clocks */
3804                 SendToICS(ics_prefix);
3805                 SendToICS("refresh\n");
3806                 continue;
3807             }
3808
3809             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3810                 ics_clock_paused = TRUE;
3811                 StopClocks();
3812                 continue;
3813             }
3814
3815             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3816                 ics_clock_paused = FALSE;
3817                 StartClocks();
3818                 continue;
3819             }
3820
3821             /* Grab player ratings from the Creating: message.
3822                Note we have to check for the special case when
3823                the ICS inserts things like [white] or [black]. */
3824             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3825                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3826                 /* star_matches:
3827                    0    player 1 name (not necessarily white)
3828                    1    player 1 rating
3829                    2    empty, white, or black (IGNORED)
3830                    3    player 2 name (not necessarily black)
3831                    4    player 2 rating
3832
3833                    The names/ratings are sorted out when the game
3834                    actually starts (below).
3835                 */
3836                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3837                 player1Rating = string_to_rating(star_match[1]);
3838                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3839                 player2Rating = string_to_rating(star_match[4]);
3840
3841                 if (appData.debugMode)
3842                   fprintf(debugFP,
3843                           "Ratings from 'Creating:' %s %d, %s %d\n",
3844                           player1Name, player1Rating,
3845                           player2Name, player2Rating);
3846
3847                 continue;
3848             }
3849
3850             /* Improved generic start/end-of-game messages */
3851             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3852                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3853                 /* If tkind == 0: */
3854                 /* star_match[0] is the game number */
3855                 /*           [1] is the white player's name */
3856                 /*           [2] is the black player's name */
3857                 /* For end-of-game: */
3858                 /*           [3] is the reason for the game end */
3859                 /*           [4] is a PGN end game-token, preceded by " " */
3860                 /* For start-of-game: */
3861                 /*           [3] begins with "Creating" or "Continuing" */
3862                 /*           [4] is " *" or empty (don't care). */
3863                 int gamenum = atoi(star_match[0]);
3864                 char *whitename, *blackname, *why, *endtoken;
3865                 ChessMove endtype = EndOfFile;
3866
3867                 if (tkind == 0) {
3868                   whitename = star_match[1];
3869                   blackname = star_match[2];
3870                   why = star_match[3];
3871                   endtoken = star_match[4];
3872                 } else {
3873                   whitename = star_match[1];
3874                   blackname = star_match[3];
3875                   why = star_match[5];
3876                   endtoken = star_match[6];
3877                 }
3878
3879                 /* Game start messages */
3880                 if (strncmp(why, "Creating ", 9) == 0 ||
3881                     strncmp(why, "Continuing ", 11) == 0) {
3882                     gs_gamenum = gamenum;
3883                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3884                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3885                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3886 #if ZIPPY
3887                     if (appData.zippyPlay) {
3888                         ZippyGameStart(whitename, blackname);
3889                     }
3890 #endif /*ZIPPY*/
3891                     partnerBoardValid = FALSE; // [HGM] bughouse
3892                     continue;
3893                 }
3894
3895                 /* Game end messages */
3896                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3897                     ics_gamenum != gamenum) {
3898                     continue;
3899                 }
3900                 while (endtoken[0] == ' ') endtoken++;
3901                 switch (endtoken[0]) {
3902                   case '*':
3903                   default:
3904                     endtype = GameUnfinished;
3905                     break;
3906                   case '0':
3907                     endtype = BlackWins;
3908                     break;
3909                   case '1':
3910                     if (endtoken[1] == '/')
3911                       endtype = GameIsDrawn;
3912                     else
3913                       endtype = WhiteWins;
3914                     break;
3915                 }
3916                 GameEnds(endtype, why, GE_ICS);
3917 #if ZIPPY
3918                 if (appData.zippyPlay && first.initDone) {
3919                     ZippyGameEnd(endtype, why);
3920                     if (first.pr == NoProc) {
3921                       /* Start the next process early so that we'll
3922                          be ready for the next challenge */
3923                       StartChessProgram(&first);
3924                     }
3925                     /* Send "new" early, in case this command takes
3926                        a long time to finish, so that we'll be ready
3927                        for the next challenge. */
3928                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3929                     Reset(TRUE, TRUE);
3930                 }
3931 #endif /*ZIPPY*/
3932                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3933                 continue;
3934             }
3935
3936             if (looking_at(buf, &i, "Removing game * from observation") ||
3937                 looking_at(buf, &i, "no longer observing game *") ||
3938                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3939                 if (gameMode == IcsObserving &&
3940                     atoi(star_match[0]) == ics_gamenum)
3941                   {
3942                       /* icsEngineAnalyze */
3943                       if (appData.icsEngineAnalyze) {
3944                             ExitAnalyzeMode();
3945                             ModeHighlight();
3946                       }
3947                       StopClocks();
3948                       gameMode = IcsIdle;
3949                       ics_gamenum = -1;
3950                       ics_user_moved = FALSE;
3951                   }
3952                 continue;
3953             }
3954
3955             if (looking_at(buf, &i, "no longer examining game *")) {
3956                 if (gameMode == IcsExamining &&
3957                     atoi(star_match[0]) == ics_gamenum)
3958                   {
3959                       gameMode = IcsIdle;
3960                       ics_gamenum = -1;
3961                       ics_user_moved = FALSE;
3962                   }
3963                 continue;
3964             }
3965
3966             /* Advance leftover_start past any newlines we find,
3967                so only partial lines can get reparsed */
3968             if (looking_at(buf, &i, "\n")) {
3969                 prevColor = curColor;
3970                 if (curColor != ColorNormal) {
3971                     if (oldi > next_out) {
3972                         SendToPlayer(&buf[next_out], oldi - next_out);
3973                         next_out = oldi;
3974                     }
3975                     Colorize(ColorNormal, FALSE);
3976                     curColor = ColorNormal;
3977                 }
3978                 if (started == STARTED_BOARD) {
3979                     started = STARTED_NONE;
3980                     parse[parse_pos] = NULLCHAR;
3981                     ParseBoard12(parse);
3982                     ics_user_moved = 0;
3983
3984                     /* Send premove here */
3985                     if (appData.premove) {
3986                       char str[MSG_SIZ];
3987                       if (currentMove == 0 &&
3988                           gameMode == IcsPlayingWhite &&
3989                           appData.premoveWhite) {
3990                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3991                         if (appData.debugMode)
3992                           fprintf(debugFP, "Sending premove:\n");
3993                         SendToICS(str);
3994                       } else if (currentMove == 1 &&
3995                                  gameMode == IcsPlayingBlack &&
3996                                  appData.premoveBlack) {
3997                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3998                         if (appData.debugMode)
3999                           fprintf(debugFP, "Sending premove:\n");
4000                         SendToICS(str);
4001                       } else if (gotPremove) {
4002                         gotPremove = 0;
4003                         ClearPremoveHighlights();
4004                         if (appData.debugMode)
4005                           fprintf(debugFP, "Sending premove:\n");
4006                           UserMoveEvent(premoveFromX, premoveFromY,
4007                                         premoveToX, premoveToY,
4008                                         premovePromoChar);
4009                       }
4010                     }
4011
4012                     /* Usually suppress following prompt */
4013                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4014                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4015                         if (looking_at(buf, &i, "*% ")) {
4016                             savingComment = FALSE;
4017                             suppressKibitz = 0;
4018                         }
4019                     }
4020                     next_out = i;
4021                 } else if (started == STARTED_HOLDINGS) {
4022                     int gamenum;
4023                     char new_piece[MSG_SIZ];
4024                     started = STARTED_NONE;
4025                     parse[parse_pos] = NULLCHAR;
4026                     if (appData.debugMode)
4027                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4028                                                         parse, currentMove);
4029                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4030                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4031                         if (gameInfo.variant == VariantNormal) {
4032                           /* [HGM] We seem to switch variant during a game!
4033                            * Presumably no holdings were displayed, so we have
4034                            * to move the position two files to the right to
4035                            * create room for them!
4036                            */
4037                           VariantClass newVariant;
4038                           switch(gameInfo.boardWidth) { // base guess on board width
4039                                 case 9:  newVariant = VariantShogi; break;
4040                                 case 10: newVariant = VariantGreat; break;
4041                                 default: newVariant = VariantCrazyhouse; break;
4042                           }
4043                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4044                           /* Get a move list just to see the header, which
4045                              will tell us whether this is really bug or zh */
4046                           if (ics_getting_history == H_FALSE) {
4047                             ics_getting_history = H_REQUESTED;
4048                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4049                             SendToICS(str);
4050                           }
4051                         }
4052                         new_piece[0] = NULLCHAR;
4053                         sscanf(parse, "game %d white [%s black [%s <- %s",
4054                                &gamenum, white_holding, black_holding,
4055                                new_piece);
4056                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4057                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4058                         /* [HGM] copy holdings to board holdings area */
4059                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4060                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4061                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4062 #if ZIPPY
4063                         if (appData.zippyPlay && first.initDone) {
4064                             ZippyHoldings(white_holding, black_holding,
4065                                           new_piece);
4066                         }
4067 #endif /*ZIPPY*/
4068                         if (tinyLayout || smallLayout) {
4069                             char wh[16], bh[16];
4070                             PackHolding(wh, white_holding);
4071                             PackHolding(bh, black_holding);
4072                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4073                                     gameInfo.white, gameInfo.black);
4074                         } else {
4075                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4076                                     gameInfo.white, white_holding, _("vs."),
4077                                     gameInfo.black, black_holding);
4078                         }
4079                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4080                         DrawPosition(FALSE, boards[currentMove]);
4081                         DisplayTitle(str);
4082                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4083                         sscanf(parse, "game %d white [%s black [%s <- %s",
4084                                &gamenum, white_holding, black_holding,
4085                                new_piece);
4086                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4087                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4088                         /* [HGM] copy holdings to partner-board holdings area */
4089                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4090                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4091                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4092                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4093                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4094                       }
4095                     }
4096                     /* Suppress following prompt */
4097                     if (looking_at(buf, &i, "*% ")) {
4098                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4099                         savingComment = FALSE;
4100                         suppressKibitz = 0;
4101                     }
4102                     next_out = i;
4103                 }
4104                 continue;
4105             }
4106
4107             i++;                /* skip unparsed character and loop back */
4108         }
4109
4110         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4111 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4112 //          SendToPlayer(&buf[next_out], i - next_out);
4113             started != STARTED_HOLDINGS && leftover_start > next_out) {
4114             SendToPlayer(&buf[next_out], leftover_start - next_out);
4115             next_out = i;
4116         }
4117
4118         leftover_len = buf_len - leftover_start;
4119         /* if buffer ends with something we couldn't parse,
4120            reparse it after appending the next read */
4121
4122     } else if (count == 0) {
4123         RemoveInputSource(isr);
4124         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4125     } else {
4126         DisplayFatalError(_("Error reading from ICS"), error, 1);
4127     }
4128 }
4129
4130
4131 /* Board style 12 looks like this:
4132
4133    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4134
4135  * The "<12> " is stripped before it gets to this routine.  The two
4136  * trailing 0's (flip state and clock ticking) are later addition, and
4137  * some chess servers may not have them, or may have only the first.
4138  * Additional trailing fields may be added in the future.
4139  */
4140
4141 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4142
4143 #define RELATION_OBSERVING_PLAYED    0
4144 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4145 #define RELATION_PLAYING_MYMOVE      1
4146 #define RELATION_PLAYING_NOTMYMOVE  -1
4147 #define RELATION_EXAMINING           2
4148 #define RELATION_ISOLATED_BOARD     -3
4149 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4150
4151 void
4152 ParseBoard12 (char *string)
4153 {
4154     GameMode newGameMode;
4155     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4156     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4157     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4158     char to_play, board_chars[200];
4159     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4160     char black[32], white[32];
4161     Board board;
4162     int prevMove = currentMove;
4163     int ticking = 2;
4164     ChessMove moveType;
4165     int fromX, fromY, toX, toY;
4166     char promoChar;
4167     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4168     char *bookHit = NULL; // [HGM] book
4169     Boolean weird = FALSE, reqFlag = FALSE;
4170
4171     fromX = fromY = toX = toY = -1;
4172
4173     newGame = FALSE;
4174
4175     if (appData.debugMode)
4176       fprintf(debugFP, _("Parsing board: %s\n"), string);
4177
4178     move_str[0] = NULLCHAR;
4179     elapsed_time[0] = NULLCHAR;
4180     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4181         int  i = 0, j;
4182         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4183             if(string[i] == ' ') { ranks++; files = 0; }
4184             else files++;
4185             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4186             i++;
4187         }
4188         for(j = 0; j <i; j++) board_chars[j] = string[j];
4189         board_chars[i] = '\0';
4190         string += i + 1;
4191     }
4192     n = sscanf(string, PATTERN, &to_play, &double_push,
4193                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4194                &gamenum, white, black, &relation, &basetime, &increment,
4195                &white_stren, &black_stren, &white_time, &black_time,
4196                &moveNum, str, elapsed_time, move_str, &ics_flip,
4197                &ticking);
4198
4199     if (n < 21) {
4200         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4201         DisplayError(str, 0);
4202         return;
4203     }
4204
4205     /* Convert the move number to internal form */
4206     moveNum = (moveNum - 1) * 2;
4207     if (to_play == 'B') moveNum++;
4208     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4209       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4210                         0, 1);
4211       return;
4212     }
4213
4214     switch (relation) {
4215       case RELATION_OBSERVING_PLAYED:
4216       case RELATION_OBSERVING_STATIC:
4217         if (gamenum == -1) {
4218             /* Old ICC buglet */
4219             relation = RELATION_OBSERVING_STATIC;
4220         }
4221         newGameMode = IcsObserving;
4222         break;
4223       case RELATION_PLAYING_MYMOVE:
4224       case RELATION_PLAYING_NOTMYMOVE:
4225         newGameMode =
4226           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4227             IcsPlayingWhite : IcsPlayingBlack;
4228         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4229         break;
4230       case RELATION_EXAMINING:
4231         newGameMode = IcsExamining;
4232         break;
4233       case RELATION_ISOLATED_BOARD:
4234       default:
4235         /* Just display this board.  If user was doing something else,
4236            we will forget about it until the next board comes. */
4237         newGameMode = IcsIdle;
4238         break;
4239       case RELATION_STARTING_POSITION:
4240         newGameMode = gameMode;
4241         break;
4242     }
4243
4244     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4245         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4246          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4247       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4248       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4249       static int lastBgGame = -1;
4250       char *toSqr;
4251       for (k = 0; k < ranks; k++) {
4252         for (j = 0; j < files; j++)
4253           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4254         if(gameInfo.holdingsWidth > 1) {
4255              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4256              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4257         }
4258       }
4259       CopyBoard(partnerBoard, board);
4260       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4261         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4262         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4263       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4264       if(toSqr = strchr(str, '-')) {
4265         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4266         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4267       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4268       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4269       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4270       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4271       if(twoBoards) {
4272           DisplayWhiteClock(white_time*fac, to_play == 'W');
4273           DisplayBlackClock(black_time*fac, to_play != 'W');
4274           activePartner = to_play;
4275           if(gamenum != lastBgGame) {
4276               char buf[MSG_SIZ];
4277               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4278               DisplayTitle(buf);
4279           }
4280           lastBgGame = gamenum;
4281           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4282                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4283       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4284                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4285       DisplayMessage(partnerStatus, "");
4286         partnerBoardValid = TRUE;
4287       return;
4288     }
4289
4290     if(appData.dualBoard && appData.bgObserve) {
4291         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4292             SendToICS(ics_prefix), SendToICS("pobserve\n");
4293         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4294             char buf[MSG_SIZ];
4295             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4296             SendToICS(buf);
4297         }
4298     }
4299
4300     /* Modify behavior for initial board display on move listing
4301        of wild games.
4302        */
4303     switch (ics_getting_history) {
4304       case H_FALSE:
4305       case H_REQUESTED:
4306         break;
4307       case H_GOT_REQ_HEADER:
4308       case H_GOT_UNREQ_HEADER:
4309         /* This is the initial position of the current game */
4310         gamenum = ics_gamenum;
4311         moveNum = 0;            /* old ICS bug workaround */
4312         if (to_play == 'B') {
4313           startedFromSetupPosition = TRUE;
4314           blackPlaysFirst = TRUE;
4315           moveNum = 1;
4316           if (forwardMostMove == 0) forwardMostMove = 1;
4317           if (backwardMostMove == 0) backwardMostMove = 1;
4318           if (currentMove == 0) currentMove = 1;
4319         }
4320         newGameMode = gameMode;
4321         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4322         break;
4323       case H_GOT_UNWANTED_HEADER:
4324         /* This is an initial board that we don't want */
4325         return;
4326       case H_GETTING_MOVES:
4327         /* Should not happen */
4328         DisplayError(_("Error gathering move list: extra board"), 0);
4329         ics_getting_history = H_FALSE;
4330         return;
4331     }
4332
4333    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4334                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4335                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4336      /* [HGM] We seem to have switched variant unexpectedly
4337       * Try to guess new variant from board size
4338       */
4339           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4340           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4341           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4342           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4343           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4344           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4345           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4346           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4347           /* Get a move list just to see the header, which
4348              will tell us whether this is really bug or zh */
4349           if (ics_getting_history == H_FALSE) {
4350             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4351             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4352             SendToICS(str);
4353           }
4354     }
4355
4356     /* Take action if this is the first board of a new game, or of a
4357        different game than is currently being displayed.  */
4358     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4359         relation == RELATION_ISOLATED_BOARD) {
4360
4361         /* Forget the old game and get the history (if any) of the new one */
4362         if (gameMode != BeginningOfGame) {
4363           Reset(TRUE, TRUE);
4364         }
4365         newGame = TRUE;
4366         if (appData.autoRaiseBoard) BoardToTop();
4367         prevMove = -3;
4368         if (gamenum == -1) {
4369             newGameMode = IcsIdle;
4370         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4371                    appData.getMoveList && !reqFlag) {
4372             /* Need to get game history */
4373             ics_getting_history = H_REQUESTED;
4374             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4375             SendToICS(str);
4376         }
4377
4378         /* Initially flip the board to have black on the bottom if playing
4379            black or if the ICS flip flag is set, but let the user change
4380            it with the Flip View button. */
4381         flipView = appData.autoFlipView ?
4382           (newGameMode == IcsPlayingBlack) || ics_flip :
4383           appData.flipView;
4384
4385         /* Done with values from previous mode; copy in new ones */
4386         gameMode = newGameMode;
4387         ModeHighlight();
4388         ics_gamenum = gamenum;
4389         if (gamenum == gs_gamenum) {
4390             int klen = strlen(gs_kind);
4391             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4392             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4393             gameInfo.event = StrSave(str);
4394         } else {
4395             gameInfo.event = StrSave("ICS game");
4396         }
4397         gameInfo.site = StrSave(appData.icsHost);
4398         gameInfo.date = PGNDate();
4399         gameInfo.round = StrSave("-");
4400         gameInfo.white = StrSave(white);
4401         gameInfo.black = StrSave(black);
4402         timeControl = basetime * 60 * 1000;
4403         timeControl_2 = 0;
4404         timeIncrement = increment * 1000;
4405         movesPerSession = 0;
4406         gameInfo.timeControl = TimeControlTagValue();
4407         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4408   if (appData.debugMode) {
4409     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4410     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4411     setbuf(debugFP, NULL);
4412   }
4413
4414         gameInfo.outOfBook = NULL;
4415
4416         /* Do we have the ratings? */
4417         if (strcmp(player1Name, white) == 0 &&
4418             strcmp(player2Name, black) == 0) {
4419             if (appData.debugMode)
4420               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4421                       player1Rating, player2Rating);
4422             gameInfo.whiteRating = player1Rating;
4423             gameInfo.blackRating = player2Rating;
4424         } else if (strcmp(player2Name, white) == 0 &&
4425                    strcmp(player1Name, black) == 0) {
4426             if (appData.debugMode)
4427               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4428                       player2Rating, player1Rating);
4429             gameInfo.whiteRating = player2Rating;
4430             gameInfo.blackRating = player1Rating;
4431         }
4432         player1Name[0] = player2Name[0] = NULLCHAR;
4433
4434         /* Silence shouts if requested */
4435         if (appData.quietPlay &&
4436             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4437             SendToICS(ics_prefix);
4438             SendToICS("set shout 0\n");
4439         }
4440     }
4441
4442     /* Deal with midgame name changes */
4443     if (!newGame) {
4444         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4445             if (gameInfo.white) free(gameInfo.white);
4446             gameInfo.white = StrSave(white);
4447         }
4448         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4449             if (gameInfo.black) free(gameInfo.black);
4450             gameInfo.black = StrSave(black);
4451         }
4452     }
4453
4454     /* Throw away game result if anything actually changes in examine mode */
4455     if (gameMode == IcsExamining && !newGame) {
4456         gameInfo.result = GameUnfinished;
4457         if (gameInfo.resultDetails != NULL) {
4458             free(gameInfo.resultDetails);
4459             gameInfo.resultDetails = NULL;
4460         }
4461     }
4462
4463     /* In pausing && IcsExamining mode, we ignore boards coming
4464        in if they are in a different variation than we are. */
4465     if (pauseExamInvalid) return;
4466     if (pausing && gameMode == IcsExamining) {
4467         if (moveNum <= pauseExamForwardMostMove) {
4468             pauseExamInvalid = TRUE;
4469             forwardMostMove = pauseExamForwardMostMove;
4470             return;
4471         }
4472     }
4473
4474   if (appData.debugMode) {
4475     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4476   }
4477     /* Parse the board */
4478     for (k = 0; k < ranks; k++) {
4479       for (j = 0; j < files; j++)
4480         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4481       if(gameInfo.holdingsWidth > 1) {
4482            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4483            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4484       }
4485     }
4486     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4487       board[5][BOARD_RGHT+1] = WhiteAngel;
4488       board[6][BOARD_RGHT+1] = WhiteMarshall;
4489       board[1][0] = BlackMarshall;
4490       board[2][0] = BlackAngel;
4491       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4492     }
4493     CopyBoard(boards[moveNum], board);
4494     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4495     if (moveNum == 0) {
4496         startedFromSetupPosition =
4497           !CompareBoards(board, initialPosition);
4498         if(startedFromSetupPosition)
4499             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4500     }
4501
4502     /* [HGM] Set castling rights. Take the outermost Rooks,
4503        to make it also work for FRC opening positions. Note that board12
4504        is really defective for later FRC positions, as it has no way to
4505        indicate which Rook can castle if they are on the same side of King.
4506        For the initial position we grant rights to the outermost Rooks,
4507        and remember thos rights, and we then copy them on positions
4508        later in an FRC game. This means WB might not recognize castlings with
4509        Rooks that have moved back to their original position as illegal,
4510        but in ICS mode that is not its job anyway.
4511     */
4512     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4513     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4514
4515         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4516             if(board[0][i] == WhiteRook) j = i;
4517         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4518         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4519             if(board[0][i] == WhiteRook) j = i;
4520         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4521         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4522             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4523         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4524         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4525             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4526         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4527
4528         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4529         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4530         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4531             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4532         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4533             if(board[BOARD_HEIGHT-1][k] == bKing)
4534                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4535         if(gameInfo.variant == VariantTwoKings) {
4536             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4537             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4538             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4539         }
4540     } else { int r;
4541         r = boards[moveNum][CASTLING][0] = initialRights[0];
4542         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4543         r = boards[moveNum][CASTLING][1] = initialRights[1];
4544         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4545         r = boards[moveNum][CASTLING][3] = initialRights[3];
4546         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4547         r = boards[moveNum][CASTLING][4] = initialRights[4];
4548         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4549         /* wildcastle kludge: always assume King has rights */
4550         r = boards[moveNum][CASTLING][2] = initialRights[2];
4551         r = boards[moveNum][CASTLING][5] = initialRights[5];
4552     }
4553     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4554     boards[moveNum][EP_STATUS] = EP_NONE;
4555     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4556     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4557     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4558
4559
4560     if (ics_getting_history == H_GOT_REQ_HEADER ||
4561         ics_getting_history == H_GOT_UNREQ_HEADER) {
4562         /* This was an initial position from a move list, not
4563            the current position */
4564         return;
4565     }
4566
4567     /* Update currentMove and known move number limits */
4568     newMove = newGame || moveNum > forwardMostMove;
4569
4570     if (newGame) {
4571         forwardMostMove = backwardMostMove = currentMove = moveNum;
4572         if (gameMode == IcsExamining && moveNum == 0) {
4573           /* Workaround for ICS limitation: we are not told the wild
4574              type when starting to examine a game.  But if we ask for
4575              the move list, the move list header will tell us */
4576             ics_getting_history = H_REQUESTED;
4577             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4578             SendToICS(str);
4579         }
4580     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4581                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4582 #if ZIPPY
4583         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4584         /* [HGM] applied this also to an engine that is silently watching        */
4585         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4586             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4587             gameInfo.variant == currentlyInitializedVariant) {
4588           takeback = forwardMostMove - moveNum;
4589           for (i = 0; i < takeback; i++) {
4590             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4591             SendToProgram("undo\n", &first);
4592           }
4593         }
4594 #endif
4595
4596         forwardMostMove = moveNum;
4597         if (!pausing || currentMove > forwardMostMove)
4598           currentMove = forwardMostMove;
4599     } else {
4600         /* New part of history that is not contiguous with old part */
4601         if (pausing && gameMode == IcsExamining) {
4602             pauseExamInvalid = TRUE;
4603             forwardMostMove = pauseExamForwardMostMove;
4604             return;
4605         }
4606         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4607 #if ZIPPY
4608             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4609                 // [HGM] when we will receive the move list we now request, it will be
4610                 // fed to the engine from the first move on. So if the engine is not
4611                 // in the initial position now, bring it there.
4612                 InitChessProgram(&first, 0);
4613             }
4614 #endif
4615             ics_getting_history = H_REQUESTED;
4616             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4617             SendToICS(str);
4618         }
4619         forwardMostMove = backwardMostMove = currentMove = moveNum;
4620     }
4621
4622     /* Update the clocks */
4623     if (strchr(elapsed_time, '.')) {
4624       /* Time is in ms */
4625       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4626       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4627     } else {
4628       /* Time is in seconds */
4629       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4630       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4631     }
4632
4633
4634 #if ZIPPY
4635     if (appData.zippyPlay && newGame &&
4636         gameMode != IcsObserving && gameMode != IcsIdle &&
4637         gameMode != IcsExamining)
4638       ZippyFirstBoard(moveNum, basetime, increment);
4639 #endif
4640
4641     /* Put the move on the move list, first converting
4642        to canonical algebraic form. */
4643     if (moveNum > 0) {
4644   if (appData.debugMode) {
4645     if (appData.debugMode) { int f = forwardMostMove;
4646         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4647                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4648                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4649     }
4650     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4651     fprintf(debugFP, "moveNum = %d\n", moveNum);
4652     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4653     setbuf(debugFP, NULL);
4654   }
4655         if (moveNum <= backwardMostMove) {
4656             /* We don't know what the board looked like before
4657                this move.  Punt. */
4658           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4659             strcat(parseList[moveNum - 1], " ");
4660             strcat(parseList[moveNum - 1], elapsed_time);
4661             moveList[moveNum - 1][0] = NULLCHAR;
4662         } else if (strcmp(move_str, "none") == 0) {
4663             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4664             /* Again, we don't know what the board looked like;
4665                this is really the start of the game. */
4666             parseList[moveNum - 1][0] = NULLCHAR;
4667             moveList[moveNum - 1][0] = NULLCHAR;
4668             backwardMostMove = moveNum;
4669             startedFromSetupPosition = TRUE;
4670             fromX = fromY = toX = toY = -1;
4671         } else {
4672           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4673           //                 So we parse the long-algebraic move string in stead of the SAN move
4674           int valid; char buf[MSG_SIZ], *prom;
4675
4676           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4677                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4678           // str looks something like "Q/a1-a2"; kill the slash
4679           if(str[1] == '/')
4680             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4681           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4682           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4683                 strcat(buf, prom); // long move lacks promo specification!
4684           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4685                 if(appData.debugMode)
4686                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4687                 safeStrCpy(move_str, buf, MSG_SIZ);
4688           }
4689           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4690                                 &fromX, &fromY, &toX, &toY, &promoChar)
4691                || ParseOneMove(buf, moveNum - 1, &moveType,
4692                                 &fromX, &fromY, &toX, &toY, &promoChar);
4693           // end of long SAN patch
4694           if (valid) {
4695             (void) CoordsToAlgebraic(boards[moveNum - 1],
4696                                      PosFlags(moveNum - 1),
4697                                      fromY, fromX, toY, toX, promoChar,
4698                                      parseList[moveNum-1]);
4699             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4700               case MT_NONE:
4701               case MT_STALEMATE:
4702               default:
4703                 break;
4704               case MT_CHECK:
4705                 if(gameInfo.variant != VariantShogi)
4706                     strcat(parseList[moveNum - 1], "+");
4707                 break;
4708               case MT_CHECKMATE:
4709               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4710                 strcat(parseList[moveNum - 1], "#");
4711                 break;
4712             }
4713             strcat(parseList[moveNum - 1], " ");
4714             strcat(parseList[moveNum - 1], elapsed_time);
4715             /* currentMoveString is set as a side-effect of ParseOneMove */
4716             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4717             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4718             strcat(moveList[moveNum - 1], "\n");
4719
4720             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4721                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4722               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4723                 ChessSquare old, new = boards[moveNum][k][j];
4724                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4725                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4726                   if(old == new) continue;
4727                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4728                   else if(new == WhiteWazir || new == BlackWazir) {
4729                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4730                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4731                       else boards[moveNum][k][j] = old; // preserve type of Gold
4732                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4733                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4734               }
4735           } else {
4736             /* Move from ICS was illegal!?  Punt. */
4737             if (appData.debugMode) {
4738               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4739               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4740             }
4741             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4742             strcat(parseList[moveNum - 1], " ");
4743             strcat(parseList[moveNum - 1], elapsed_time);
4744             moveList[moveNum - 1][0] = NULLCHAR;
4745             fromX = fromY = toX = toY = -1;
4746           }
4747         }
4748   if (appData.debugMode) {
4749     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4750     setbuf(debugFP, NULL);
4751   }
4752
4753 #if ZIPPY
4754         /* Send move to chess program (BEFORE animating it). */
4755         if (appData.zippyPlay && !newGame && newMove &&
4756            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4757
4758             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4759                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4760                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4761                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4762                             move_str);
4763                     DisplayError(str, 0);
4764                 } else {
4765                     if (first.sendTime) {
4766                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4767                     }
4768                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4769                     if (firstMove && !bookHit) {
4770                         firstMove = FALSE;
4771                         if (first.useColors) {
4772                           SendToProgram(gameMode == IcsPlayingWhite ?
4773                                         "white\ngo\n" :
4774                                         "black\ngo\n", &first);
4775                         } else {
4776                           SendToProgram("go\n", &first);
4777                         }
4778                         first.maybeThinking = TRUE;
4779                     }
4780                 }
4781             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4782               if (moveList[moveNum - 1][0] == NULLCHAR) {
4783                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4784                 DisplayError(str, 0);
4785               } else {
4786                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4787                 SendMoveToProgram(moveNum - 1, &first);
4788               }
4789             }
4790         }
4791 #endif
4792     }
4793
4794     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4795         /* If move comes from a remote source, animate it.  If it
4796            isn't remote, it will have already been animated. */
4797         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4798             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4799         }
4800         if (!pausing && appData.highlightLastMove) {
4801             SetHighlights(fromX, fromY, toX, toY);
4802         }
4803     }
4804
4805     /* Start the clocks */
4806     whiteFlag = blackFlag = FALSE;
4807     appData.clockMode = !(basetime == 0 && increment == 0);
4808     if (ticking == 0) {
4809       ics_clock_paused = TRUE;
4810       StopClocks();
4811     } else if (ticking == 1) {
4812       ics_clock_paused = FALSE;
4813     }
4814     if (gameMode == IcsIdle ||
4815         relation == RELATION_OBSERVING_STATIC ||
4816         relation == RELATION_EXAMINING ||
4817         ics_clock_paused)
4818       DisplayBothClocks();
4819     else
4820       StartClocks();
4821
4822     /* Display opponents and material strengths */
4823     if (gameInfo.variant != VariantBughouse &&
4824         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4825         if (tinyLayout || smallLayout) {
4826             if(gameInfo.variant == VariantNormal)
4827               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4828                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4829                     basetime, increment);
4830             else
4831               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4832                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4833                     basetime, increment, (int) gameInfo.variant);
4834         } else {
4835             if(gameInfo.variant == VariantNormal)
4836               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4837                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4838                     basetime, increment);
4839             else
4840               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4841                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4842                     basetime, increment, VariantName(gameInfo.variant));
4843         }
4844         DisplayTitle(str);
4845   if (appData.debugMode) {
4846     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4847   }
4848     }
4849
4850
4851     /* Display the board */
4852     if (!pausing && !appData.noGUI) {
4853
4854       if (appData.premove)
4855           if (!gotPremove ||
4856              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4857              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4858               ClearPremoveHighlights();
4859
4860       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4861         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4862       DrawPosition(j, boards[currentMove]);
4863
4864       DisplayMove(moveNum - 1);
4865       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4866             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4867               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4868         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4869       }
4870     }
4871
4872     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4873 #if ZIPPY
4874     if(bookHit) { // [HGM] book: simulate book reply
4875         static char bookMove[MSG_SIZ]; // a bit generous?
4876
4877         programStats.nodes = programStats.depth = programStats.time =
4878         programStats.score = programStats.got_only_move = 0;
4879         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4880
4881         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4882         strcat(bookMove, bookHit);
4883         HandleMachineMove(bookMove, &first);
4884     }
4885 #endif
4886 }
4887
4888 void
4889 GetMoveListEvent ()
4890 {
4891     char buf[MSG_SIZ];
4892     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4893         ics_getting_history = H_REQUESTED;
4894         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4895         SendToICS(buf);
4896     }
4897 }
4898
4899 void
4900 AnalysisPeriodicEvent (int force)
4901 {
4902     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4903          && !force) || !appData.periodicUpdates)
4904       return;
4905
4906     /* Send . command to Crafty to collect stats */
4907     SendToProgram(".\n", &first);
4908
4909     /* Don't send another until we get a response (this makes
4910        us stop sending to old Crafty's which don't understand
4911        the "." command (sending illegal cmds resets node count & time,
4912        which looks bad)) */
4913     programStats.ok_to_send = 0;
4914 }
4915
4916 void
4917 ics_update_width (int new_width)
4918 {
4919         ics_printf("set width %d\n", new_width);
4920 }
4921
4922 void
4923 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4924 {
4925     char buf[MSG_SIZ];
4926
4927     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4928         // null move in variant where engine does not understand it (for analysis purposes)
4929         SendBoard(cps, moveNum + 1); // send position after move in stead.
4930         return;
4931     }
4932     if (cps->useUsermove) {
4933       SendToProgram("usermove ", cps);
4934     }
4935     if (cps->useSAN) {
4936       char *space;
4937       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4938         int len = space - parseList[moveNum];
4939         memcpy(buf, parseList[moveNum], len);
4940         buf[len++] = '\n';
4941         buf[len] = NULLCHAR;
4942       } else {
4943         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4944       }
4945       SendToProgram(buf, cps);
4946     } else {
4947       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4948         AlphaRank(moveList[moveNum], 4);
4949         SendToProgram(moveList[moveNum], cps);
4950         AlphaRank(moveList[moveNum], 4); // and back
4951       } else
4952       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4953        * the engine. It would be nice to have a better way to identify castle
4954        * moves here. */
4955       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4956                                                                          && cps->useOOCastle) {
4957         int fromX = moveList[moveNum][0] - AAA;
4958         int fromY = moveList[moveNum][1] - ONE;
4959         int toX = moveList[moveNum][2] - AAA;
4960         int toY = moveList[moveNum][3] - ONE;
4961         if((boards[moveNum][fromY][fromX] == WhiteKing
4962             && boards[moveNum][toY][toX] == WhiteRook)
4963            || (boards[moveNum][fromY][fromX] == BlackKing
4964                && boards[moveNum][toY][toX] == BlackRook)) {
4965           if(toX > fromX) SendToProgram("O-O\n", cps);
4966           else SendToProgram("O-O-O\n", cps);
4967         }
4968         else SendToProgram(moveList[moveNum], cps);
4969       } else
4970       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4971         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4972           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4973           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4974                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4975         } else
4976           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4977                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4978         SendToProgram(buf, cps);
4979       }
4980       else SendToProgram(moveList[moveNum], cps);
4981       /* End of additions by Tord */
4982     }
4983
4984     /* [HGM] setting up the opening has brought engine in force mode! */
4985     /*       Send 'go' if we are in a mode where machine should play. */
4986     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4987         (gameMode == TwoMachinesPlay   ||
4988 #if ZIPPY
4989          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4990 #endif
4991          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4992         SendToProgram("go\n", cps);
4993   if (appData.debugMode) {
4994     fprintf(debugFP, "(extra)\n");
4995   }
4996     }
4997     setboardSpoiledMachineBlack = 0;
4998 }
4999
5000 void
5001 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5002 {
5003     char user_move[MSG_SIZ];
5004     char suffix[4];
5005
5006     if(gameInfo.variant == VariantSChess && promoChar) {
5007         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5008         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5009     } else suffix[0] = NULLCHAR;
5010
5011     switch (moveType) {
5012       default:
5013         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5014                 (int)moveType, fromX, fromY, toX, toY);
5015         DisplayError(user_move + strlen("say "), 0);
5016         break;
5017       case WhiteKingSideCastle:
5018       case BlackKingSideCastle:
5019       case WhiteQueenSideCastleWild:
5020       case BlackQueenSideCastleWild:
5021       /* PUSH Fabien */
5022       case WhiteHSideCastleFR:
5023       case BlackHSideCastleFR:
5024       /* POP Fabien */
5025         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5026         break;
5027       case WhiteQueenSideCastle:
5028       case BlackQueenSideCastle:
5029       case WhiteKingSideCastleWild:
5030       case BlackKingSideCastleWild:
5031       /* PUSH Fabien */
5032       case WhiteASideCastleFR:
5033       case BlackASideCastleFR:
5034       /* POP Fabien */
5035         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5036         break;
5037       case WhiteNonPromotion:
5038       case BlackNonPromotion:
5039         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5040         break;
5041       case WhitePromotion:
5042       case BlackPromotion:
5043         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5044           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5045                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5046                 PieceToChar(WhiteFerz));
5047         else if(gameInfo.variant == VariantGreat)
5048           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5049                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5050                 PieceToChar(WhiteMan));
5051         else
5052           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5053                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5054                 promoChar);
5055         break;
5056       case WhiteDrop:
5057       case BlackDrop:
5058       drop:
5059         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5060                  ToUpper(PieceToChar((ChessSquare) fromX)),
5061                  AAA + toX, ONE + toY);
5062         break;
5063       case IllegalMove:  /* could be a variant we don't quite understand */
5064         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5065       case NormalMove:
5066       case WhiteCapturesEnPassant:
5067       case BlackCapturesEnPassant:
5068         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5069                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5070         break;
5071     }
5072     SendToICS(user_move);
5073     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5074         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5075 }
5076
5077 void
5078 UploadGameEvent ()
5079 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5080     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5081     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5082     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5083       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5084       return;
5085     }
5086     if(gameMode != IcsExamining) { // is this ever not the case?
5087         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5088
5089         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5090           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5091         } else { // on FICS we must first go to general examine mode
5092           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5093         }
5094         if(gameInfo.variant != VariantNormal) {
5095             // try figure out wild number, as xboard names are not always valid on ICS
5096             for(i=1; i<=36; i++) {
5097               snprintf(buf, MSG_SIZ, "wild/%d", i);
5098                 if(StringToVariant(buf) == gameInfo.variant) break;
5099             }
5100             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5101             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5102             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5103         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5104         SendToICS(ics_prefix);
5105         SendToICS(buf);
5106         if(startedFromSetupPosition || backwardMostMove != 0) {
5107           fen = PositionToFEN(backwardMostMove, NULL);
5108           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5109             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5110             SendToICS(buf);
5111           } else { // FICS: everything has to set by separate bsetup commands
5112             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5113             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5114             SendToICS(buf);
5115             if(!WhiteOnMove(backwardMostMove)) {
5116                 SendToICS("bsetup tomove black\n");
5117             }
5118             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5119             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5120             SendToICS(buf);
5121             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5122             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5123             SendToICS(buf);
5124             i = boards[backwardMostMove][EP_STATUS];
5125             if(i >= 0) { // set e.p.
5126               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5127                 SendToICS(buf);
5128             }
5129             bsetup++;
5130           }
5131         }
5132       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5133             SendToICS("bsetup done\n"); // switch to normal examining.
5134     }
5135     for(i = backwardMostMove; i<last; i++) {
5136         char buf[20];
5137         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5138         SendToICS(buf);
5139     }
5140     SendToICS(ics_prefix);
5141     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5142 }
5143
5144 void
5145 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5146 {
5147     if (rf == DROP_RANK) {
5148       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5149       sprintf(move, "%c@%c%c\n",
5150                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5151     } else {
5152         if (promoChar == 'x' || promoChar == NULLCHAR) {
5153           sprintf(move, "%c%c%c%c\n",
5154                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5155         } else {
5156             sprintf(move, "%c%c%c%c%c\n",
5157                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5158         }
5159     }
5160 }
5161
5162 void
5163 ProcessICSInitScript (FILE *f)
5164 {
5165     char buf[MSG_SIZ];
5166
5167     while (fgets(buf, MSG_SIZ, f)) {
5168         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5169     }
5170
5171     fclose(f);
5172 }
5173
5174
5175 static int lastX, lastY, selectFlag, dragging;
5176
5177 void
5178 Sweep (int step)
5179 {
5180     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5181     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5182     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5183     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5184     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5185     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5186     do {
5187         promoSweep -= step;
5188         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5189         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5190         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5191         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5192         if(!step) step = -1;
5193     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5194             appData.testLegality && (promoSweep == king ||
5195             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5196     if(toX >= 0) {
5197         int victim = boards[currentMove][toY][toX];
5198         boards[currentMove][toY][toX] = promoSweep;
5199         DrawPosition(FALSE, boards[currentMove]);
5200         boards[currentMove][toY][toX] = victim;
5201     } else
5202     ChangeDragPiece(promoSweep);
5203 }
5204
5205 int
5206 PromoScroll (int x, int y)
5207 {
5208   int step = 0;
5209
5210   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5211   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5212   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5213   if(!step) return FALSE;
5214   lastX = x; lastY = y;
5215   if((promoSweep < BlackPawn) == flipView) step = -step;
5216   if(step > 0) selectFlag = 1;
5217   if(!selectFlag) Sweep(step);
5218   return FALSE;
5219 }
5220
5221 void
5222 NextPiece (int step)
5223 {
5224     ChessSquare piece = boards[currentMove][toY][toX];
5225     do {
5226         pieceSweep -= step;
5227         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5228         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5229         if(!step) step = -1;
5230     } while(PieceToChar(pieceSweep) == '.');
5231     boards[currentMove][toY][toX] = pieceSweep;
5232     DrawPosition(FALSE, boards[currentMove]);
5233     boards[currentMove][toY][toX] = piece;
5234 }
5235 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5236 void
5237 AlphaRank (char *move, int n)
5238 {
5239 //    char *p = move, c; int x, y;
5240
5241     if (appData.debugMode) {
5242         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5243     }
5244
5245     if(move[1]=='*' &&
5246        move[2]>='0' && move[2]<='9' &&
5247        move[3]>='a' && move[3]<='x'    ) {
5248         move[1] = '@';
5249         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5250         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5251     } else
5252     if(move[0]>='0' && move[0]<='9' &&
5253        move[1]>='a' && move[1]<='x' &&
5254        move[2]>='0' && move[2]<='9' &&
5255        move[3]>='a' && move[3]<='x'    ) {
5256         /* input move, Shogi -> normal */
5257         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5258         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5259         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5260         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5261     } else
5262     if(move[1]=='@' &&
5263        move[3]>='0' && move[3]<='9' &&
5264        move[2]>='a' && move[2]<='x'    ) {
5265         move[1] = '*';
5266         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5267         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5268     } else
5269     if(
5270        move[0]>='a' && move[0]<='x' &&
5271        move[3]>='0' && move[3]<='9' &&
5272        move[2]>='a' && move[2]<='x'    ) {
5273          /* output move, normal -> Shogi */
5274         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5275         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5276         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5277         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5278         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5279     }
5280     if (appData.debugMode) {
5281         fprintf(debugFP, "   out = '%s'\n", move);
5282     }
5283 }
5284
5285 char yy_textstr[8000];
5286
5287 /* Parser for moves from gnuchess, ICS, or user typein box */
5288 Boolean
5289 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5290 {
5291     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5292
5293     switch (*moveType) {
5294       case WhitePromotion:
5295       case BlackPromotion:
5296       case WhiteNonPromotion:
5297       case BlackNonPromotion:
5298       case NormalMove:
5299       case WhiteCapturesEnPassant:
5300       case BlackCapturesEnPassant:
5301       case WhiteKingSideCastle:
5302       case WhiteQueenSideCastle:
5303       case BlackKingSideCastle:
5304       case BlackQueenSideCastle:
5305       case WhiteKingSideCastleWild:
5306       case WhiteQueenSideCastleWild:
5307       case BlackKingSideCastleWild:
5308       case BlackQueenSideCastleWild:
5309       /* Code added by Tord: */
5310       case WhiteHSideCastleFR:
5311       case WhiteASideCastleFR:
5312       case BlackHSideCastleFR:
5313       case BlackASideCastleFR:
5314       /* End of code added by Tord */
5315       case IllegalMove:         /* bug or odd chess variant */
5316         *fromX = currentMoveString[0] - AAA;
5317         *fromY = currentMoveString[1] - ONE;
5318         *toX = currentMoveString[2] - AAA;
5319         *toY = currentMoveString[3] - ONE;
5320         *promoChar = currentMoveString[4];
5321         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5322             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5323     if (appData.debugMode) {
5324         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5325     }
5326             *fromX = *fromY = *toX = *toY = 0;
5327             return FALSE;
5328         }
5329         if (appData.testLegality) {
5330           return (*moveType != IllegalMove);
5331         } else {
5332           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5333                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5334         }
5335
5336       case WhiteDrop:
5337       case BlackDrop:
5338         *fromX = *moveType == WhiteDrop ?
5339           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5340           (int) CharToPiece(ToLower(currentMoveString[0]));
5341         *fromY = DROP_RANK;
5342         *toX = currentMoveString[2] - AAA;
5343         *toY = currentMoveString[3] - ONE;
5344         *promoChar = NULLCHAR;
5345         return TRUE;
5346
5347       case AmbiguousMove:
5348       case ImpossibleMove:
5349       case EndOfFile:
5350       case ElapsedTime:
5351       case Comment:
5352       case PGNTag:
5353       case NAG:
5354       case WhiteWins:
5355       case BlackWins:
5356       case GameIsDrawn:
5357       default:
5358     if (appData.debugMode) {
5359         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5360     }
5361         /* bug? */
5362         *fromX = *fromY = *toX = *toY = 0;
5363         *promoChar = NULLCHAR;
5364         return FALSE;
5365     }
5366 }
5367
5368 Boolean pushed = FALSE;
5369 char *lastParseAttempt;
5370
5371 void
5372 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5373 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5374   int fromX, fromY, toX, toY; char promoChar;
5375   ChessMove moveType;
5376   Boolean valid;
5377   int nr = 0;
5378
5379   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5380     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5381     pushed = TRUE;
5382   }
5383   endPV = forwardMostMove;
5384   do {
5385     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5386     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5387     lastParseAttempt = pv;
5388     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5389     if(!valid && nr == 0 &&
5390        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5391         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5392         // Hande case where played move is different from leading PV move
5393         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5394         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5395         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5396         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5397           endPV += 2; // if position different, keep this
5398           moveList[endPV-1][0] = fromX + AAA;
5399           moveList[endPV-1][1] = fromY + ONE;
5400           moveList[endPV-1][2] = toX + AAA;
5401           moveList[endPV-1][3] = toY + ONE;
5402           parseList[endPV-1][0] = NULLCHAR;
5403           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5404         }
5405       }
5406     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5407     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5408     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5409     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5410         valid++; // allow comments in PV
5411         continue;
5412     }
5413     nr++;
5414     if(endPV+1 > framePtr) break; // no space, truncate
5415     if(!valid) break;
5416     endPV++;
5417     CopyBoard(boards[endPV], boards[endPV-1]);
5418     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5419     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5420     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5421     CoordsToAlgebraic(boards[endPV - 1],
5422                              PosFlags(endPV - 1),
5423                              fromY, fromX, toY, toX, promoChar,
5424                              parseList[endPV - 1]);
5425   } while(valid);
5426   if(atEnd == 2) return; // used hidden, for PV conversion
5427   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5428   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5429   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5430                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5431   DrawPosition(TRUE, boards[currentMove]);
5432 }
5433
5434 int
5435 MultiPV (ChessProgramState *cps)
5436 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5437         int i;
5438         for(i=0; i<cps->nrOptions; i++)
5439             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5440                 return i;
5441         return -1;
5442 }
5443
5444 Boolean
5445 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5446 {
5447         int startPV, multi, lineStart, origIndex = index;
5448         char *p, buf2[MSG_SIZ];
5449
5450         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5451         lastX = x; lastY = y;
5452         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5453         lineStart = startPV = index;
5454         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5455         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5456         index = startPV;
5457         do{ while(buf[index] && buf[index] != '\n') index++;
5458         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5459         buf[index] = 0;
5460         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5461                 int n = first.option[multi].value;
5462                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5463                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5464                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5465                 first.option[multi].value = n;
5466                 *start = *end = 0;
5467                 return FALSE;
5468         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5469                 ExcludeClick(origIndex - lineStart);
5470                 return FALSE;
5471         }
5472         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5473         *start = startPV; *end = index-1;
5474         return TRUE;
5475 }
5476
5477 char *
5478 PvToSAN (char *pv)
5479 {
5480         static char buf[10*MSG_SIZ];
5481         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5482         *buf = NULLCHAR;
5483         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5484         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5485         for(i = forwardMostMove; i<endPV; i++){
5486             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5487             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5488             k += strlen(buf+k);
5489         }
5490         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5491         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5492         endPV = savedEnd;
5493         return buf;
5494 }
5495
5496 Boolean
5497 LoadPV (int x, int y)
5498 { // called on right mouse click to load PV
5499   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5500   lastX = x; lastY = y;
5501   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5502   return TRUE;
5503 }
5504
5505 void
5506 UnLoadPV ()
5507 {
5508   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5509   if(endPV < 0) return;
5510   if(appData.autoCopyPV) CopyFENToClipboard();
5511   endPV = -1;
5512   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5513         Boolean saveAnimate = appData.animate;
5514         if(pushed) {
5515             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5516                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5517             } else storedGames--; // abandon shelved tail of original game
5518         }
5519         pushed = FALSE;
5520         forwardMostMove = currentMove;
5521         currentMove = oldFMM;
5522         appData.animate = FALSE;
5523         ToNrEvent(forwardMostMove);
5524         appData.animate = saveAnimate;
5525   }
5526   currentMove = forwardMostMove;
5527   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5528   ClearPremoveHighlights();
5529   DrawPosition(TRUE, boards[currentMove]);
5530 }
5531
5532 void
5533 MovePV (int x, int y, int h)
5534 { // step through PV based on mouse coordinates (called on mouse move)
5535   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5536
5537   // we must somehow check if right button is still down (might be released off board!)
5538   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5539   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5540   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5541   if(!step) return;
5542   lastX = x; lastY = y;
5543
5544   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5545   if(endPV < 0) return;
5546   if(y < margin) step = 1; else
5547   if(y > h - margin) step = -1;
5548   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5549   currentMove += step;
5550   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5551   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5552                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5553   DrawPosition(FALSE, boards[currentMove]);
5554 }
5555
5556
5557 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5558 // All positions will have equal probability, but the current method will not provide a unique
5559 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5560 #define DARK 1
5561 #define LITE 2
5562 #define ANY 3
5563
5564 int squaresLeft[4];
5565 int piecesLeft[(int)BlackPawn];
5566 int seed, nrOfShuffles;
5567
5568 void
5569 GetPositionNumber ()
5570 {       // sets global variable seed
5571         int i;
5572
5573         seed = appData.defaultFrcPosition;
5574         if(seed < 0) { // randomize based on time for negative FRC position numbers
5575                 for(i=0; i<50; i++) seed += random();
5576                 seed = random() ^ random() >> 8 ^ random() << 8;
5577                 if(seed<0) seed = -seed;
5578         }
5579 }
5580
5581 int
5582 put (Board board, int pieceType, int rank, int n, int shade)
5583 // put the piece on the (n-1)-th empty squares of the given shade
5584 {
5585         int i;
5586
5587         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5588                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5589                         board[rank][i] = (ChessSquare) pieceType;
5590                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5591                         squaresLeft[ANY]--;
5592                         piecesLeft[pieceType]--;
5593                         return i;
5594                 }
5595         }
5596         return -1;
5597 }
5598
5599
5600 void
5601 AddOnePiece (Board board, int pieceType, int rank, int shade)
5602 // calculate where the next piece goes, (any empty square), and put it there
5603 {
5604         int i;
5605
5606         i = seed % squaresLeft[shade];
5607         nrOfShuffles *= squaresLeft[shade];
5608         seed /= squaresLeft[shade];
5609         put(board, pieceType, rank, i, shade);
5610 }
5611
5612 void
5613 AddTwoPieces (Board board, int pieceType, int rank)
5614 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5615 {
5616         int i, n=squaresLeft[ANY], j=n-1, k;
5617
5618         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5619         i = seed % k;  // pick one
5620         nrOfShuffles *= k;
5621         seed /= k;
5622         while(i >= j) i -= j--;
5623         j = n - 1 - j; i += j;
5624         put(board, pieceType, rank, j, ANY);
5625         put(board, pieceType, rank, i, ANY);
5626 }
5627
5628 void
5629 SetUpShuffle (Board board, int number)
5630 {
5631         int i, p, first=1;
5632
5633         GetPositionNumber(); nrOfShuffles = 1;
5634
5635         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5636         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5637         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5638
5639         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5640
5641         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5642             p = (int) board[0][i];
5643             if(p < (int) BlackPawn) piecesLeft[p] ++;
5644             board[0][i] = EmptySquare;
5645         }
5646
5647         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5648             // shuffles restricted to allow normal castling put KRR first
5649             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5650                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5651             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5652                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5653             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5654                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5655             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5656                 put(board, WhiteRook, 0, 0, ANY);
5657             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5658         }
5659
5660         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5661             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5662             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5663                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5664                 while(piecesLeft[p] >= 2) {
5665                     AddOnePiece(board, p, 0, LITE);
5666                     AddOnePiece(board, p, 0, DARK);
5667                 }
5668                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5669             }
5670
5671         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5672             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5673             // but we leave King and Rooks for last, to possibly obey FRC restriction
5674             if(p == (int)WhiteRook) continue;
5675             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5676             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5677         }
5678
5679         // now everything is placed, except perhaps King (Unicorn) and Rooks
5680
5681         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5682             // Last King gets castling rights
5683             while(piecesLeft[(int)WhiteUnicorn]) {
5684                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5685                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5686             }
5687
5688             while(piecesLeft[(int)WhiteKing]) {
5689                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5690                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5691             }
5692
5693
5694         } else {
5695             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5696             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5697         }
5698
5699         // Only Rooks can be left; simply place them all
5700         while(piecesLeft[(int)WhiteRook]) {
5701                 i = put(board, WhiteRook, 0, 0, ANY);
5702                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5703                         if(first) {
5704                                 first=0;
5705                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5706                         }
5707                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5708                 }
5709         }
5710         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5711             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5712         }
5713
5714         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5715 }
5716
5717 int
5718 SetCharTable (char *table, const char * map)
5719 /* [HGM] moved here from winboard.c because of its general usefulness */
5720 /*       Basically a safe strcpy that uses the last character as King */
5721 {
5722     int result = FALSE; int NrPieces;
5723
5724     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5725                     && NrPieces >= 12 && !(NrPieces&1)) {
5726         int i; /* [HGM] Accept even length from 12 to 34 */
5727
5728         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5729         for( i=0; i<NrPieces/2-1; i++ ) {
5730             table[i] = map[i];
5731             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5732         }
5733         table[(int) WhiteKing]  = map[NrPieces/2-1];
5734         table[(int) BlackKing]  = map[NrPieces-1];
5735
5736         result = TRUE;
5737     }
5738
5739     return result;
5740 }
5741
5742 void
5743 Prelude (Board board)
5744 {       // [HGM] superchess: random selection of exo-pieces
5745         int i, j, k; ChessSquare p;
5746         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5747
5748         GetPositionNumber(); // use FRC position number
5749
5750         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5751             SetCharTable(pieceToChar, appData.pieceToCharTable);
5752             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5753                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5754         }
5755
5756         j = seed%4;                 seed /= 4;
5757         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5758         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5759         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5760         j = seed%3 + (seed%3 >= j); seed /= 3;
5761         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5762         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5763         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5764         j = seed%3;                 seed /= 3;
5765         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5766         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5767         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5768         j = seed%2 + (seed%2 >= j); seed /= 2;
5769         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5770         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5771         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5772         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5773         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5774         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5775         put(board, exoPieces[0],    0, 0, ANY);
5776         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5777 }
5778
5779 void
5780 InitPosition (int redraw)
5781 {
5782     ChessSquare (* pieces)[BOARD_FILES];
5783     int i, j, pawnRow, overrule,
5784     oldx = gameInfo.boardWidth,
5785     oldy = gameInfo.boardHeight,
5786     oldh = gameInfo.holdingsWidth;
5787     static int oldv;
5788
5789     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5790
5791     /* [AS] Initialize pv info list [HGM] and game status */
5792     {
5793         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5794             pvInfoList[i].depth = 0;
5795             boards[i][EP_STATUS] = EP_NONE;
5796             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5797         }
5798
5799         initialRulePlies = 0; /* 50-move counter start */
5800
5801         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5802         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5803     }
5804
5805
5806     /* [HGM] logic here is completely changed. In stead of full positions */
5807     /* the initialized data only consist of the two backranks. The switch */
5808     /* selects which one we will use, which is than copied to the Board   */
5809     /* initialPosition, which for the rest is initialized by Pawns and    */
5810     /* empty squares. This initial position is then copied to boards[0],  */
5811     /* possibly after shuffling, so that it remains available.            */
5812
5813     gameInfo.holdingsWidth = 0; /* default board sizes */
5814     gameInfo.boardWidth    = 8;
5815     gameInfo.boardHeight   = 8;
5816     gameInfo.holdingsSize  = 0;
5817     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5818     for(i=0; i<BOARD_FILES-2; i++)
5819       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5820     initialPosition[EP_STATUS] = EP_NONE;
5821     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5822     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5823          SetCharTable(pieceNickName, appData.pieceNickNames);
5824     else SetCharTable(pieceNickName, "............");
5825     pieces = FIDEArray;
5826
5827     switch (gameInfo.variant) {
5828     case VariantFischeRandom:
5829       shuffleOpenings = TRUE;
5830     default:
5831       break;
5832     case VariantShatranj:
5833       pieces = ShatranjArray;
5834       nrCastlingRights = 0;
5835       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5836       break;
5837     case VariantMakruk:
5838       pieces = makrukArray;
5839       nrCastlingRights = 0;
5840       startedFromSetupPosition = TRUE;
5841       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5842       break;
5843     case VariantTwoKings:
5844       pieces = twoKingsArray;
5845       break;
5846     case VariantGrand:
5847       pieces = GrandArray;
5848       nrCastlingRights = 0;
5849       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5850       gameInfo.boardWidth = 10;
5851       gameInfo.boardHeight = 10;
5852       gameInfo.holdingsSize = 7;
5853       break;
5854     case VariantCapaRandom:
5855       shuffleOpenings = TRUE;
5856     case VariantCapablanca:
5857       pieces = CapablancaArray;
5858       gameInfo.boardWidth = 10;
5859       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5860       break;
5861     case VariantGothic:
5862       pieces = GothicArray;
5863       gameInfo.boardWidth = 10;
5864       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5865       break;
5866     case VariantSChess:
5867       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5868       gameInfo.holdingsSize = 7;
5869       break;
5870     case VariantJanus:
5871       pieces = JanusArray;
5872       gameInfo.boardWidth = 10;
5873       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5874       nrCastlingRights = 6;
5875         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5876         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5877         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5878         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5879         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5880         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5881       break;
5882     case VariantFalcon:
5883       pieces = FalconArray;
5884       gameInfo.boardWidth = 10;
5885       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5886       break;
5887     case VariantXiangqi:
5888       pieces = XiangqiArray;
5889       gameInfo.boardWidth  = 9;
5890       gameInfo.boardHeight = 10;
5891       nrCastlingRights = 0;
5892       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5893       break;
5894     case VariantShogi:
5895       pieces = ShogiArray;
5896       gameInfo.boardWidth  = 9;
5897       gameInfo.boardHeight = 9;
5898       gameInfo.holdingsSize = 7;
5899       nrCastlingRights = 0;
5900       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5901       break;
5902     case VariantCourier:
5903       pieces = CourierArray;
5904       gameInfo.boardWidth  = 12;
5905       nrCastlingRights = 0;
5906       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5907       break;
5908     case VariantKnightmate:
5909       pieces = KnightmateArray;
5910       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5911       break;
5912     case VariantSpartan:
5913       pieces = SpartanArray;
5914       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5915       break;
5916     case VariantFairy:
5917       pieces = fairyArray;
5918       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5919       break;
5920     case VariantGreat:
5921       pieces = GreatArray;
5922       gameInfo.boardWidth = 10;
5923       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5924       gameInfo.holdingsSize = 8;
5925       break;
5926     case VariantSuper:
5927       pieces = FIDEArray;
5928       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5929       gameInfo.holdingsSize = 8;
5930       startedFromSetupPosition = TRUE;
5931       break;
5932     case VariantCrazyhouse:
5933     case VariantBughouse:
5934       pieces = FIDEArray;
5935       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5936       gameInfo.holdingsSize = 5;
5937       break;
5938     case VariantWildCastle:
5939       pieces = FIDEArray;
5940       /* !!?shuffle with kings guaranteed to be on d or e file */
5941       shuffleOpenings = 1;
5942       break;
5943     case VariantNoCastle:
5944       pieces = FIDEArray;
5945       nrCastlingRights = 0;
5946       /* !!?unconstrained back-rank shuffle */
5947       shuffleOpenings = 1;
5948       break;
5949     }
5950
5951     overrule = 0;
5952     if(appData.NrFiles >= 0) {
5953         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5954         gameInfo.boardWidth = appData.NrFiles;
5955     }
5956     if(appData.NrRanks >= 0) {
5957         gameInfo.boardHeight = appData.NrRanks;
5958     }
5959     if(appData.holdingsSize >= 0) {
5960         i = appData.holdingsSize;
5961         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5962         gameInfo.holdingsSize = i;
5963     }
5964     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5965     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5966         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5967
5968     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5969     if(pawnRow < 1) pawnRow = 1;
5970     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5971
5972     /* User pieceToChar list overrules defaults */
5973     if(appData.pieceToCharTable != NULL)
5974         SetCharTable(pieceToChar, appData.pieceToCharTable);
5975
5976     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5977
5978         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5979             s = (ChessSquare) 0; /* account holding counts in guard band */
5980         for( i=0; i<BOARD_HEIGHT; i++ )
5981             initialPosition[i][j] = s;
5982
5983         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5984         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5985         initialPosition[pawnRow][j] = WhitePawn;
5986         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5987         if(gameInfo.variant == VariantXiangqi) {
5988             if(j&1) {
5989                 initialPosition[pawnRow][j] =
5990                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5991                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5992                    initialPosition[2][j] = WhiteCannon;
5993                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5994                 }
5995             }
5996         }
5997         if(gameInfo.variant == VariantGrand) {
5998             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5999                initialPosition[0][j] = WhiteRook;
6000                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6001             }
6002         }
6003         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6004     }
6005     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6006
6007             j=BOARD_LEFT+1;
6008             initialPosition[1][j] = WhiteBishop;
6009             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6010             j=BOARD_RGHT-2;
6011             initialPosition[1][j] = WhiteRook;
6012             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6013     }
6014
6015     if( nrCastlingRights == -1) {
6016         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6017         /*       This sets default castling rights from none to normal corners   */
6018         /* Variants with other castling rights must set them themselves above    */
6019         nrCastlingRights = 6;
6020
6021         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6022         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6023         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6024         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6025         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6026         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6027      }
6028
6029      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6030      if(gameInfo.variant == VariantGreat) { // promotion commoners
6031         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6032         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6033         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6034         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6035      }
6036      if( gameInfo.variant == VariantSChess ) {
6037       initialPosition[1][0] = BlackMarshall;
6038       initialPosition[2][0] = BlackAngel;
6039       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6040       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6041       initialPosition[1][1] = initialPosition[2][1] = 
6042       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6043      }
6044   if (appData.debugMode) {
6045     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6046   }
6047     if(shuffleOpenings) {
6048         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6049         startedFromSetupPosition = TRUE;
6050     }
6051     if(startedFromPositionFile) {
6052       /* [HGM] loadPos: use PositionFile for every new game */
6053       CopyBoard(initialPosition, filePosition);
6054       for(i=0; i<nrCastlingRights; i++)
6055           initialRights[i] = filePosition[CASTLING][i];
6056       startedFromSetupPosition = TRUE;
6057     }
6058
6059     CopyBoard(boards[0], initialPosition);
6060
6061     if(oldx != gameInfo.boardWidth ||
6062        oldy != gameInfo.boardHeight ||
6063        oldv != gameInfo.variant ||
6064        oldh != gameInfo.holdingsWidth
6065                                          )
6066             InitDrawingSizes(-2 ,0);
6067
6068     oldv = gameInfo.variant;
6069     if (redraw)
6070       DrawPosition(TRUE, boards[currentMove]);
6071 }
6072
6073 void
6074 SendBoard (ChessProgramState *cps, int moveNum)
6075 {
6076     char message[MSG_SIZ];
6077
6078     if (cps->useSetboard) {
6079       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6080       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6081       SendToProgram(message, cps);
6082       free(fen);
6083
6084     } else {
6085       ChessSquare *bp;
6086       int i, j, left=0, right=BOARD_WIDTH;
6087       /* Kludge to set black to move, avoiding the troublesome and now
6088        * deprecated "black" command.
6089        */
6090       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6091         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6092
6093       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6094
6095       SendToProgram("edit\n", cps);
6096       SendToProgram("#\n", cps);
6097       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6098         bp = &boards[moveNum][i][left];
6099         for (j = left; j < right; j++, bp++) {
6100           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6101           if ((int) *bp < (int) BlackPawn) {
6102             if(j == BOARD_RGHT+1)
6103                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6104             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6105             if(message[0] == '+' || message[0] == '~') {
6106               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6107                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6108                         AAA + j, ONE + i);
6109             }
6110             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6111                 message[1] = BOARD_RGHT   - 1 - j + '1';
6112                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6113             }
6114             SendToProgram(message, cps);
6115           }
6116         }
6117       }
6118
6119       SendToProgram("c\n", cps);
6120       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6121         bp = &boards[moveNum][i][left];
6122         for (j = left; j < right; j++, bp++) {
6123           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6124           if (((int) *bp != (int) EmptySquare)
6125               && ((int) *bp >= (int) BlackPawn)) {
6126             if(j == BOARD_LEFT-2)
6127                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6128             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6129                     AAA + j, ONE + i);
6130             if(message[0] == '+' || message[0] == '~') {
6131               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6132                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6133                         AAA + j, ONE + i);
6134             }
6135             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6136                 message[1] = BOARD_RGHT   - 1 - j + '1';
6137                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6138             }
6139             SendToProgram(message, cps);
6140           }
6141         }
6142       }
6143
6144       SendToProgram(".\n", cps);
6145     }
6146     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6147 }
6148
6149 char exclusionHeader[MSG_SIZ];
6150 int exCnt, excludePtr;
6151 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6152 static Exclusion excluTab[200];
6153 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6154
6155 static void
6156 WriteMap (int s)
6157 {
6158     int j;
6159     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6160     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6161 }
6162
6163 static void
6164 ClearMap ()
6165 {
6166     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6167     excludePtr = 24; exCnt = 0;
6168     WriteMap(0);
6169 }
6170
6171 static void
6172 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6173 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6174     char buf[2*MOVE_LEN], *p;
6175     Exclusion *e = excluTab;
6176     int i;
6177     for(i=0; i<exCnt; i++)
6178         if(e[i].ff == fromX && e[i].fr == fromY &&
6179            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6180     if(i == exCnt) { // was not in exclude list; add it
6181         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6182         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6183             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6184             return; // abort
6185         }
6186         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6187         excludePtr++; e[i].mark = excludePtr++;
6188         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6189         exCnt++;
6190     }
6191     exclusionHeader[e[i].mark] = state;
6192 }
6193
6194 static int
6195 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6196 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6197     char buf[MSG_SIZ];
6198     int j, k;
6199     ChessMove moveType;
6200     if((signed char)promoChar == -1) { // kludge to indicate best move
6201         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6202             return 1; // if unparsable, abort
6203     }
6204     // update exclusion map (resolving toggle by consulting existing state)
6205     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6206     j = k%8; k >>= 3;
6207     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6208     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6209          excludeMap[k] |=   1<<j;
6210     else excludeMap[k] &= ~(1<<j);
6211     // update header
6212     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6213     // inform engine
6214     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6215     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6216     SendToProgram(buf, &first);
6217     return (state == '+');
6218 }
6219
6220 static void
6221 ExcludeClick (int index)
6222 {
6223     int i, j;
6224     Exclusion *e = excluTab;
6225     if(index < 25) { // none, best or tail clicked
6226         if(index < 13) { // none: include all
6227             WriteMap(0); // clear map
6228             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6229             SendToProgram("include all\n", &first); // and inform engine
6230         } else if(index > 18) { // tail
6231             if(exclusionHeader[19] == '-') { // tail was excluded
6232                 SendToProgram("include all\n", &first);
6233                 WriteMap(0); // clear map completely
6234                 // now re-exclude selected moves
6235                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6236                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6237             } else { // tail was included or in mixed state
6238                 SendToProgram("exclude all\n", &first);
6239                 WriteMap(0xFF); // fill map completely
6240                 // now re-include selected moves
6241                 j = 0; // count them
6242                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6243                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6244                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6245             }
6246         } else { // best
6247             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6248         }
6249     } else {
6250         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6251             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6252             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6253             break;
6254         }
6255     }
6256 }
6257
6258 ChessSquare
6259 DefaultPromoChoice (int white)
6260 {
6261     ChessSquare result;
6262     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6263         result = WhiteFerz; // no choice
6264     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6265         result= WhiteKing; // in Suicide Q is the last thing we want
6266     else if(gameInfo.variant == VariantSpartan)
6267         result = white ? WhiteQueen : WhiteAngel;
6268     else result = WhiteQueen;
6269     if(!white) result = WHITE_TO_BLACK result;
6270     return result;
6271 }
6272
6273 static int autoQueen; // [HGM] oneclick
6274
6275 int
6276 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6277 {
6278     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6279     /* [HGM] add Shogi promotions */
6280     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6281     ChessSquare piece;
6282     ChessMove moveType;
6283     Boolean premove;
6284
6285     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6286     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6287
6288     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6289       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6290         return FALSE;
6291
6292     piece = boards[currentMove][fromY][fromX];
6293     if(gameInfo.variant == VariantShogi) {
6294         promotionZoneSize = BOARD_HEIGHT/3;
6295         highestPromotingPiece = (int)WhiteFerz;
6296     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6297         promotionZoneSize = 3;
6298     }
6299
6300     // Treat Lance as Pawn when it is not representing Amazon
6301     if(gameInfo.variant != VariantSuper) {
6302         if(piece == WhiteLance) piece = WhitePawn; else
6303         if(piece == BlackLance) piece = BlackPawn;
6304     }
6305
6306     // next weed out all moves that do not touch the promotion zone at all
6307     if((int)piece >= BlackPawn) {
6308         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6309              return FALSE;
6310         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6311     } else {
6312         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6313            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6314     }
6315
6316     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6317
6318     // weed out mandatory Shogi promotions
6319     if(gameInfo.variant == VariantShogi) {
6320         if(piece >= BlackPawn) {
6321             if(toY == 0 && piece == BlackPawn ||
6322                toY == 0 && piece == BlackQueen ||
6323                toY <= 1 && piece == BlackKnight) {
6324                 *promoChoice = '+';
6325                 return FALSE;
6326             }
6327         } else {
6328             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6329                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6330                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6331                 *promoChoice = '+';
6332                 return FALSE;
6333             }
6334         }
6335     }
6336
6337     // weed out obviously illegal Pawn moves
6338     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6339         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6340         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6341         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6342         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6343         // note we are not allowed to test for valid (non-)capture, due to premove
6344     }
6345
6346     // we either have a choice what to promote to, or (in Shogi) whether to promote
6347     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6348         *promoChoice = PieceToChar(BlackFerz);  // no choice
6349         return FALSE;
6350     }
6351     // no sense asking what we must promote to if it is going to explode...
6352     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6353         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6354         return FALSE;
6355     }
6356     // give caller the default choice even if we will not make it
6357     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6358     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6359     if(        sweepSelect && gameInfo.variant != VariantGreat
6360                            && gameInfo.variant != VariantGrand
6361                            && gameInfo.variant != VariantSuper) return FALSE;
6362     if(autoQueen) return FALSE; // predetermined
6363
6364     // suppress promotion popup on illegal moves that are not premoves
6365     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6366               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6367     if(appData.testLegality && !premove) {
6368         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6369                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6370         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6371             return FALSE;
6372     }
6373
6374     return TRUE;
6375 }
6376
6377 int
6378 InPalace (int row, int column)
6379 {   /* [HGM] for Xiangqi */
6380     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6381          column < (BOARD_WIDTH + 4)/2 &&
6382          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6383     return FALSE;
6384 }
6385
6386 int
6387 PieceForSquare (int x, int y)
6388 {
6389   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6390      return -1;
6391   else
6392      return boards[currentMove][y][x];
6393 }
6394
6395 int
6396 OKToStartUserMove (int x, int y)
6397 {
6398     ChessSquare from_piece;
6399     int white_piece;
6400
6401     if (matchMode) return FALSE;
6402     if (gameMode == EditPosition) return TRUE;
6403
6404     if (x >= 0 && y >= 0)
6405       from_piece = boards[currentMove][y][x];
6406     else
6407       from_piece = EmptySquare;
6408
6409     if (from_piece == EmptySquare) return FALSE;
6410
6411     white_piece = (int)from_piece >= (int)WhitePawn &&
6412       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6413
6414     switch (gameMode) {
6415       case AnalyzeFile:
6416       case TwoMachinesPlay:
6417       case EndOfGame:
6418         return FALSE;
6419
6420       case IcsObserving:
6421       case IcsIdle:
6422         return FALSE;
6423
6424       case MachinePlaysWhite:
6425       case IcsPlayingBlack:
6426         if (appData.zippyPlay) return FALSE;
6427         if (white_piece) {
6428             DisplayMoveError(_("You are playing Black"));
6429             return FALSE;
6430         }
6431         break;
6432
6433       case MachinePlaysBlack:
6434       case IcsPlayingWhite:
6435         if (appData.zippyPlay) return FALSE;
6436         if (!white_piece) {
6437             DisplayMoveError(_("You are playing White"));
6438             return FALSE;
6439         }
6440         break;
6441
6442       case PlayFromGameFile:
6443             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6444       case EditGame:
6445         if (!white_piece && WhiteOnMove(currentMove)) {
6446             DisplayMoveError(_("It is White's turn"));
6447             return FALSE;
6448         }
6449         if (white_piece && !WhiteOnMove(currentMove)) {
6450             DisplayMoveError(_("It is Black's turn"));
6451             return FALSE;
6452         }
6453         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6454             /* Editing correspondence game history */
6455             /* Could disallow this or prompt for confirmation */
6456             cmailOldMove = -1;
6457         }
6458         break;
6459
6460       case BeginningOfGame:
6461         if (appData.icsActive) return FALSE;
6462         if (!appData.noChessProgram) {
6463             if (!white_piece) {
6464                 DisplayMoveError(_("You are playing White"));
6465                 return FALSE;
6466             }
6467         }
6468         break;
6469
6470       case Training:
6471         if (!white_piece && WhiteOnMove(currentMove)) {
6472             DisplayMoveError(_("It is White's turn"));
6473             return FALSE;
6474         }
6475         if (white_piece && !WhiteOnMove(currentMove)) {
6476             DisplayMoveError(_("It is Black's turn"));
6477             return FALSE;
6478         }
6479         break;
6480
6481       default:
6482       case IcsExamining:
6483         break;
6484     }
6485     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6486         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6487         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6488         && gameMode != AnalyzeFile && gameMode != Training) {
6489         DisplayMoveError(_("Displayed position is not current"));
6490         return FALSE;
6491     }
6492     return TRUE;
6493 }
6494
6495 Boolean
6496 OnlyMove (int *x, int *y, Boolean captures) 
6497 {
6498     DisambiguateClosure cl;
6499     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6500     switch(gameMode) {
6501       case MachinePlaysBlack:
6502       case IcsPlayingWhite:
6503       case BeginningOfGame:
6504         if(!WhiteOnMove(currentMove)) return FALSE;
6505         break;
6506       case MachinePlaysWhite:
6507       case IcsPlayingBlack:
6508         if(WhiteOnMove(currentMove)) return FALSE;
6509         break;
6510       case EditGame:
6511         break;
6512       default:
6513         return FALSE;
6514     }
6515     cl.pieceIn = EmptySquare;
6516     cl.rfIn = *y;
6517     cl.ffIn = *x;
6518     cl.rtIn = -1;
6519     cl.ftIn = -1;
6520     cl.promoCharIn = NULLCHAR;
6521     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6522     if( cl.kind == NormalMove ||
6523         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6524         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6525         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6526       fromX = cl.ff;
6527       fromY = cl.rf;
6528       *x = cl.ft;
6529       *y = cl.rt;
6530       return TRUE;
6531     }
6532     if(cl.kind != ImpossibleMove) return FALSE;
6533     cl.pieceIn = EmptySquare;
6534     cl.rfIn = -1;
6535     cl.ffIn = -1;
6536     cl.rtIn = *y;
6537     cl.ftIn = *x;
6538     cl.promoCharIn = NULLCHAR;
6539     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6540     if( cl.kind == NormalMove ||
6541         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6542         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6543         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6544       fromX = cl.ff;
6545       fromY = cl.rf;
6546       *x = cl.ft;
6547       *y = cl.rt;
6548       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6549       return TRUE;
6550     }
6551     return FALSE;
6552 }
6553
6554 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6555 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6556 int lastLoadGameUseList = FALSE;
6557 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6558 ChessMove lastLoadGameStart = EndOfFile;
6559 int doubleClick;
6560
6561 void
6562 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6563 {
6564     ChessMove moveType;
6565     ChessSquare pup;
6566     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6567
6568     /* Check if the user is playing in turn.  This is complicated because we
6569        let the user "pick up" a piece before it is his turn.  So the piece he
6570        tried to pick up may have been captured by the time he puts it down!
6571        Therefore we use the color the user is supposed to be playing in this
6572        test, not the color of the piece that is currently on the starting
6573        square---except in EditGame mode, where the user is playing both
6574        sides; fortunately there the capture race can't happen.  (It can
6575        now happen in IcsExamining mode, but that's just too bad.  The user
6576        will get a somewhat confusing message in that case.)
6577        */
6578
6579     switch (gameMode) {
6580       case AnalyzeFile:
6581       case TwoMachinesPlay:
6582       case EndOfGame:
6583       case IcsObserving:
6584       case IcsIdle:
6585         /* We switched into a game mode where moves are not accepted,
6586            perhaps while the mouse button was down. */
6587         return;
6588
6589       case MachinePlaysWhite:
6590         /* User is moving for Black */
6591         if (WhiteOnMove(currentMove)) {
6592             DisplayMoveError(_("It is White's turn"));
6593             return;
6594         }
6595         break;
6596
6597       case MachinePlaysBlack:
6598         /* User is moving for White */
6599         if (!WhiteOnMove(currentMove)) {
6600             DisplayMoveError(_("It is Black's turn"));
6601             return;
6602         }
6603         break;
6604
6605       case PlayFromGameFile:
6606             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6607       case EditGame:
6608       case IcsExamining:
6609       case BeginningOfGame:
6610       case AnalyzeMode:
6611       case Training:
6612         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6613         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6614             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6615             /* User is moving for Black */
6616             if (WhiteOnMove(currentMove)) {
6617                 DisplayMoveError(_("It is White's turn"));
6618                 return;
6619             }
6620         } else {
6621             /* User is moving for White */
6622             if (!WhiteOnMove(currentMove)) {
6623                 DisplayMoveError(_("It is Black's turn"));
6624                 return;
6625             }
6626         }
6627         break;
6628
6629       case IcsPlayingBlack:
6630         /* User is moving for Black */
6631         if (WhiteOnMove(currentMove)) {
6632             if (!appData.premove) {
6633                 DisplayMoveError(_("It is White's turn"));
6634             } else if (toX >= 0 && toY >= 0) {
6635                 premoveToX = toX;
6636                 premoveToY = toY;
6637                 premoveFromX = fromX;
6638                 premoveFromY = fromY;
6639                 premovePromoChar = promoChar;
6640                 gotPremove = 1;
6641                 if (appData.debugMode)
6642                     fprintf(debugFP, "Got premove: fromX %d,"
6643                             "fromY %d, toX %d, toY %d\n",
6644                             fromX, fromY, toX, toY);
6645             }
6646             return;
6647         }
6648         break;
6649
6650       case IcsPlayingWhite:
6651         /* User is moving for White */
6652         if (!WhiteOnMove(currentMove)) {
6653             if (!appData.premove) {
6654                 DisplayMoveError(_("It is Black's turn"));
6655             } else if (toX >= 0 && toY >= 0) {
6656                 premoveToX = toX;
6657                 premoveToY = toY;
6658                 premoveFromX = fromX;
6659                 premoveFromY = fromY;
6660                 premovePromoChar = promoChar;
6661                 gotPremove = 1;
6662                 if (appData.debugMode)
6663                     fprintf(debugFP, "Got premove: fromX %d,"
6664                             "fromY %d, toX %d, toY %d\n",
6665                             fromX, fromY, toX, toY);
6666             }
6667             return;
6668         }
6669         break;
6670
6671       default:
6672         break;
6673
6674       case EditPosition:
6675         /* EditPosition, empty square, or different color piece;
6676            click-click move is possible */
6677         if (toX == -2 || toY == -2) {
6678             boards[0][fromY][fromX] = EmptySquare;
6679             DrawPosition(FALSE, boards[currentMove]);
6680             return;
6681         } else if (toX >= 0 && toY >= 0) {
6682             boards[0][toY][toX] = boards[0][fromY][fromX];
6683             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6684                 if(boards[0][fromY][0] != EmptySquare) {
6685                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6686                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6687                 }
6688             } else
6689             if(fromX == BOARD_RGHT+1) {
6690                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6691                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6692                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6693                 }
6694             } else
6695             boards[0][fromY][fromX] = gatingPiece;
6696             DrawPosition(FALSE, boards[currentMove]);
6697             return;
6698         }
6699         return;
6700     }
6701
6702     if(toX < 0 || toY < 0) return;
6703     pup = boards[currentMove][toY][toX];
6704
6705     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6706     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6707          if( pup != EmptySquare ) return;
6708          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6709            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6710                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6711            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6712            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6713            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6714            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6715          fromY = DROP_RANK;
6716     }
6717
6718     /* [HGM] always test for legality, to get promotion info */
6719     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6720                                          fromY, fromX, toY, toX, promoChar);
6721
6722     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6723
6724     /* [HGM] but possibly ignore an IllegalMove result */
6725     if (appData.testLegality) {
6726         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6727             DisplayMoveError(_("Illegal move"));
6728             return;
6729         }
6730     }
6731
6732     if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6733         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6734              ClearPremoveHighlights(); // was included
6735         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6736         return;
6737     }
6738
6739     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6740 }
6741
6742 /* Common tail of UserMoveEvent and DropMenuEvent */
6743 int
6744 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6745 {
6746     char *bookHit = 0;
6747
6748     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6749         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6750         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6751         if(WhiteOnMove(currentMove)) {
6752             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6753         } else {
6754             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6755         }
6756     }
6757
6758     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6759        move type in caller when we know the move is a legal promotion */
6760     if(moveType == NormalMove && promoChar)
6761         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6762
6763     /* [HGM] <popupFix> The following if has been moved here from
6764        UserMoveEvent(). Because it seemed to belong here (why not allow
6765        piece drops in training games?), and because it can only be
6766        performed after it is known to what we promote. */
6767     if (gameMode == Training) {
6768       /* compare the move played on the board to the next move in the
6769        * game. If they match, display the move and the opponent's response.
6770        * If they don't match, display an error message.
6771        */
6772       int saveAnimate;
6773       Board testBoard;
6774       CopyBoard(testBoard, boards[currentMove]);
6775       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6776
6777       if (CompareBoards(testBoard, boards[currentMove+1])) {
6778         ForwardInner(currentMove+1);
6779
6780         /* Autoplay the opponent's response.
6781          * if appData.animate was TRUE when Training mode was entered,
6782          * the response will be animated.
6783          */
6784         saveAnimate = appData.animate;
6785         appData.animate = animateTraining;
6786         ForwardInner(currentMove+1);
6787         appData.animate = saveAnimate;
6788
6789         /* check for the end of the game */
6790         if (currentMove >= forwardMostMove) {
6791           gameMode = PlayFromGameFile;
6792           ModeHighlight();
6793           SetTrainingModeOff();
6794           DisplayInformation(_("End of game"));
6795         }
6796       } else {
6797         DisplayError(_("Incorrect move"), 0);
6798       }
6799       return 1;
6800     }
6801
6802   /* Ok, now we know that the move is good, so we can kill
6803      the previous line in Analysis Mode */
6804   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6805                                 && currentMove < forwardMostMove) {
6806     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6807     else forwardMostMove = currentMove;
6808   }
6809
6810   ClearMap();
6811
6812   /* If we need the chess program but it's dead, restart it */
6813   ResurrectChessProgram();
6814
6815   /* A user move restarts a paused game*/
6816   if (pausing)
6817     PauseEvent();
6818
6819   thinkOutput[0] = NULLCHAR;
6820
6821   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6822
6823   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6824     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6825     return 1;
6826   }
6827
6828   if (gameMode == BeginningOfGame) {
6829     if (appData.noChessProgram) {
6830       gameMode = EditGame;
6831       SetGameInfo();
6832     } else {
6833       char buf[MSG_SIZ];
6834       gameMode = MachinePlaysBlack;
6835       StartClocks();
6836       SetGameInfo();
6837       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6838       DisplayTitle(buf);
6839       if (first.sendName) {
6840         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6841         SendToProgram(buf, &first);
6842       }
6843       StartClocks();
6844     }
6845     ModeHighlight();
6846   }
6847
6848   /* Relay move to ICS or chess engine */
6849   if (appData.icsActive) {
6850     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6851         gameMode == IcsExamining) {
6852       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6853         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6854         SendToICS("draw ");
6855         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6856       }
6857       // also send plain move, in case ICS does not understand atomic claims
6858       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6859       ics_user_moved = 1;
6860     }
6861   } else {
6862     if (first.sendTime && (gameMode == BeginningOfGame ||
6863                            gameMode == MachinePlaysWhite ||
6864                            gameMode == MachinePlaysBlack)) {
6865       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6866     }
6867     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6868          // [HGM] book: if program might be playing, let it use book
6869         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6870         first.maybeThinking = TRUE;
6871     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6872         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6873         SendBoard(&first, currentMove+1);
6874     } else SendMoveToProgram(forwardMostMove-1, &first);
6875     if (currentMove == cmailOldMove + 1) {
6876       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6877     }
6878   }
6879
6880   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6881
6882   switch (gameMode) {
6883   case EditGame:
6884     if(appData.testLegality)
6885     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6886     case MT_NONE:
6887     case MT_CHECK:
6888       break;
6889     case MT_CHECKMATE:
6890     case MT_STAINMATE:
6891       if (WhiteOnMove(currentMove)) {
6892         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6893       } else {
6894         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6895       }
6896       break;
6897     case MT_STALEMATE:
6898       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6899       break;
6900     }
6901     break;
6902
6903   case MachinePlaysBlack:
6904   case MachinePlaysWhite:
6905     /* disable certain menu options while machine is thinking */
6906     SetMachineThinkingEnables();
6907     break;
6908
6909   default:
6910     break;
6911   }
6912
6913   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6914   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6915
6916   if(bookHit) { // [HGM] book: simulate book reply
6917         static char bookMove[MSG_SIZ]; // a bit generous?
6918
6919         programStats.nodes = programStats.depth = programStats.time =
6920         programStats.score = programStats.got_only_move = 0;
6921         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6922
6923         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6924         strcat(bookMove, bookHit);
6925         HandleMachineMove(bookMove, &first);
6926   }
6927   return 1;
6928 }
6929
6930 void
6931 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6932 {
6933     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6934     Markers *m = (Markers *) closure;
6935     if(rf == fromY && ff == fromX)
6936         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6937                          || kind == WhiteCapturesEnPassant
6938                          || kind == BlackCapturesEnPassant);
6939     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6940 }
6941
6942 void
6943 MarkTargetSquares (int clear)
6944 {
6945   int x, y;
6946   if(clear) // no reason to ever suppress clearing
6947     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6948   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6949      !appData.testLegality || gameMode == EditPosition) return;
6950   if(!clear) {
6951     int capt = 0;
6952     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6953     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6954       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6955       if(capt)
6956       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6957     }
6958   }
6959   DrawPosition(FALSE, NULL);
6960 }
6961
6962 int
6963 Explode (Board board, int fromX, int fromY, int toX, int toY)
6964 {
6965     if(gameInfo.variant == VariantAtomic &&
6966        (board[toY][toX] != EmptySquare ||                     // capture?
6967         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6968                          board[fromY][fromX] == BlackPawn   )
6969       )) {
6970         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6971         return TRUE;
6972     }
6973     return FALSE;
6974 }
6975
6976 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6977
6978 int
6979 CanPromote (ChessSquare piece, int y)
6980 {
6981         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6982         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6983         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6984            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6985            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6986                                                   gameInfo.variant == VariantMakruk) return FALSE;
6987         return (piece == BlackPawn && y == 1 ||
6988                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6989                 piece == BlackLance && y == 1 ||
6990                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6991 }
6992
6993 void
6994 LeftClick (ClickType clickType, int xPix, int yPix)
6995 {
6996     int x, y;
6997     Boolean saveAnimate;
6998     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
6999     char promoChoice = NULLCHAR;
7000     ChessSquare piece;
7001     static TimeMark lastClickTime, prevClickTime;
7002
7003     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7004
7005     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7006
7007     if (clickType == Press) ErrorPopDown();
7008
7009     x = EventToSquare(xPix, BOARD_WIDTH);
7010     y = EventToSquare(yPix, BOARD_HEIGHT);
7011     if (!flipView && y >= 0) {
7012         y = BOARD_HEIGHT - 1 - y;
7013     }
7014     if (flipView && x >= 0) {
7015         x = BOARD_WIDTH - 1 - x;
7016     }
7017
7018     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7019         defaultPromoChoice = promoSweep;
7020         promoSweep = EmptySquare;   // terminate sweep
7021         promoDefaultAltered = TRUE;
7022         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7023     }
7024
7025     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7026         if(clickType == Release) return; // ignore upclick of click-click destination
7027         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7028         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7029         if(gameInfo.holdingsWidth &&
7030                 (WhiteOnMove(currentMove)
7031                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7032                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7033             // click in right holdings, for determining promotion piece
7034             ChessSquare p = boards[currentMove][y][x];
7035             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7036             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7037             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7038                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7039                 fromX = fromY = -1;
7040                 return;
7041             }
7042         }
7043         DrawPosition(FALSE, boards[currentMove]);
7044         return;
7045     }
7046
7047     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7048     if(clickType == Press
7049             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7050               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7051               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7052         return;
7053
7054     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7055         // could be static click on premove from-square: abort premove
7056         gotPremove = 0;
7057         ClearPremoveHighlights();
7058     }
7059
7060     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7061         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7062
7063     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7064         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7065                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7066         defaultPromoChoice = DefaultPromoChoice(side);
7067     }
7068
7069     autoQueen = appData.alwaysPromoteToQueen;
7070
7071     if (fromX == -1) {
7072       int originalY = y;
7073       gatingPiece = EmptySquare;
7074       if (clickType != Press) {
7075         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7076             DragPieceEnd(xPix, yPix); dragging = 0;
7077             DrawPosition(FALSE, NULL);
7078         }
7079         return;
7080       }
7081       doubleClick = FALSE;
7082       fromX = x; fromY = y; toX = toY = -1;
7083       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7084          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7085          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7086             /* First square */
7087             if (OKToStartUserMove(fromX, fromY)) {
7088                 second = 0;
7089                 MarkTargetSquares(0);
7090                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7091                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7092                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7093                     promoSweep = defaultPromoChoice;
7094                     selectFlag = 0; lastX = xPix; lastY = yPix;
7095                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7096                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7097                 }
7098                 if (appData.highlightDragging) {
7099                     SetHighlights(fromX, fromY, -1, -1);
7100                 } else {
7101                     ClearHighlights();
7102                 }
7103             } else fromX = fromY = -1;
7104             return;
7105         }
7106     }
7107
7108     /* fromX != -1 */
7109     if (clickType == Press && gameMode != EditPosition) {
7110         ChessSquare fromP;
7111         ChessSquare toP;
7112         int frc;
7113
7114         // ignore off-board to clicks
7115         if(y < 0 || x < 0) return;
7116
7117         /* Check if clicking again on the same color piece */
7118         fromP = boards[currentMove][fromY][fromX];
7119         toP = boards[currentMove][y][x];
7120         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7121         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7122              WhitePawn <= toP && toP <= WhiteKing &&
7123              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7124              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7125             (BlackPawn <= fromP && fromP <= BlackKing &&
7126              BlackPawn <= toP && toP <= BlackKing &&
7127              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7128              !(fromP == BlackKing && toP == BlackRook && frc))) {
7129             /* Clicked again on same color piece -- changed his mind */
7130             second = (x == fromX && y == fromY);
7131             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7132                 second = FALSE; // first double-click rather than scond click
7133                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7134             }
7135             promoDefaultAltered = FALSE;
7136             MarkTargetSquares(1);
7137            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7138             if (appData.highlightDragging) {
7139                 SetHighlights(x, y, -1, -1);
7140             } else {
7141                 ClearHighlights();
7142             }
7143             if (OKToStartUserMove(x, y)) {
7144                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7145                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7146                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7147                  gatingPiece = boards[currentMove][fromY][fromX];
7148                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7149                 fromX = x;
7150                 fromY = y; dragging = 1;
7151                 MarkTargetSquares(0);
7152                 DragPieceBegin(xPix, yPix, FALSE);
7153                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7154                     promoSweep = defaultPromoChoice;
7155                     selectFlag = 0; lastX = xPix; lastY = yPix;
7156                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7157                 }
7158             }
7159            }
7160            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7161            second = FALSE; 
7162         }
7163         // ignore clicks on holdings
7164         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7165     }
7166
7167     if (clickType == Release && x == fromX && y == fromY) {
7168         DragPieceEnd(xPix, yPix); dragging = 0;
7169         if(clearFlag) {
7170             // a deferred attempt to click-click move an empty square on top of a piece
7171             boards[currentMove][y][x] = EmptySquare;
7172             ClearHighlights();
7173             DrawPosition(FALSE, boards[currentMove]);
7174             fromX = fromY = -1; clearFlag = 0;
7175             return;
7176         }
7177         if (appData.animateDragging) {
7178             /* Undo animation damage if any */
7179             DrawPosition(FALSE, NULL);
7180         }
7181         if (second || sweepSelecting) {
7182             /* Second up/down in same square; just abort move */
7183             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7184             second = sweepSelecting = 0;
7185             fromX = fromY = -1;
7186             gatingPiece = EmptySquare;
7187             ClearHighlights();
7188             gotPremove = 0;
7189             ClearPremoveHighlights();
7190         } else {
7191             /* First upclick in same square; start click-click mode */
7192             SetHighlights(x, y, -1, -1);
7193         }
7194         return;
7195     }
7196
7197     clearFlag = 0;
7198
7199     /* we now have a different from- and (possibly off-board) to-square */
7200     /* Completed move */
7201     if(!sweepSelecting) {
7202         toX = x;
7203         toY = y;
7204     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7205
7206     saveAnimate = appData.animate;
7207     if (clickType == Press) {
7208         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7209             // must be Edit Position mode with empty-square selected
7210             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7211             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7212             return;
7213         }
7214         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7215           if(appData.sweepSelect) {
7216             ChessSquare piece = boards[currentMove][fromY][fromX];
7217             promoSweep = defaultPromoChoice;
7218             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7219             selectFlag = 0; lastX = xPix; lastY = yPix;
7220             Sweep(0); // Pawn that is going to promote: preview promotion piece
7221             sweepSelecting = 1;
7222             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7223             MarkTargetSquares(1);
7224           }
7225           return; // promo popup appears on up-click
7226         }
7227         /* Finish clickclick move */
7228         if (appData.animate || appData.highlightLastMove) {
7229             SetHighlights(fromX, fromY, toX, toY);
7230         } else {
7231             ClearHighlights();
7232         }
7233     } else {
7234         /* Finish drag move */
7235         if (appData.highlightLastMove) {
7236             SetHighlights(fromX, fromY, toX, toY);
7237         } else {
7238             ClearHighlights();
7239         }
7240         DragPieceEnd(xPix, yPix); dragging = 0;
7241         /* Don't animate move and drag both */
7242         appData.animate = FALSE;
7243     }
7244     MarkTargetSquares(1);
7245
7246     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7247     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7248         ChessSquare piece = boards[currentMove][fromY][fromX];
7249         if(gameMode == EditPosition && piece != EmptySquare &&
7250            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7251             int n;
7252
7253             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7254                 n = PieceToNumber(piece - (int)BlackPawn);
7255                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7256                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7257                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7258             } else
7259             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7260                 n = PieceToNumber(piece);
7261                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7262                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7263                 boards[currentMove][n][BOARD_WIDTH-2]++;
7264             }
7265             boards[currentMove][fromY][fromX] = EmptySquare;
7266         }
7267         ClearHighlights();
7268         fromX = fromY = -1;
7269         DrawPosition(TRUE, boards[currentMove]);
7270         return;
7271     }
7272
7273     // off-board moves should not be highlighted
7274     if(x < 0 || y < 0) ClearHighlights();
7275
7276     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7277
7278     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7279         SetHighlights(fromX, fromY, toX, toY);
7280         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7281             // [HGM] super: promotion to captured piece selected from holdings
7282             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7283             promotionChoice = TRUE;
7284             // kludge follows to temporarily execute move on display, without promoting yet
7285             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7286             boards[currentMove][toY][toX] = p;
7287             DrawPosition(FALSE, boards[currentMove]);
7288             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7289             boards[currentMove][toY][toX] = q;
7290             DisplayMessage("Click in holdings to choose piece", "");
7291             return;
7292         }
7293         PromotionPopUp();
7294     } else {
7295         int oldMove = currentMove;
7296         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7297         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7298         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7299         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7300            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7301             DrawPosition(TRUE, boards[currentMove]);
7302         fromX = fromY = -1;
7303     }
7304     appData.animate = saveAnimate;
7305     if (appData.animate || appData.animateDragging) {
7306         /* Undo animation damage if needed */
7307         DrawPosition(FALSE, NULL);
7308     }
7309 }
7310
7311 int
7312 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7313 {   // front-end-free part taken out of PieceMenuPopup
7314     int whichMenu; int xSqr, ySqr;
7315
7316     if(seekGraphUp) { // [HGM] seekgraph
7317         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7318         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7319         return -2;
7320     }
7321
7322     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7323          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7324         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7325         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7326         if(action == Press)   {
7327             originalFlip = flipView;
7328             flipView = !flipView; // temporarily flip board to see game from partners perspective
7329             DrawPosition(TRUE, partnerBoard);
7330             DisplayMessage(partnerStatus, "");
7331             partnerUp = TRUE;
7332         } else if(action == Release) {
7333             flipView = originalFlip;
7334             DrawPosition(TRUE, boards[currentMove]);
7335             partnerUp = FALSE;
7336         }
7337         return -2;
7338     }
7339
7340     xSqr = EventToSquare(x, BOARD_WIDTH);
7341     ySqr = EventToSquare(y, BOARD_HEIGHT);
7342     if (action == Release) {
7343         if(pieceSweep != EmptySquare) {
7344             EditPositionMenuEvent(pieceSweep, toX, toY);
7345             pieceSweep = EmptySquare;
7346         } else UnLoadPV(); // [HGM] pv
7347     }
7348     if (action != Press) return -2; // return code to be ignored
7349     switch (gameMode) {
7350       case IcsExamining:
7351         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7352       case EditPosition:
7353         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7354         if (xSqr < 0 || ySqr < 0) return -1;
7355         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7356         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7357         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7358         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7359         NextPiece(0);
7360         return 2; // grab
7361       case IcsObserving:
7362         if(!appData.icsEngineAnalyze) return -1;
7363       case IcsPlayingWhite:
7364       case IcsPlayingBlack:
7365         if(!appData.zippyPlay) goto noZip;
7366       case AnalyzeMode:
7367       case AnalyzeFile:
7368       case MachinePlaysWhite:
7369       case MachinePlaysBlack:
7370       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7371         if (!appData.dropMenu) {
7372           LoadPV(x, y);
7373           return 2; // flag front-end to grab mouse events
7374         }
7375         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7376            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7377       case EditGame:
7378       noZip:
7379         if (xSqr < 0 || ySqr < 0) return -1;
7380         if (!appData.dropMenu || appData.testLegality &&
7381             gameInfo.variant != VariantBughouse &&
7382             gameInfo.variant != VariantCrazyhouse) return -1;
7383         whichMenu = 1; // drop menu
7384         break;
7385       default:
7386         return -1;
7387     }
7388
7389     if (((*fromX = xSqr) < 0) ||
7390         ((*fromY = ySqr) < 0)) {
7391         *fromX = *fromY = -1;
7392         return -1;
7393     }
7394     if (flipView)
7395       *fromX = BOARD_WIDTH - 1 - *fromX;
7396     else
7397       *fromY = BOARD_HEIGHT - 1 - *fromY;
7398
7399     return whichMenu;
7400 }
7401
7402 void
7403 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7404 {
7405 //    char * hint = lastHint;
7406     FrontEndProgramStats stats;
7407
7408     stats.which = cps == &first ? 0 : 1;
7409     stats.depth = cpstats->depth;
7410     stats.nodes = cpstats->nodes;
7411     stats.score = cpstats->score;
7412     stats.time = cpstats->time;
7413     stats.pv = cpstats->movelist;
7414     stats.hint = lastHint;
7415     stats.an_move_index = 0;
7416     stats.an_move_count = 0;
7417
7418     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7419         stats.hint = cpstats->move_name;
7420         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7421         stats.an_move_count = cpstats->nr_moves;
7422     }
7423
7424     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
7425
7426     SetProgramStats( &stats );
7427 }
7428
7429 void
7430 ClearEngineOutputPane (int which)
7431 {
7432     static FrontEndProgramStats dummyStats;
7433     dummyStats.which = which;
7434     dummyStats.pv = "#";
7435     SetProgramStats( &dummyStats );
7436 }
7437
7438 #define MAXPLAYERS 500
7439
7440 char *
7441 TourneyStandings (int display)
7442 {
7443     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7444     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7445     char result, *p, *names[MAXPLAYERS];
7446
7447     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7448         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7449     names[0] = p = strdup(appData.participants);
7450     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7451
7452     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7453
7454     while(result = appData.results[nr]) {
7455         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7456         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7457         wScore = bScore = 0;
7458         switch(result) {
7459           case '+': wScore = 2; break;
7460           case '-': bScore = 2; break;
7461           case '=': wScore = bScore = 1; break;
7462           case ' ':
7463           case '*': return strdup("busy"); // tourney not finished
7464         }
7465         score[w] += wScore;
7466         score[b] += bScore;
7467         games[w]++;
7468         games[b]++;
7469         nr++;
7470     }
7471     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7472     for(w=0; w<nPlayers; w++) {
7473         bScore = -1;
7474         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7475         ranking[w] = b; points[w] = bScore; score[b] = -2;
7476     }
7477     p = malloc(nPlayers*34+1);
7478     for(w=0; w<nPlayers && w<display; w++)
7479         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7480     free(names[0]);
7481     return p;
7482 }
7483
7484 void
7485 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7486 {       // count all piece types
7487         int p, f, r;
7488         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7489         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7490         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7491                 p = board[r][f];
7492                 pCnt[p]++;
7493                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7494                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7495                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7496                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7497                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7498                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7499         }
7500 }
7501
7502 int
7503 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7504 {
7505         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7506         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7507
7508         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7509         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7510         if(myPawns == 2 && nMine == 3) // KPP
7511             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7512         if(myPawns == 1 && nMine == 2) // KP
7513             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7514         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7515             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7516         if(myPawns) return FALSE;
7517         if(pCnt[WhiteRook+side])
7518             return pCnt[BlackRook-side] ||
7519                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7520                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7521                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7522         if(pCnt[WhiteCannon+side]) {
7523             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7524             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7525         }
7526         if(pCnt[WhiteKnight+side])
7527             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7528         return FALSE;
7529 }
7530
7531 int
7532 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7533 {
7534         VariantClass v = gameInfo.variant;
7535
7536         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7537         if(v == VariantShatranj) return TRUE; // always winnable through baring
7538         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7539         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7540
7541         if(v == VariantXiangqi) {
7542                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7543
7544                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7545                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7546                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7547                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7548                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7549                 if(stale) // we have at least one last-rank P plus perhaps C
7550                     return majors // KPKX
7551                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7552                 else // KCA*E*
7553                     return pCnt[WhiteFerz+side] // KCAK
7554                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7555                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7556                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7557
7558         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7559                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7560
7561                 if(nMine == 1) return FALSE; // bare King
7562                 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
7563                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7564                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7565                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7566                 if(pCnt[WhiteKnight+side])
7567                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7568                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7569                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7570                 if(nBishops)
7571                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7572                 if(pCnt[WhiteAlfil+side])
7573                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7574                 if(pCnt[WhiteWazir+side])
7575                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7576         }
7577
7578         return TRUE;
7579 }
7580
7581 int
7582 CompareWithRights (Board b1, Board b2)
7583 {
7584     int rights = 0;
7585     if(!CompareBoards(b1, b2)) return FALSE;
7586     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7587     /* compare castling rights */
7588     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7589            rights++; /* King lost rights, while rook still had them */
7590     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7591         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7592            rights++; /* but at least one rook lost them */
7593     }
7594     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7595            rights++;
7596     if( b1[CASTLING][5] != NoRights ) {
7597         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7598            rights++;
7599     }
7600     return rights == 0;
7601 }
7602
7603 int
7604 Adjudicate (ChessProgramState *cps)
7605 {       // [HGM] some adjudications useful with buggy engines
7606         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7607         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7608         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7609         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7610         int k, count = 0; static int bare = 1;
7611         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7612         Boolean canAdjudicate = !appData.icsActive;
7613
7614         // most tests only when we understand the game, i.e. legality-checking on
7615             if( appData.testLegality )
7616             {   /* [HGM] Some more adjudications for obstinate engines */
7617                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7618                 static int moveCount = 6;
7619                 ChessMove result;
7620                 char *reason = NULL;
7621
7622                 /* Count what is on board. */
7623                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7624
7625                 /* Some material-based adjudications that have to be made before stalemate test */
7626                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7627                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7628                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7629                      if(canAdjudicate && appData.checkMates) {
7630                          if(engineOpponent)
7631                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7632                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7633                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7634                          return 1;
7635                      }
7636                 }
7637
7638                 /* Bare King in Shatranj (loses) or Losers (wins) */
7639                 if( nrW == 1 || nrB == 1) {
7640                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7641                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7642                      if(canAdjudicate && appData.checkMates) {
7643                          if(engineOpponent)
7644                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7645                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7646                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7647                          return 1;
7648                      }
7649                   } else
7650                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7651                   {    /* bare King */
7652                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7653                         if(canAdjudicate && appData.checkMates) {
7654                             /* but only adjudicate if adjudication enabled */
7655                             if(engineOpponent)
7656                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7657                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7658                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7659                             return 1;
7660                         }
7661                   }
7662                 } else bare = 1;
7663
7664
7665             // don't wait for engine to announce game end if we can judge ourselves
7666             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7667               case MT_CHECK:
7668                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7669                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7670                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7671                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7672                             checkCnt++;
7673                         if(checkCnt >= 2) {
7674                             reason = "Xboard adjudication: 3rd check";
7675                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7676                             break;
7677                         }
7678                     }
7679                 }
7680               case MT_NONE:
7681               default:
7682                 break;
7683               case MT_STALEMATE:
7684               case MT_STAINMATE:
7685                 reason = "Xboard adjudication: Stalemate";
7686                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7687                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7688                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7689                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7690                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7691                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7692                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7693                                                                         EP_CHECKMATE : EP_WINS);
7694                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7695                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7696                 }
7697                 break;
7698               case MT_CHECKMATE:
7699                 reason = "Xboard adjudication: Checkmate";
7700                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7701                 break;
7702             }
7703
7704                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7705                     case EP_STALEMATE:
7706                         result = GameIsDrawn; break;
7707                     case EP_CHECKMATE:
7708                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7709                     case EP_WINS:
7710                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7711                     default:
7712                         result = EndOfFile;
7713                 }
7714                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7715                     if(engineOpponent)
7716                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7717                     GameEnds( result, reason, GE_XBOARD );
7718                     return 1;
7719                 }
7720
7721                 /* Next absolutely insufficient mating material. */
7722                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7723                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7724                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7725
7726                      /* always flag draws, for judging claims */
7727                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7728
7729                      if(canAdjudicate && appData.materialDraws) {
7730                          /* but only adjudicate them if adjudication enabled */
7731                          if(engineOpponent) {
7732                            SendToProgram("force\n", engineOpponent); // suppress reply
7733                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7734                          }
7735                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7736                          return 1;
7737                      }
7738                 }
7739
7740                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7741                 if(gameInfo.variant == VariantXiangqi ?
7742                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7743                  : nrW + nrB == 4 &&
7744                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7745                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7746                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7747                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7748                    ) ) {
7749                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7750                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7751                           if(engineOpponent) {
7752                             SendToProgram("force\n", engineOpponent); // suppress reply
7753                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7754                           }
7755                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7756                           return 1;
7757                      }
7758                 } else moveCount = 6;
7759             }
7760
7761         // Repetition draws and 50-move rule can be applied independently of legality testing
7762
7763                 /* Check for rep-draws */
7764                 count = 0;
7765                 for(k = forwardMostMove-2;
7766                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7767                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7768                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7769                     k-=2)
7770                 {   int rights=0;
7771                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7772                         /* compare castling rights */
7773                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7774                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7775                                 rights++; /* King lost rights, while rook still had them */
7776                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7777                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7778                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7779                                    rights++; /* but at least one rook lost them */
7780                         }
7781                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7782                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7783                                 rights++;
7784                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7785                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7786                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7787                                    rights++;
7788                         }
7789                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7790                             && appData.drawRepeats > 1) {
7791                              /* adjudicate after user-specified nr of repeats */
7792                              int result = GameIsDrawn;
7793                              char *details = "XBoard adjudication: repetition draw";
7794                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7795                                 // [HGM] xiangqi: check for forbidden perpetuals
7796                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7797                                 for(m=forwardMostMove; m>k; m-=2) {
7798                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7799                                         ourPerpetual = 0; // the current mover did not always check
7800                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7801                                         hisPerpetual = 0; // the opponent did not always check
7802                                 }
7803                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7804                                                                         ourPerpetual, hisPerpetual);
7805                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7806                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7807                                     details = "Xboard adjudication: perpetual checking";
7808                                 } else
7809                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7810                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7811                                 } else
7812                                 // Now check for perpetual chases
7813                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7814                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7815                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7816                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7817                                         static char resdet[MSG_SIZ];
7818                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7819                                         details = resdet;
7820                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7821                                     } else
7822                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7823                                         break; // Abort repetition-checking loop.
7824                                 }
7825                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7826                              }
7827                              if(engineOpponent) {
7828                                SendToProgram("force\n", engineOpponent); // suppress reply
7829                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7830                              }
7831                              GameEnds( result, details, GE_XBOARD );
7832                              return 1;
7833                         }
7834                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7835                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7836                     }
7837                 }
7838
7839                 /* Now we test for 50-move draws. Determine ply count */
7840                 count = forwardMostMove;
7841                 /* look for last irreversble move */
7842                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7843                     count--;
7844                 /* if we hit starting position, add initial plies */
7845                 if( count == backwardMostMove )
7846                     count -= initialRulePlies;
7847                 count = forwardMostMove - count;
7848                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7849                         // adjust reversible move counter for checks in Xiangqi
7850                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7851                         if(i < backwardMostMove) i = backwardMostMove;
7852                         while(i <= forwardMostMove) {
7853                                 lastCheck = inCheck; // check evasion does not count
7854                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7855                                 if(inCheck || lastCheck) count--; // check does not count
7856                                 i++;
7857                         }
7858                 }
7859                 if( count >= 100)
7860                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7861                          /* this is used to judge if draw claims are legal */
7862                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7863                          if(engineOpponent) {
7864                            SendToProgram("force\n", engineOpponent); // suppress reply
7865                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7866                          }
7867                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7868                          return 1;
7869                 }
7870
7871                 /* if draw offer is pending, treat it as a draw claim
7872                  * when draw condition present, to allow engines a way to
7873                  * claim draws before making their move to avoid a race
7874                  * condition occurring after their move
7875                  */
7876                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7877                          char *p = NULL;
7878                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7879                              p = "Draw claim: 50-move rule";
7880                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7881                              p = "Draw claim: 3-fold repetition";
7882                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7883                              p = "Draw claim: insufficient mating material";
7884                          if( p != NULL && canAdjudicate) {
7885                              if(engineOpponent) {
7886                                SendToProgram("force\n", engineOpponent); // suppress reply
7887                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7888                              }
7889                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7890                              return 1;
7891                          }
7892                 }
7893
7894                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7895                     if(engineOpponent) {
7896                       SendToProgram("force\n", engineOpponent); // suppress reply
7897                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7898                     }
7899                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7900                     return 1;
7901                 }
7902         return 0;
7903 }
7904
7905 char *
7906 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7907 {   // [HGM] book: this routine intercepts moves to simulate book replies
7908     char *bookHit = NULL;
7909
7910     //first determine if the incoming move brings opponent into his book
7911     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7912         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7913     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7914     if(bookHit != NULL && !cps->bookSuspend) {
7915         // make sure opponent is not going to reply after receiving move to book position
7916         SendToProgram("force\n", cps);
7917         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7918     }
7919     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7920     // now arrange restart after book miss
7921     if(bookHit) {
7922         // after a book hit we never send 'go', and the code after the call to this routine
7923         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7924         char buf[MSG_SIZ], *move = bookHit;
7925         if(cps->useSAN) {
7926             int fromX, fromY, toX, toY;
7927             char promoChar;
7928             ChessMove moveType;
7929             move = buf + 30;
7930             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7931                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7932                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7933                                     PosFlags(forwardMostMove),
7934                                     fromY, fromX, toY, toX, promoChar, move);
7935             } else {
7936                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7937                 bookHit = NULL;
7938             }
7939         }
7940         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7941         SendToProgram(buf, cps);
7942         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7943     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7944         SendToProgram("go\n", cps);
7945         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7946     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7947         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7948             SendToProgram("go\n", cps);
7949         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7950     }
7951     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7952 }
7953
7954 int
7955 LoadError (char *errmess, ChessProgramState *cps)
7956 {   // unloads engine and switches back to -ncp mode if it was first
7957     if(cps->initDone) return FALSE;
7958     cps->isr = NULL; // this should suppress further error popups from breaking pipes
7959     DestroyChildProcess(cps->pr, 9 ); // just to be sure
7960     cps->pr = NoProc; 
7961     if(cps == &first) {
7962         appData.noChessProgram = TRUE;
7963         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
7964         gameMode = BeginningOfGame; ModeHighlight();
7965         SetNCPMode();
7966     }
7967     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
7968     DisplayMessage("", ""); // erase waiting message
7969     if(errmess) DisplayError(errmess, 0); // announce reason, if given
7970     return TRUE;
7971 }
7972
7973 char *savedMessage;
7974 ChessProgramState *savedState;
7975 void
7976 DeferredBookMove (void)
7977 {
7978         if(savedState->lastPing != savedState->lastPong)
7979                     ScheduleDelayedEvent(DeferredBookMove, 10);
7980         else
7981         HandleMachineMove(savedMessage, savedState);
7982 }
7983
7984 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7985
7986 void
7987 HandleMachineMove (char *message, ChessProgramState *cps)
7988 {
7989     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7990     char realname[MSG_SIZ];
7991     int fromX, fromY, toX, toY;
7992     ChessMove moveType;
7993     char promoChar;
7994     char *p, *pv=buf1;
7995     int machineWhite, oldError;
7996     char *bookHit;
7997
7998     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7999         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8000         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8001             DisplayError(_("Invalid pairing from pairing engine"), 0);
8002             return;
8003         }
8004         pairingReceived = 1;
8005         NextMatchGame();
8006         return; // Skim the pairing messages here.
8007     }
8008
8009     oldError = cps->userError; cps->userError = 0;
8010
8011 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8012     /*
8013      * Kludge to ignore BEL characters
8014      */
8015     while (*message == '\007') message++;
8016
8017     /*
8018      * [HGM] engine debug message: ignore lines starting with '#' character
8019      */
8020     if(cps->debug && *message == '#') return;
8021
8022     /*
8023      * Look for book output
8024      */
8025     if (cps == &first && bookRequested) {
8026         if (message[0] == '\t' || message[0] == ' ') {
8027             /* Part of the book output is here; append it */
8028             strcat(bookOutput, message);
8029             strcat(bookOutput, "  \n");
8030             return;
8031         } else if (bookOutput[0] != NULLCHAR) {
8032             /* All of book output has arrived; display it */
8033             char *p = bookOutput;
8034             while (*p != NULLCHAR) {
8035                 if (*p == '\t') *p = ' ';
8036                 p++;
8037             }
8038             DisplayInformation(bookOutput);
8039             bookRequested = FALSE;
8040             /* Fall through to parse the current output */
8041         }
8042     }
8043
8044     /*
8045      * Look for machine move.
8046      */
8047     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8048         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8049     {
8050         /* This method is only useful on engines that support ping */
8051         if (cps->lastPing != cps->lastPong) {
8052           if (gameMode == BeginningOfGame) {
8053             /* Extra move from before last new; ignore */
8054             if (appData.debugMode) {
8055                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8056             }
8057           } else {
8058             if (appData.debugMode) {
8059                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8060                         cps->which, gameMode);
8061             }
8062
8063             SendToProgram("undo\n", cps);
8064           }
8065           return;
8066         }
8067
8068         switch (gameMode) {
8069           case BeginningOfGame:
8070             /* Extra move from before last reset; ignore */
8071             if (appData.debugMode) {
8072                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8073             }
8074             return;
8075
8076           case EndOfGame:
8077           case IcsIdle:
8078           default:
8079             /* Extra move after we tried to stop.  The mode test is
8080                not a reliable way of detecting this problem, but it's
8081                the best we can do on engines that don't support ping.
8082             */
8083             if (appData.debugMode) {
8084                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8085                         cps->which, gameMode);
8086             }
8087             SendToProgram("undo\n", cps);
8088             return;
8089
8090           case MachinePlaysWhite:
8091           case IcsPlayingWhite:
8092             machineWhite = TRUE;
8093             break;
8094
8095           case MachinePlaysBlack:
8096           case IcsPlayingBlack:
8097             machineWhite = FALSE;
8098             break;
8099
8100           case TwoMachinesPlay:
8101             machineWhite = (cps->twoMachinesColor[0] == 'w');
8102             break;
8103         }
8104         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8105             if (appData.debugMode) {
8106                 fprintf(debugFP,
8107                         "Ignoring move out of turn by %s, gameMode %d"
8108                         ", forwardMost %d\n",
8109                         cps->which, gameMode, forwardMostMove);
8110             }
8111             return;
8112         }
8113
8114         if(cps->alphaRank) AlphaRank(machineMove, 4);
8115         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8116                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8117             /* Machine move could not be parsed; ignore it. */
8118           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8119                     machineMove, _(cps->which));
8120             DisplayError(buf1, 0);
8121             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8122                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8123             if (gameMode == TwoMachinesPlay) {
8124               GameEnds(machineWhite ? BlackWins : WhiteWins,
8125                        buf1, GE_XBOARD);
8126             }
8127             return;
8128         }
8129
8130         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8131         /* So we have to redo legality test with true e.p. status here,  */
8132         /* to make sure an illegal e.p. capture does not slip through,   */
8133         /* to cause a forfeit on a justified illegal-move complaint      */
8134         /* of the opponent.                                              */
8135         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8136            ChessMove moveType;
8137            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8138                              fromY, fromX, toY, toX, promoChar);
8139             if(moveType == IllegalMove) {
8140               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8141                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8142                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8143                            buf1, GE_XBOARD);
8144                 return;
8145            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8146            /* [HGM] Kludge to handle engines that send FRC-style castling
8147               when they shouldn't (like TSCP-Gothic) */
8148            switch(moveType) {
8149              case WhiteASideCastleFR:
8150              case BlackASideCastleFR:
8151                toX+=2;
8152                currentMoveString[2]++;
8153                break;
8154              case WhiteHSideCastleFR:
8155              case BlackHSideCastleFR:
8156                toX--;
8157                currentMoveString[2]--;
8158                break;
8159              default: ; // nothing to do, but suppresses warning of pedantic compilers
8160            }
8161         }
8162         hintRequested = FALSE;
8163         lastHint[0] = NULLCHAR;
8164         bookRequested = FALSE;
8165         /* Program may be pondering now */
8166         cps->maybeThinking = TRUE;
8167         if (cps->sendTime == 2) cps->sendTime = 1;
8168         if (cps->offeredDraw) cps->offeredDraw--;
8169
8170         /* [AS] Save move info*/
8171         pvInfoList[ forwardMostMove ].score = programStats.score;
8172         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8173         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8174
8175         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8176
8177         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8178         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8179             int count = 0;
8180
8181             while( count < adjudicateLossPlies ) {
8182                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8183
8184                 if( count & 1 ) {
8185                     score = -score; /* Flip score for winning side */
8186                 }
8187
8188                 if( score > adjudicateLossThreshold ) {
8189                     break;
8190                 }
8191
8192                 count++;
8193             }
8194
8195             if( count >= adjudicateLossPlies ) {
8196                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8197
8198                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8199                     "Xboard adjudication",
8200                     GE_XBOARD );
8201
8202                 return;
8203             }
8204         }
8205
8206         if(Adjudicate(cps)) {
8207             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8208             return; // [HGM] adjudicate: for all automatic game ends
8209         }
8210
8211 #if ZIPPY
8212         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8213             first.initDone) {
8214           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8215                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8216                 SendToICS("draw ");
8217                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8218           }
8219           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8220           ics_user_moved = 1;
8221           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8222                 char buf[3*MSG_SIZ];
8223
8224                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8225                         programStats.score / 100.,
8226                         programStats.depth,
8227                         programStats.time / 100.,
8228                         (unsigned int)programStats.nodes,
8229                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8230                         programStats.movelist);
8231                 SendToICS(buf);
8232 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8233           }
8234         }
8235 #endif
8236
8237         /* [AS] Clear stats for next move */
8238         ClearProgramStats();
8239         thinkOutput[0] = NULLCHAR;
8240         hiddenThinkOutputState = 0;
8241
8242         bookHit = NULL;
8243         if (gameMode == TwoMachinesPlay) {
8244             /* [HGM] relaying draw offers moved to after reception of move */
8245             /* and interpreting offer as claim if it brings draw condition */
8246             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8247                 SendToProgram("draw\n", cps->other);
8248             }
8249             if (cps->other->sendTime) {
8250                 SendTimeRemaining(cps->other,
8251                                   cps->other->twoMachinesColor[0] == 'w');
8252             }
8253             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8254             if (firstMove && !bookHit) {
8255                 firstMove = FALSE;
8256                 if (cps->other->useColors) {
8257                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8258                 }
8259                 SendToProgram("go\n", cps->other);
8260             }
8261             cps->other->maybeThinking = TRUE;
8262         }
8263
8264         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8265
8266         if (!pausing && appData.ringBellAfterMoves) {
8267             RingBell();
8268         }
8269
8270         /*
8271          * Reenable menu items that were disabled while
8272          * machine was thinking
8273          */
8274         if (gameMode != TwoMachinesPlay)
8275             SetUserThinkingEnables();
8276
8277         // [HGM] book: after book hit opponent has received move and is now in force mode
8278         // force the book reply into it, and then fake that it outputted this move by jumping
8279         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8280         if(bookHit) {
8281                 static char bookMove[MSG_SIZ]; // a bit generous?
8282
8283                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8284                 strcat(bookMove, bookHit);
8285                 message = bookMove;
8286                 cps = cps->other;
8287                 programStats.nodes = programStats.depth = programStats.time =
8288                 programStats.score = programStats.got_only_move = 0;
8289                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8290
8291                 if(cps->lastPing != cps->lastPong) {
8292                     savedMessage = message; // args for deferred call
8293                     savedState = cps;
8294                     ScheduleDelayedEvent(DeferredBookMove, 10);
8295                     return;
8296                 }
8297                 goto FakeBookMove;
8298         }
8299
8300         return;
8301     }
8302
8303     /* Set special modes for chess engines.  Later something general
8304      *  could be added here; for now there is just one kludge feature,
8305      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8306      *  when "xboard" is given as an interactive command.
8307      */
8308     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8309         cps->useSigint = FALSE;
8310         cps->useSigterm = FALSE;
8311     }
8312     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8313       ParseFeatures(message+8, cps);
8314       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8315     }
8316
8317     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8318                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8319       int dummy, s=6; char buf[MSG_SIZ];
8320       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8321       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8322       if(startedFromSetupPosition) return;
8323       ParseFEN(boards[0], &dummy, message+s);
8324       DrawPosition(TRUE, boards[0]);
8325       startedFromSetupPosition = TRUE;
8326       return;
8327     }
8328     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8329      * want this, I was asked to put it in, and obliged.
8330      */
8331     if (!strncmp(message, "setboard ", 9)) {
8332         Board initial_position;
8333
8334         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8335
8336         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8337             DisplayError(_("Bad FEN received from engine"), 0);
8338             return ;
8339         } else {
8340            Reset(TRUE, FALSE);
8341            CopyBoard(boards[0], initial_position);
8342            initialRulePlies = FENrulePlies;
8343            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8344            else gameMode = MachinePlaysBlack;
8345            DrawPosition(FALSE, boards[currentMove]);
8346         }
8347         return;
8348     }
8349
8350     /*
8351      * Look for communication commands
8352      */
8353     if (!strncmp(message, "telluser ", 9)) {
8354         if(message[9] == '\\' && message[10] == '\\')
8355             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8356         PlayTellSound();
8357         DisplayNote(message + 9);
8358         return;
8359     }
8360     if (!strncmp(message, "tellusererror ", 14)) {
8361         cps->userError = 1;
8362         if(message[14] == '\\' && message[15] == '\\')
8363             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8364         PlayTellSound();
8365         DisplayError(message + 14, 0);
8366         return;
8367     }
8368     if (!strncmp(message, "tellopponent ", 13)) {
8369       if (appData.icsActive) {
8370         if (loggedOn) {
8371           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8372           SendToICS(buf1);
8373         }
8374       } else {
8375         DisplayNote(message + 13);
8376       }
8377       return;
8378     }
8379     if (!strncmp(message, "tellothers ", 11)) {
8380       if (appData.icsActive) {
8381         if (loggedOn) {
8382           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8383           SendToICS(buf1);
8384         }
8385       }
8386       return;
8387     }
8388     if (!strncmp(message, "tellall ", 8)) {
8389       if (appData.icsActive) {
8390         if (loggedOn) {
8391           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8392           SendToICS(buf1);
8393         }
8394       } else {
8395         DisplayNote(message + 8);
8396       }
8397       return;
8398     }
8399     if (strncmp(message, "warning", 7) == 0) {
8400         /* Undocumented feature, use tellusererror in new code */
8401         DisplayError(message, 0);
8402         return;
8403     }
8404     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8405         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8406         strcat(realname, " query");
8407         AskQuestion(realname, buf2, buf1, cps->pr);
8408         return;
8409     }
8410     /* Commands from the engine directly to ICS.  We don't allow these to be
8411      *  sent until we are logged on. Crafty kibitzes have been known to
8412      *  interfere with the login process.
8413      */
8414     if (loggedOn) {
8415         if (!strncmp(message, "tellics ", 8)) {
8416             SendToICS(message + 8);
8417             SendToICS("\n");
8418             return;
8419         }
8420         if (!strncmp(message, "tellicsnoalias ", 15)) {
8421             SendToICS(ics_prefix);
8422             SendToICS(message + 15);
8423             SendToICS("\n");
8424             return;
8425         }
8426         /* The following are for backward compatibility only */
8427         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8428             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8429             SendToICS(ics_prefix);
8430             SendToICS(message);
8431             SendToICS("\n");
8432             return;
8433         }
8434     }
8435     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8436         return;
8437     }
8438     /*
8439      * If the move is illegal, cancel it and redraw the board.
8440      * Also deal with other error cases.  Matching is rather loose
8441      * here to accommodate engines written before the spec.
8442      */
8443     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8444         strncmp(message, "Error", 5) == 0) {
8445         if (StrStr(message, "name") ||
8446             StrStr(message, "rating") || StrStr(message, "?") ||
8447             StrStr(message, "result") || StrStr(message, "board") ||
8448             StrStr(message, "bk") || StrStr(message, "computer") ||
8449             StrStr(message, "variant") || StrStr(message, "hint") ||
8450             StrStr(message, "random") || StrStr(message, "depth") ||
8451             StrStr(message, "accepted")) {
8452             return;
8453         }
8454         if (StrStr(message, "protover")) {
8455           /* Program is responding to input, so it's apparently done
8456              initializing, and this error message indicates it is
8457              protocol version 1.  So we don't need to wait any longer
8458              for it to initialize and send feature commands. */
8459           FeatureDone(cps, 1);
8460           cps->protocolVersion = 1;
8461           return;
8462         }
8463         cps->maybeThinking = FALSE;
8464
8465         if (StrStr(message, "draw")) {
8466             /* Program doesn't have "draw" command */
8467             cps->sendDrawOffers = 0;
8468             return;
8469         }
8470         if (cps->sendTime != 1 &&
8471             (StrStr(message, "time") || StrStr(message, "otim"))) {
8472           /* Program apparently doesn't have "time" or "otim" command */
8473           cps->sendTime = 0;
8474           return;
8475         }
8476         if (StrStr(message, "analyze")) {
8477             cps->analysisSupport = FALSE;
8478             cps->analyzing = FALSE;
8479 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8480             EditGameEvent(); // [HGM] try to preserve loaded game
8481             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8482             DisplayError(buf2, 0);
8483             return;
8484         }
8485         if (StrStr(message, "(no matching move)st")) {
8486           /* Special kludge for GNU Chess 4 only */
8487           cps->stKludge = TRUE;
8488           SendTimeControl(cps, movesPerSession, timeControl,
8489                           timeIncrement, appData.searchDepth,
8490                           searchTime);
8491           return;
8492         }
8493         if (StrStr(message, "(no matching move)sd")) {
8494           /* Special kludge for GNU Chess 4 only */
8495           cps->sdKludge = TRUE;
8496           SendTimeControl(cps, movesPerSession, timeControl,
8497                           timeIncrement, appData.searchDepth,
8498                           searchTime);
8499           return;
8500         }
8501         if (!StrStr(message, "llegal")) {
8502             return;
8503         }
8504         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8505             gameMode == IcsIdle) return;
8506         if (forwardMostMove <= backwardMostMove) return;
8507         if (pausing) PauseEvent();
8508       if(appData.forceIllegal) {
8509             // [HGM] illegal: machine refused move; force position after move into it
8510           SendToProgram("force\n", cps);
8511           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8512                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8513                 // when black is to move, while there might be nothing on a2 or black
8514                 // might already have the move. So send the board as if white has the move.
8515                 // But first we must change the stm of the engine, as it refused the last move
8516                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8517                 if(WhiteOnMove(forwardMostMove)) {
8518                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8519                     SendBoard(cps, forwardMostMove); // kludgeless board
8520                 } else {
8521                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8522                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8523                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8524                 }
8525           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8526             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8527                  gameMode == TwoMachinesPlay)
8528               SendToProgram("go\n", cps);
8529             return;
8530       } else
8531         if (gameMode == PlayFromGameFile) {
8532             /* Stop reading this game file */
8533             gameMode = EditGame;
8534             ModeHighlight();
8535         }
8536         /* [HGM] illegal-move claim should forfeit game when Xboard */
8537         /* only passes fully legal moves                            */
8538         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8539             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8540                                 "False illegal-move claim", GE_XBOARD );
8541             return; // do not take back move we tested as valid
8542         }
8543         currentMove = forwardMostMove-1;
8544         DisplayMove(currentMove-1); /* before DisplayMoveError */
8545         SwitchClocks(forwardMostMove-1); // [HGM] race
8546         DisplayBothClocks();
8547         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8548                 parseList[currentMove], _(cps->which));
8549         DisplayMoveError(buf1);
8550         DrawPosition(FALSE, boards[currentMove]);
8551
8552         SetUserThinkingEnables();
8553         return;
8554     }
8555     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8556         /* Program has a broken "time" command that
8557            outputs a string not ending in newline.
8558            Don't use it. */
8559         cps->sendTime = 0;
8560     }
8561
8562     /*
8563      * If chess program startup fails, exit with an error message.
8564      * Attempts to recover here are futile. [HGM] Well, we try anyway
8565      */
8566     if ((StrStr(message, "unknown host") != NULL)
8567         || (StrStr(message, "No remote directory") != NULL)
8568         || (StrStr(message, "not found") != NULL)
8569         || (StrStr(message, "No such file") != NULL)
8570         || (StrStr(message, "can't alloc") != NULL)
8571         || (StrStr(message, "Permission denied") != NULL)) {
8572
8573         cps->maybeThinking = FALSE;
8574         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8575                 _(cps->which), cps->program, cps->host, message);
8576         RemoveInputSource(cps->isr);
8577         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8578             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8579             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8580         }
8581         return;
8582     }
8583
8584     /*
8585      * Look for hint output
8586      */
8587     if (sscanf(message, "Hint: %s", buf1) == 1) {
8588         if (cps == &first && hintRequested) {
8589             hintRequested = FALSE;
8590             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8591                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8592                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8593                                     PosFlags(forwardMostMove),
8594                                     fromY, fromX, toY, toX, promoChar, buf1);
8595                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8596                 DisplayInformation(buf2);
8597             } else {
8598                 /* Hint move could not be parsed!? */
8599               snprintf(buf2, sizeof(buf2),
8600                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8601                         buf1, _(cps->which));
8602                 DisplayError(buf2, 0);
8603             }
8604         } else {
8605           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8606         }
8607         return;
8608     }
8609
8610     /*
8611      * Ignore other messages if game is not in progress
8612      */
8613     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8614         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8615
8616     /*
8617      * look for win, lose, draw, or draw offer
8618      */
8619     if (strncmp(message, "1-0", 3) == 0) {
8620         char *p, *q, *r = "";
8621         p = strchr(message, '{');
8622         if (p) {
8623             q = strchr(p, '}');
8624             if (q) {
8625                 *q = NULLCHAR;
8626                 r = p + 1;
8627             }
8628         }
8629         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8630         return;
8631     } else if (strncmp(message, "0-1", 3) == 0) {
8632         char *p, *q, *r = "";
8633         p = strchr(message, '{');
8634         if (p) {
8635             q = strchr(p, '}');
8636             if (q) {
8637                 *q = NULLCHAR;
8638                 r = p + 1;
8639             }
8640         }
8641         /* Kludge for Arasan 4.1 bug */
8642         if (strcmp(r, "Black resigns") == 0) {
8643             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8644             return;
8645         }
8646         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8647         return;
8648     } else if (strncmp(message, "1/2", 3) == 0) {
8649         char *p, *q, *r = "";
8650         p = strchr(message, '{');
8651         if (p) {
8652             q = strchr(p, '}');
8653             if (q) {
8654                 *q = NULLCHAR;
8655                 r = p + 1;
8656             }
8657         }
8658
8659         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8660         return;
8661
8662     } else if (strncmp(message, "White resign", 12) == 0) {
8663         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8664         return;
8665     } else if (strncmp(message, "Black resign", 12) == 0) {
8666         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8667         return;
8668     } else if (strncmp(message, "White matches", 13) == 0 ||
8669                strncmp(message, "Black matches", 13) == 0   ) {
8670         /* [HGM] ignore GNUShogi noises */
8671         return;
8672     } else if (strncmp(message, "White", 5) == 0 &&
8673                message[5] != '(' &&
8674                StrStr(message, "Black") == NULL) {
8675         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8676         return;
8677     } else if (strncmp(message, "Black", 5) == 0 &&
8678                message[5] != '(') {
8679         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8680         return;
8681     } else if (strcmp(message, "resign") == 0 ||
8682                strcmp(message, "computer resigns") == 0) {
8683         switch (gameMode) {
8684           case MachinePlaysBlack:
8685           case IcsPlayingBlack:
8686             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8687             break;
8688           case MachinePlaysWhite:
8689           case IcsPlayingWhite:
8690             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8691             break;
8692           case TwoMachinesPlay:
8693             if (cps->twoMachinesColor[0] == 'w')
8694               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8695             else
8696               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8697             break;
8698           default:
8699             /* can't happen */
8700             break;
8701         }
8702         return;
8703     } else if (strncmp(message, "opponent mates", 14) == 0) {
8704         switch (gameMode) {
8705           case MachinePlaysBlack:
8706           case IcsPlayingBlack:
8707             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8708             break;
8709           case MachinePlaysWhite:
8710           case IcsPlayingWhite:
8711             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8712             break;
8713           case TwoMachinesPlay:
8714             if (cps->twoMachinesColor[0] == 'w')
8715               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8716             else
8717               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8718             break;
8719           default:
8720             /* can't happen */
8721             break;
8722         }
8723         return;
8724     } else if (strncmp(message, "computer mates", 14) == 0) {
8725         switch (gameMode) {
8726           case MachinePlaysBlack:
8727           case IcsPlayingBlack:
8728             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8729             break;
8730           case MachinePlaysWhite:
8731           case IcsPlayingWhite:
8732             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8733             break;
8734           case TwoMachinesPlay:
8735             if (cps->twoMachinesColor[0] == 'w')
8736               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8737             else
8738               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8739             break;
8740           default:
8741             /* can't happen */
8742             break;
8743         }
8744         return;
8745     } else if (strncmp(message, "checkmate", 9) == 0) {
8746         if (WhiteOnMove(forwardMostMove)) {
8747             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8748         } else {
8749             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8750         }
8751         return;
8752     } else if (strstr(message, "Draw") != NULL ||
8753                strstr(message, "game is a draw") != NULL) {
8754         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8755         return;
8756     } else if (strstr(message, "offer") != NULL &&
8757                strstr(message, "draw") != NULL) {
8758 #if ZIPPY
8759         if (appData.zippyPlay && first.initDone) {
8760             /* Relay offer to ICS */
8761             SendToICS(ics_prefix);
8762             SendToICS("draw\n");
8763         }
8764 #endif
8765         cps->offeredDraw = 2; /* valid until this engine moves twice */
8766         if (gameMode == TwoMachinesPlay) {
8767             if (cps->other->offeredDraw) {
8768                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8769             /* [HGM] in two-machine mode we delay relaying draw offer      */
8770             /* until after we also have move, to see if it is really claim */
8771             }
8772         } else if (gameMode == MachinePlaysWhite ||
8773                    gameMode == MachinePlaysBlack) {
8774           if (userOfferedDraw) {
8775             DisplayInformation(_("Machine accepts your draw offer"));
8776             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8777           } else {
8778             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8779           }
8780         }
8781     }
8782
8783
8784     /*
8785      * Look for thinking output
8786      */
8787     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8788           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8789                                 ) {
8790         int plylev, mvleft, mvtot, curscore, time;
8791         char mvname[MOVE_LEN];
8792         u64 nodes; // [DM]
8793         char plyext;
8794         int ignore = FALSE;
8795         int prefixHint = FALSE;
8796         mvname[0] = NULLCHAR;
8797
8798         switch (gameMode) {
8799           case MachinePlaysBlack:
8800           case IcsPlayingBlack:
8801             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8802             break;
8803           case MachinePlaysWhite:
8804           case IcsPlayingWhite:
8805             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8806             break;
8807           case AnalyzeMode:
8808           case AnalyzeFile:
8809             break;
8810           case IcsObserving: /* [DM] icsEngineAnalyze */
8811             if (!appData.icsEngineAnalyze) ignore = TRUE;
8812             break;
8813           case TwoMachinesPlay:
8814             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8815                 ignore = TRUE;
8816             }
8817             break;
8818           default:
8819             ignore = TRUE;
8820             break;
8821         }
8822
8823         if (!ignore) {
8824             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8825             buf1[0] = NULLCHAR;
8826             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8827                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8828
8829                 if (plyext != ' ' && plyext != '\t') {
8830                     time *= 100;
8831                 }
8832
8833                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8834                 if( cps->scoreIsAbsolute &&
8835                     ( gameMode == MachinePlaysBlack ||
8836                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8837                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8838                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8839                      !WhiteOnMove(currentMove)
8840                     ) )
8841                 {
8842                     curscore = -curscore;
8843                 }
8844
8845                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8846
8847                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8848                         char buf[MSG_SIZ];
8849                         FILE *f;
8850                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8851                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8852                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8853                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8854                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8855                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8856                                 fclose(f);
8857                         } else DisplayError(_("failed writing PV"), 0);
8858                 }
8859
8860                 tempStats.depth = plylev;
8861                 tempStats.nodes = nodes;
8862                 tempStats.time = time;
8863                 tempStats.score = curscore;
8864                 tempStats.got_only_move = 0;
8865
8866                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8867                         int ticklen;
8868
8869                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8870                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8871                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8872                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8873                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8874                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8875                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8876                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8877                 }
8878
8879                 /* Buffer overflow protection */
8880                 if (pv[0] != NULLCHAR) {
8881                     if (strlen(pv) >= sizeof(tempStats.movelist)
8882                         && appData.debugMode) {
8883                         fprintf(debugFP,
8884                                 "PV is too long; using the first %u bytes.\n",
8885                                 (unsigned) sizeof(tempStats.movelist) - 1);
8886                     }
8887
8888                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8889                 } else {
8890                     sprintf(tempStats.movelist, " no PV\n");
8891                 }
8892
8893                 if (tempStats.seen_stat) {
8894                     tempStats.ok_to_send = 1;
8895                 }
8896
8897                 if (strchr(tempStats.movelist, '(') != NULL) {
8898                     tempStats.line_is_book = 1;
8899                     tempStats.nr_moves = 0;
8900                     tempStats.moves_left = 0;
8901                 } else {
8902                     tempStats.line_is_book = 0;
8903                 }
8904
8905                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8906                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8907
8908                 SendProgramStatsToFrontend( cps, &tempStats );
8909
8910                 /*
8911                     [AS] Protect the thinkOutput buffer from overflow... this
8912                     is only useful if buf1 hasn't overflowed first!
8913                 */
8914                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8915                          plylev,
8916                          (gameMode == TwoMachinesPlay ?
8917                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8918                          ((double) curscore) / 100.0,
8919                          prefixHint ? lastHint : "",
8920                          prefixHint ? " " : "" );
8921
8922                 if( buf1[0] != NULLCHAR ) {
8923                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8924
8925                     if( strlen(pv) > max_len ) {
8926                         if( appData.debugMode) {
8927                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8928                         }
8929                         pv[max_len+1] = '\0';
8930                     }
8931
8932                     strcat( thinkOutput, pv);
8933                 }
8934
8935                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8936                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8937                     DisplayMove(currentMove - 1);
8938                 }
8939                 return;
8940
8941             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8942                 /* crafty (9.25+) says "(only move) <move>"
8943                  * if there is only 1 legal move
8944                  */
8945                 sscanf(p, "(only move) %s", buf1);
8946                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8947                 sprintf(programStats.movelist, "%s (only move)", buf1);
8948                 programStats.depth = 1;
8949                 programStats.nr_moves = 1;
8950                 programStats.moves_left = 1;
8951                 programStats.nodes = 1;
8952                 programStats.time = 1;
8953                 programStats.got_only_move = 1;
8954
8955                 /* Not really, but we also use this member to
8956                    mean "line isn't going to change" (Crafty
8957                    isn't searching, so stats won't change) */
8958                 programStats.line_is_book = 1;
8959
8960                 SendProgramStatsToFrontend( cps, &programStats );
8961
8962                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8963                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8964                     DisplayMove(currentMove - 1);
8965                 }
8966                 return;
8967             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8968                               &time, &nodes, &plylev, &mvleft,
8969                               &mvtot, mvname) >= 5) {
8970                 /* The stat01: line is from Crafty (9.29+) in response
8971                    to the "." command */
8972                 programStats.seen_stat = 1;
8973                 cps->maybeThinking = TRUE;
8974
8975                 if (programStats.got_only_move || !appData.periodicUpdates)
8976                   return;
8977
8978                 programStats.depth = plylev;
8979                 programStats.time = time;
8980                 programStats.nodes = nodes;
8981                 programStats.moves_left = mvleft;
8982                 programStats.nr_moves = mvtot;
8983                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8984                 programStats.ok_to_send = 1;
8985                 programStats.movelist[0] = '\0';
8986
8987                 SendProgramStatsToFrontend( cps, &programStats );
8988
8989                 return;
8990
8991             } else if (strncmp(message,"++",2) == 0) {
8992                 /* Crafty 9.29+ outputs this */
8993                 programStats.got_fail = 2;
8994                 return;
8995
8996             } else if (strncmp(message,"--",2) == 0) {
8997                 /* Crafty 9.29+ outputs this */
8998                 programStats.got_fail = 1;
8999                 return;
9000
9001             } else if (thinkOutput[0] != NULLCHAR &&
9002                        strncmp(message, "    ", 4) == 0) {
9003                 unsigned message_len;
9004
9005                 p = message;
9006                 while (*p && *p == ' ') p++;
9007
9008                 message_len = strlen( p );
9009
9010                 /* [AS] Avoid buffer overflow */
9011                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9012                     strcat(thinkOutput, " ");
9013                     strcat(thinkOutput, p);
9014                 }
9015
9016                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9017                     strcat(programStats.movelist, " ");
9018                     strcat(programStats.movelist, p);
9019                 }
9020
9021                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9022                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9023                     DisplayMove(currentMove - 1);
9024                 }
9025                 return;
9026             }
9027         }
9028         else {
9029             buf1[0] = NULLCHAR;
9030
9031             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9032                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9033             {
9034                 ChessProgramStats cpstats;
9035
9036                 if (plyext != ' ' && plyext != '\t') {
9037                     time *= 100;
9038                 }
9039
9040                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9041                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9042                     curscore = -curscore;
9043                 }
9044
9045                 cpstats.depth = plylev;
9046                 cpstats.nodes = nodes;
9047                 cpstats.time = time;
9048                 cpstats.score = curscore;
9049                 cpstats.got_only_move = 0;
9050                 cpstats.movelist[0] = '\0';
9051
9052                 if (buf1[0] != NULLCHAR) {
9053                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9054                 }
9055
9056                 cpstats.ok_to_send = 0;
9057                 cpstats.line_is_book = 0;
9058                 cpstats.nr_moves = 0;
9059                 cpstats.moves_left = 0;
9060
9061                 SendProgramStatsToFrontend( cps, &cpstats );
9062             }
9063         }
9064     }
9065 }
9066
9067
9068 /* Parse a game score from the character string "game", and
9069    record it as the history of the current game.  The game
9070    score is NOT assumed to start from the standard position.
9071    The display is not updated in any way.
9072    */
9073 void
9074 ParseGameHistory (char *game)
9075 {
9076     ChessMove moveType;
9077     int fromX, fromY, toX, toY, boardIndex;
9078     char promoChar;
9079     char *p, *q;
9080     char buf[MSG_SIZ];
9081
9082     if (appData.debugMode)
9083       fprintf(debugFP, "Parsing game history: %s\n", game);
9084
9085     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9086     gameInfo.site = StrSave(appData.icsHost);
9087     gameInfo.date = PGNDate();
9088     gameInfo.round = StrSave("-");
9089
9090     /* Parse out names of players */
9091     while (*game == ' ') game++;
9092     p = buf;
9093     while (*game != ' ') *p++ = *game++;
9094     *p = NULLCHAR;
9095     gameInfo.white = StrSave(buf);
9096     while (*game == ' ') game++;
9097     p = buf;
9098     while (*game != ' ' && *game != '\n') *p++ = *game++;
9099     *p = NULLCHAR;
9100     gameInfo.black = StrSave(buf);
9101
9102     /* Parse moves */
9103     boardIndex = blackPlaysFirst ? 1 : 0;
9104     yynewstr(game);
9105     for (;;) {
9106         yyboardindex = boardIndex;
9107         moveType = (ChessMove) Myylex();
9108         switch (moveType) {
9109           case IllegalMove:             /* maybe suicide chess, etc. */
9110   if (appData.debugMode) {
9111     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9112     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9113     setbuf(debugFP, NULL);
9114   }
9115           case WhitePromotion:
9116           case BlackPromotion:
9117           case WhiteNonPromotion:
9118           case BlackNonPromotion:
9119           case NormalMove:
9120           case WhiteCapturesEnPassant:
9121           case BlackCapturesEnPassant:
9122           case WhiteKingSideCastle:
9123           case WhiteQueenSideCastle:
9124           case BlackKingSideCastle:
9125           case BlackQueenSideCastle:
9126           case WhiteKingSideCastleWild:
9127           case WhiteQueenSideCastleWild:
9128           case BlackKingSideCastleWild:
9129           case BlackQueenSideCastleWild:
9130           /* PUSH Fabien */
9131           case WhiteHSideCastleFR:
9132           case WhiteASideCastleFR:
9133           case BlackHSideCastleFR:
9134           case BlackASideCastleFR:
9135           /* POP Fabien */
9136             fromX = currentMoveString[0] - AAA;
9137             fromY = currentMoveString[1] - ONE;
9138             toX = currentMoveString[2] - AAA;
9139             toY = currentMoveString[3] - ONE;
9140             promoChar = currentMoveString[4];
9141             break;
9142           case WhiteDrop:
9143           case BlackDrop:
9144             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9145             fromX = moveType == WhiteDrop ?
9146               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9147             (int) CharToPiece(ToLower(currentMoveString[0]));
9148             fromY = DROP_RANK;
9149             toX = currentMoveString[2] - AAA;
9150             toY = currentMoveString[3] - ONE;
9151             promoChar = NULLCHAR;
9152             break;
9153           case AmbiguousMove:
9154             /* bug? */
9155             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9156   if (appData.debugMode) {
9157     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9158     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9159     setbuf(debugFP, NULL);
9160   }
9161             DisplayError(buf, 0);
9162             return;
9163           case ImpossibleMove:
9164             /* bug? */
9165             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9166   if (appData.debugMode) {
9167     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9168     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9169     setbuf(debugFP, NULL);
9170   }
9171             DisplayError(buf, 0);
9172             return;
9173           case EndOfFile:
9174             if (boardIndex < backwardMostMove) {
9175                 /* Oops, gap.  How did that happen? */
9176                 DisplayError(_("Gap in move list"), 0);
9177                 return;
9178             }
9179             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9180             if (boardIndex > forwardMostMove) {
9181                 forwardMostMove = boardIndex;
9182             }
9183             return;
9184           case ElapsedTime:
9185             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9186                 strcat(parseList[boardIndex-1], " ");
9187                 strcat(parseList[boardIndex-1], yy_text);
9188             }
9189             continue;
9190           case Comment:
9191           case PGNTag:
9192           case NAG:
9193           default:
9194             /* ignore */
9195             continue;
9196           case WhiteWins:
9197           case BlackWins:
9198           case GameIsDrawn:
9199           case GameUnfinished:
9200             if (gameMode == IcsExamining) {
9201                 if (boardIndex < backwardMostMove) {
9202                     /* Oops, gap.  How did that happen? */
9203                     return;
9204                 }
9205                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9206                 return;
9207             }
9208             gameInfo.result = moveType;
9209             p = strchr(yy_text, '{');
9210             if (p == NULL) p = strchr(yy_text, '(');
9211             if (p == NULL) {
9212                 p = yy_text;
9213                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9214             } else {
9215                 q = strchr(p, *p == '{' ? '}' : ')');
9216                 if (q != NULL) *q = NULLCHAR;
9217                 p++;
9218             }
9219             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9220             gameInfo.resultDetails = StrSave(p);
9221             continue;
9222         }
9223         if (boardIndex >= forwardMostMove &&
9224             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9225             backwardMostMove = blackPlaysFirst ? 1 : 0;
9226             return;
9227         }
9228         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9229                                  fromY, fromX, toY, toX, promoChar,
9230                                  parseList[boardIndex]);
9231         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9232         /* currentMoveString is set as a side-effect of yylex */
9233         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9234         strcat(moveList[boardIndex], "\n");
9235         boardIndex++;
9236         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9237         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9238           case MT_NONE:
9239           case MT_STALEMATE:
9240           default:
9241             break;
9242           case MT_CHECK:
9243             if(gameInfo.variant != VariantShogi)
9244                 strcat(parseList[boardIndex - 1], "+");
9245             break;
9246           case MT_CHECKMATE:
9247           case MT_STAINMATE:
9248             strcat(parseList[boardIndex - 1], "#");
9249             break;
9250         }
9251     }
9252 }
9253
9254
9255 /* Apply a move to the given board  */
9256 void
9257 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9258 {
9259   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9260   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9261
9262     /* [HGM] compute & store e.p. status and castling rights for new position */
9263     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9264
9265       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9266       oldEP = (signed char)board[EP_STATUS];
9267       board[EP_STATUS] = EP_NONE;
9268
9269   if (fromY == DROP_RANK) {
9270         /* must be first */
9271         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9272             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9273             return;
9274         }
9275         piece = board[toY][toX] = (ChessSquare) fromX;
9276   } else {
9277       int i;
9278
9279       if( board[toY][toX] != EmptySquare )
9280            board[EP_STATUS] = EP_CAPTURE;
9281
9282       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9283            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9284                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9285       } else
9286       if( board[fromY][fromX] == WhitePawn ) {
9287            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9288                board[EP_STATUS] = EP_PAWN_MOVE;
9289            if( toY-fromY==2) {
9290                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9291                         gameInfo.variant != VariantBerolina || toX < fromX)
9292                       board[EP_STATUS] = toX | berolina;
9293                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9294                         gameInfo.variant != VariantBerolina || toX > fromX)
9295                       board[EP_STATUS] = toX;
9296            }
9297       } else
9298       if( board[fromY][fromX] == BlackPawn ) {
9299            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9300                board[EP_STATUS] = EP_PAWN_MOVE;
9301            if( toY-fromY== -2) {
9302                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9303                         gameInfo.variant != VariantBerolina || toX < fromX)
9304                       board[EP_STATUS] = toX | berolina;
9305                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9306                         gameInfo.variant != VariantBerolina || toX > fromX)
9307                       board[EP_STATUS] = toX;
9308            }
9309        }
9310
9311        for(i=0; i<nrCastlingRights; i++) {
9312            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9313               board[CASTLING][i] == toX   && castlingRank[i] == toY
9314              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9315        }
9316
9317      if (fromX == toX && fromY == toY) return;
9318
9319      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9320      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9321      if(gameInfo.variant == VariantKnightmate)
9322          king += (int) WhiteUnicorn - (int) WhiteKing;
9323
9324     /* Code added by Tord: */
9325     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9326     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9327         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9328       board[fromY][fromX] = EmptySquare;
9329       board[toY][toX] = EmptySquare;
9330       if((toX > fromX) != (piece == WhiteRook)) {
9331         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9332       } else {
9333         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9334       }
9335     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9336                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9337       board[fromY][fromX] = EmptySquare;
9338       board[toY][toX] = EmptySquare;
9339       if((toX > fromX) != (piece == BlackRook)) {
9340         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9341       } else {
9342         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9343       }
9344     /* End of code added by Tord */
9345
9346     } else if (board[fromY][fromX] == king
9347         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9348         && toY == fromY && toX > fromX+1) {
9349         board[fromY][fromX] = EmptySquare;
9350         board[toY][toX] = king;
9351         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9352         board[fromY][BOARD_RGHT-1] = EmptySquare;
9353     } else if (board[fromY][fromX] == king
9354         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9355                && toY == fromY && toX < fromX-1) {
9356         board[fromY][fromX] = EmptySquare;
9357         board[toY][toX] = king;
9358         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9359         board[fromY][BOARD_LEFT] = EmptySquare;
9360     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9361                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9362                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9363                ) {
9364         /* white pawn promotion */
9365         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9366         if(gameInfo.variant==VariantBughouse ||
9367            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9368             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9369         board[fromY][fromX] = EmptySquare;
9370     } else if ((fromY >= BOARD_HEIGHT>>1)
9371                && (toX != fromX)
9372                && gameInfo.variant != VariantXiangqi
9373                && gameInfo.variant != VariantBerolina
9374                && (board[fromY][fromX] == WhitePawn)
9375                && (board[toY][toX] == EmptySquare)) {
9376         board[fromY][fromX] = EmptySquare;
9377         board[toY][toX] = WhitePawn;
9378         captured = board[toY - 1][toX];
9379         board[toY - 1][toX] = EmptySquare;
9380     } else if ((fromY == BOARD_HEIGHT-4)
9381                && (toX == fromX)
9382                && gameInfo.variant == VariantBerolina
9383                && (board[fromY][fromX] == WhitePawn)
9384                && (board[toY][toX] == EmptySquare)) {
9385         board[fromY][fromX] = EmptySquare;
9386         board[toY][toX] = WhitePawn;
9387         if(oldEP & EP_BEROLIN_A) {
9388                 captured = board[fromY][fromX-1];
9389                 board[fromY][fromX-1] = EmptySquare;
9390         }else{  captured = board[fromY][fromX+1];
9391                 board[fromY][fromX+1] = EmptySquare;
9392         }
9393     } else if (board[fromY][fromX] == king
9394         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9395                && toY == fromY && toX > fromX+1) {
9396         board[fromY][fromX] = EmptySquare;
9397         board[toY][toX] = king;
9398         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9399         board[fromY][BOARD_RGHT-1] = EmptySquare;
9400     } else if (board[fromY][fromX] == king
9401         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9402                && toY == fromY && toX < fromX-1) {
9403         board[fromY][fromX] = EmptySquare;
9404         board[toY][toX] = king;
9405         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9406         board[fromY][BOARD_LEFT] = EmptySquare;
9407     } else if (fromY == 7 && fromX == 3
9408                && board[fromY][fromX] == BlackKing
9409                && toY == 7 && toX == 5) {
9410         board[fromY][fromX] = EmptySquare;
9411         board[toY][toX] = BlackKing;
9412         board[fromY][7] = EmptySquare;
9413         board[toY][4] = BlackRook;
9414     } else if (fromY == 7 && fromX == 3
9415                && board[fromY][fromX] == BlackKing
9416                && toY == 7 && toX == 1) {
9417         board[fromY][fromX] = EmptySquare;
9418         board[toY][toX] = BlackKing;
9419         board[fromY][0] = EmptySquare;
9420         board[toY][2] = BlackRook;
9421     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9422                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9423                && toY < promoRank && promoChar
9424                ) {
9425         /* black pawn promotion */
9426         board[toY][toX] = CharToPiece(ToLower(promoChar));
9427         if(gameInfo.variant==VariantBughouse ||
9428            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9429             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9430         board[fromY][fromX] = EmptySquare;
9431     } else if ((fromY < BOARD_HEIGHT>>1)
9432                && (toX != fromX)
9433                && gameInfo.variant != VariantXiangqi
9434                && gameInfo.variant != VariantBerolina
9435                && (board[fromY][fromX] == BlackPawn)
9436                && (board[toY][toX] == EmptySquare)) {
9437         board[fromY][fromX] = EmptySquare;
9438         board[toY][toX] = BlackPawn;
9439         captured = board[toY + 1][toX];
9440         board[toY + 1][toX] = EmptySquare;
9441     } else if ((fromY == 3)
9442                && (toX == fromX)
9443                && gameInfo.variant == VariantBerolina
9444                && (board[fromY][fromX] == BlackPawn)
9445                && (board[toY][toX] == EmptySquare)) {
9446         board[fromY][fromX] = EmptySquare;
9447         board[toY][toX] = BlackPawn;
9448         if(oldEP & EP_BEROLIN_A) {
9449                 captured = board[fromY][fromX-1];
9450                 board[fromY][fromX-1] = EmptySquare;
9451         }else{  captured = board[fromY][fromX+1];
9452                 board[fromY][fromX+1] = EmptySquare;
9453         }
9454     } else {
9455         board[toY][toX] = board[fromY][fromX];
9456         board[fromY][fromX] = EmptySquare;
9457     }
9458   }
9459
9460     if (gameInfo.holdingsWidth != 0) {
9461
9462       /* !!A lot more code needs to be written to support holdings  */
9463       /* [HGM] OK, so I have written it. Holdings are stored in the */
9464       /* penultimate board files, so they are automaticlly stored   */
9465       /* in the game history.                                       */
9466       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9467                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9468         /* Delete from holdings, by decreasing count */
9469         /* and erasing image if necessary            */
9470         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9471         if(p < (int) BlackPawn) { /* white drop */
9472              p -= (int)WhitePawn;
9473                  p = PieceToNumber((ChessSquare)p);
9474              if(p >= gameInfo.holdingsSize) p = 0;
9475              if(--board[p][BOARD_WIDTH-2] <= 0)
9476                   board[p][BOARD_WIDTH-1] = EmptySquare;
9477              if((int)board[p][BOARD_WIDTH-2] < 0)
9478                         board[p][BOARD_WIDTH-2] = 0;
9479         } else {                  /* black drop */
9480              p -= (int)BlackPawn;
9481                  p = PieceToNumber((ChessSquare)p);
9482              if(p >= gameInfo.holdingsSize) p = 0;
9483              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9484                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9485              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9486                         board[BOARD_HEIGHT-1-p][1] = 0;
9487         }
9488       }
9489       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9490           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9491         /* [HGM] holdings: Add to holdings, if holdings exist */
9492         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9493                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9494                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9495         }
9496         p = (int) captured;
9497         if (p >= (int) BlackPawn) {
9498           p -= (int)BlackPawn;
9499           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9500                   /* in Shogi restore piece to its original  first */
9501                   captured = (ChessSquare) (DEMOTED captured);
9502                   p = DEMOTED p;
9503           }
9504           p = PieceToNumber((ChessSquare)p);
9505           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9506           board[p][BOARD_WIDTH-2]++;
9507           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9508         } else {
9509           p -= (int)WhitePawn;
9510           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9511                   captured = (ChessSquare) (DEMOTED captured);
9512                   p = DEMOTED p;
9513           }
9514           p = PieceToNumber((ChessSquare)p);
9515           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9516           board[BOARD_HEIGHT-1-p][1]++;
9517           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9518         }
9519       }
9520     } else if (gameInfo.variant == VariantAtomic) {
9521       if (captured != EmptySquare) {
9522         int y, x;
9523         for (y = toY-1; y <= toY+1; y++) {
9524           for (x = toX-1; x <= toX+1; x++) {
9525             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9526                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9527               board[y][x] = EmptySquare;
9528             }
9529           }
9530         }
9531         board[toY][toX] = EmptySquare;
9532       }
9533     }
9534     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9535         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9536     } else
9537     if(promoChar == '+') {
9538         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9539         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9540     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9541         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9542         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9543            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9544         board[toY][toX] = newPiece;
9545     }
9546     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9547                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9548         // [HGM] superchess: take promotion piece out of holdings
9549         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9550         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9551             if(!--board[k][BOARD_WIDTH-2])
9552                 board[k][BOARD_WIDTH-1] = EmptySquare;
9553         } else {
9554             if(!--board[BOARD_HEIGHT-1-k][1])
9555                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9556         }
9557     }
9558
9559 }
9560
9561 /* Updates forwardMostMove */
9562 void
9563 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9564 {
9565 //    forwardMostMove++; // [HGM] bare: moved downstream
9566
9567     (void) CoordsToAlgebraic(boards[forwardMostMove],
9568                              PosFlags(forwardMostMove),
9569                              fromY, fromX, toY, toX, promoChar,
9570                              parseList[forwardMostMove]);
9571
9572     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9573         int timeLeft; static int lastLoadFlag=0; int king, piece;
9574         piece = boards[forwardMostMove][fromY][fromX];
9575         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9576         if(gameInfo.variant == VariantKnightmate)
9577             king += (int) WhiteUnicorn - (int) WhiteKing;
9578         if(forwardMostMove == 0) {
9579             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9580                 fprintf(serverMoves, "%s;", UserName());
9581             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9582                 fprintf(serverMoves, "%s;", second.tidy);
9583             fprintf(serverMoves, "%s;", first.tidy);
9584             if(gameMode == MachinePlaysWhite)
9585                 fprintf(serverMoves, "%s;", UserName());
9586             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9587                 fprintf(serverMoves, "%s;", second.tidy);
9588         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9589         lastLoadFlag = loadFlag;
9590         // print base move
9591         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9592         // print castling suffix
9593         if( toY == fromY && piece == king ) {
9594             if(toX-fromX > 1)
9595                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9596             if(fromX-toX >1)
9597                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9598         }
9599         // e.p. suffix
9600         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9601              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9602              boards[forwardMostMove][toY][toX] == EmptySquare
9603              && fromX != toX && fromY != toY)
9604                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9605         // promotion suffix
9606         if(promoChar != NULLCHAR)
9607                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9608         if(!loadFlag) {
9609                 char buf[MOVE_LEN*2], *p; int len;
9610             fprintf(serverMoves, "/%d/%d",
9611                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9612             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9613             else                      timeLeft = blackTimeRemaining/1000;
9614             fprintf(serverMoves, "/%d", timeLeft);
9615                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9616                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9617                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9618             fprintf(serverMoves, "/%s", buf);
9619         }
9620         fflush(serverMoves);
9621     }
9622
9623     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9624         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9625       return;
9626     }
9627     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9628     if (commentList[forwardMostMove+1] != NULL) {
9629         free(commentList[forwardMostMove+1]);
9630         commentList[forwardMostMove+1] = NULL;
9631     }
9632     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9633     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9634     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9635     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9636     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9637     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9638     adjustedClock = FALSE;
9639     gameInfo.result = GameUnfinished;
9640     if (gameInfo.resultDetails != NULL) {
9641         free(gameInfo.resultDetails);
9642         gameInfo.resultDetails = NULL;
9643     }
9644     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9645                               moveList[forwardMostMove - 1]);
9646     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9647       case MT_NONE:
9648       case MT_STALEMATE:
9649       default:
9650         break;
9651       case MT_CHECK:
9652         if(gameInfo.variant != VariantShogi)
9653             strcat(parseList[forwardMostMove - 1], "+");
9654         break;
9655       case MT_CHECKMATE:
9656       case MT_STAINMATE:
9657         strcat(parseList[forwardMostMove - 1], "#");
9658         break;
9659     }
9660
9661 }
9662
9663 /* Updates currentMove if not pausing */
9664 void
9665 ShowMove (int fromX, int fromY, int toX, int toY)
9666 {
9667     int instant = (gameMode == PlayFromGameFile) ?
9668         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9669     if(appData.noGUI) return;
9670     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9671         if (!instant) {
9672             if (forwardMostMove == currentMove + 1) {
9673                 AnimateMove(boards[forwardMostMove - 1],
9674                             fromX, fromY, toX, toY);
9675             }
9676             if (appData.highlightLastMove) {
9677                 SetHighlights(fromX, fromY, toX, toY);
9678             }
9679         }
9680         currentMove = forwardMostMove;
9681     }
9682
9683     if (instant) return;
9684
9685     DisplayMove(currentMove - 1);
9686     DrawPosition(FALSE, boards[currentMove]);
9687     DisplayBothClocks();
9688     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9689 }
9690
9691 void
9692 SendEgtPath (ChessProgramState *cps)
9693 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9694         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9695
9696         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9697
9698         while(*p) {
9699             char c, *q = name+1, *r, *s;
9700
9701             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9702             while(*p && *p != ',') *q++ = *p++;
9703             *q++ = ':'; *q = 0;
9704             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9705                 strcmp(name, ",nalimov:") == 0 ) {
9706                 // take nalimov path from the menu-changeable option first, if it is defined
9707               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9708                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9709             } else
9710             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9711                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9712                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9713                 s = r = StrStr(s, ":") + 1; // beginning of path info
9714                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9715                 c = *r; *r = 0;             // temporarily null-terminate path info
9716                     *--q = 0;               // strip of trailig ':' from name
9717                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9718                 *r = c;
9719                 SendToProgram(buf,cps);     // send egtbpath command for this format
9720             }
9721             if(*p == ',') p++; // read away comma to position for next format name
9722         }
9723 }
9724
9725 void
9726 InitChessProgram (ChessProgramState *cps, int setup)
9727 /* setup needed to setup FRC opening position */
9728 {
9729     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9730     if (appData.noChessProgram) return;
9731     hintRequested = FALSE;
9732     bookRequested = FALSE;
9733
9734     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9735     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9736     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9737     if(cps->memSize) { /* [HGM] memory */
9738       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9739         SendToProgram(buf, cps);
9740     }
9741     SendEgtPath(cps); /* [HGM] EGT */
9742     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9743       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9744         SendToProgram(buf, cps);
9745     }
9746
9747     SendToProgram(cps->initString, cps);
9748     if (gameInfo.variant != VariantNormal &&
9749         gameInfo.variant != VariantLoadable
9750         /* [HGM] also send variant if board size non-standard */
9751         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9752                                             ) {
9753       char *v = VariantName(gameInfo.variant);
9754       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9755         /* [HGM] in protocol 1 we have to assume all variants valid */
9756         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9757         DisplayFatalError(buf, 0, 1);
9758         return;
9759       }
9760
9761       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9762       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9763       if( gameInfo.variant == VariantXiangqi )
9764            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9765       if( gameInfo.variant == VariantShogi )
9766            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9767       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9768            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9769       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9770           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9771            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9772       if( gameInfo.variant == VariantCourier )
9773            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9774       if( gameInfo.variant == VariantSuper )
9775            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9776       if( gameInfo.variant == VariantGreat )
9777            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9778       if( gameInfo.variant == VariantSChess )
9779            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9780       if( gameInfo.variant == VariantGrand )
9781            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9782
9783       if(overruled) {
9784         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9785                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9786            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9787            if(StrStr(cps->variants, b) == NULL) {
9788                // specific sized variant not known, check if general sizing allowed
9789                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9790                    if(StrStr(cps->variants, "boardsize") == NULL) {
9791                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9792                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9793                        DisplayFatalError(buf, 0, 1);
9794                        return;
9795                    }
9796                    /* [HGM] here we really should compare with the maximum supported board size */
9797                }
9798            }
9799       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9800       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9801       SendToProgram(buf, cps);
9802     }
9803     currentlyInitializedVariant = gameInfo.variant;
9804
9805     /* [HGM] send opening position in FRC to first engine */
9806     if(setup) {
9807           SendToProgram("force\n", cps);
9808           SendBoard(cps, 0);
9809           /* engine is now in force mode! Set flag to wake it up after first move. */
9810           setboardSpoiledMachineBlack = 1;
9811     }
9812
9813     if (cps->sendICS) {
9814       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9815       SendToProgram(buf, cps);
9816     }
9817     cps->maybeThinking = FALSE;
9818     cps->offeredDraw = 0;
9819     if (!appData.icsActive) {
9820         SendTimeControl(cps, movesPerSession, timeControl,
9821                         timeIncrement, appData.searchDepth,
9822                         searchTime);
9823     }
9824     if (appData.showThinking
9825         // [HGM] thinking: four options require thinking output to be sent
9826         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9827                                 ) {
9828         SendToProgram("post\n", cps);
9829     }
9830     SendToProgram("hard\n", cps);
9831     if (!appData.ponderNextMove) {
9832         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9833            it without being sure what state we are in first.  "hard"
9834            is not a toggle, so that one is OK.
9835          */
9836         SendToProgram("easy\n", cps);
9837     }
9838     if (cps->usePing) {
9839       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9840       SendToProgram(buf, cps);
9841     }
9842     cps->initDone = TRUE;
9843     ClearEngineOutputPane(cps == &second);
9844 }
9845
9846
9847 void
9848 StartChessProgram (ChessProgramState *cps)
9849 {
9850     char buf[MSG_SIZ];
9851     int err;
9852
9853     if (appData.noChessProgram) return;
9854     cps->initDone = FALSE;
9855
9856     if (strcmp(cps->host, "localhost") == 0) {
9857         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9858     } else if (*appData.remoteShell == NULLCHAR) {
9859         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9860     } else {
9861         if (*appData.remoteUser == NULLCHAR) {
9862           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9863                     cps->program);
9864         } else {
9865           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9866                     cps->host, appData.remoteUser, cps->program);
9867         }
9868         err = StartChildProcess(buf, "", &cps->pr);
9869     }
9870
9871     if (err != 0) {
9872       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9873         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9874         if(cps != &first) return;
9875         appData.noChessProgram = TRUE;
9876         ThawUI();
9877         SetNCPMode();
9878 //      DisplayFatalError(buf, err, 1);
9879 //      cps->pr = NoProc;
9880 //      cps->isr = NULL;
9881         return;
9882     }
9883
9884     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9885     if (cps->protocolVersion > 1) {
9886       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9887       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9888       cps->comboCnt = 0;  //                and values of combo boxes
9889       SendToProgram(buf, cps);
9890     } else {
9891       SendToProgram("xboard\n", cps);
9892     }
9893 }
9894
9895 void
9896 TwoMachinesEventIfReady P((void))
9897 {
9898   static int curMess = 0;
9899   if (first.lastPing != first.lastPong) {
9900     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9901     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9902     return;
9903   }
9904   if (second.lastPing != second.lastPong) {
9905     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9906     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9907     return;
9908   }
9909   DisplayMessage("", ""); curMess = 0;
9910   ThawUI();
9911   TwoMachinesEvent();
9912 }
9913
9914 char *
9915 MakeName (char *template)
9916 {
9917     time_t clock;
9918     struct tm *tm;
9919     static char buf[MSG_SIZ];
9920     char *p = buf;
9921     int i;
9922
9923     clock = time((time_t *)NULL);
9924     tm = localtime(&clock);
9925
9926     while(*p++ = *template++) if(p[-1] == '%') {
9927         switch(*template++) {
9928           case 0:   *p = 0; return buf;
9929           case 'Y': i = tm->tm_year+1900; break;
9930           case 'y': i = tm->tm_year-100; break;
9931           case 'M': i = tm->tm_mon+1; break;
9932           case 'd': i = tm->tm_mday; break;
9933           case 'h': i = tm->tm_hour; break;
9934           case 'm': i = tm->tm_min; break;
9935           case 's': i = tm->tm_sec; break;
9936           default:  i = 0;
9937         }
9938         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9939     }
9940     return buf;
9941 }
9942
9943 int
9944 CountPlayers (char *p)
9945 {
9946     int n = 0;
9947     while(p = strchr(p, '\n')) p++, n++; // count participants
9948     return n;
9949 }
9950
9951 FILE *
9952 WriteTourneyFile (char *results, FILE *f)
9953 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9954     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9955     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9956         // create a file with tournament description
9957         fprintf(f, "-participants {%s}\n", appData.participants);
9958         fprintf(f, "-seedBase %d\n", appData.seedBase);
9959         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9960         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9961         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9962         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9963         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9964         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9965         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9966         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9967         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9968         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9969         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9970         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9971         if(searchTime > 0)
9972                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9973         else {
9974                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9975                 fprintf(f, "-tc %s\n", appData.timeControl);
9976                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9977         }
9978         fprintf(f, "-results \"%s\"\n", results);
9979     }
9980     return f;
9981 }
9982
9983 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9984
9985 void
9986 Substitute (char *participants, int expunge)
9987 {
9988     int i, changed, changes=0, nPlayers=0;
9989     char *p, *q, *r, buf[MSG_SIZ];
9990     if(participants == NULL) return;
9991     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9992     r = p = participants; q = appData.participants;
9993     while(*p && *p == *q) {
9994         if(*p == '\n') r = p+1, nPlayers++;
9995         p++; q++;
9996     }
9997     if(*p) { // difference
9998         while(*p && *p++ != '\n');
9999         while(*q && *q++ != '\n');
10000       changed = nPlayers;
10001         changes = 1 + (strcmp(p, q) != 0);
10002     }
10003     if(changes == 1) { // a single engine mnemonic was changed
10004         q = r; while(*q) nPlayers += (*q++ == '\n');
10005         p = buf; while(*r && (*p = *r++) != '\n') p++;
10006         *p = NULLCHAR;
10007         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10008         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10009         if(mnemonic[i]) { // The substitute is valid
10010             FILE *f;
10011             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10012                 flock(fileno(f), LOCK_EX);
10013                 ParseArgsFromFile(f);
10014                 fseek(f, 0, SEEK_SET);
10015                 FREE(appData.participants); appData.participants = participants;
10016                 if(expunge) { // erase results of replaced engine
10017                     int len = strlen(appData.results), w, b, dummy;
10018                     for(i=0; i<len; i++) {
10019                         Pairing(i, nPlayers, &w, &b, &dummy);
10020                         if((w == changed || b == changed) && appData.results[i] == '*') {
10021                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10022                             fclose(f);
10023                             return;
10024                         }
10025                     }
10026                     for(i=0; i<len; i++) {
10027                         Pairing(i, nPlayers, &w, &b, &dummy);
10028                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10029                     }
10030                 }
10031                 WriteTourneyFile(appData.results, f);
10032                 fclose(f); // release lock
10033                 return;
10034             }
10035         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10036     }
10037     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10038     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10039     free(participants);
10040     return;
10041 }
10042
10043 int
10044 CreateTourney (char *name)
10045 {
10046         FILE *f;
10047         if(matchMode && strcmp(name, appData.tourneyFile)) {
10048              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10049         }
10050         if(name[0] == NULLCHAR) {
10051             if(appData.participants[0])
10052                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10053             return 0;
10054         }
10055         f = fopen(name, "r");
10056         if(f) { // file exists
10057             ASSIGN(appData.tourneyFile, name);
10058             ParseArgsFromFile(f); // parse it
10059         } else {
10060             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10061             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10062                 DisplayError(_("Not enough participants"), 0);
10063                 return 0;
10064             }
10065             ASSIGN(appData.tourneyFile, name);
10066             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10067             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10068         }
10069         fclose(f);
10070         appData.noChessProgram = FALSE;
10071         appData.clockMode = TRUE;
10072         SetGNUMode();
10073         return 1;
10074 }
10075
10076 int
10077 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10078 {
10079     char buf[MSG_SIZ], *p, *q;
10080     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10081     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10082     skip = !all && group[0]; // if group requested, we start in skip mode
10083     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10084         p = names; q = buf; header = 0;
10085         while(*p && *p != '\n') *q++ = *p++;
10086         *q = 0;
10087         if(*p == '\n') p++;
10088         if(buf[0] == '#') {
10089             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10090             depth++; // we must be entering a new group
10091             if(all) continue; // suppress printing group headers when complete list requested
10092             header = 1;
10093             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10094         }
10095         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10096         if(engineList[i]) free(engineList[i]);
10097         engineList[i] = strdup(buf);
10098         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10099         if(engineMnemonic[i]) free(engineMnemonic[i]);
10100         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10101             strcat(buf, " (");
10102             sscanf(q + 8, "%s", buf + strlen(buf));
10103             strcat(buf, ")");
10104         }
10105         engineMnemonic[i] = strdup(buf);
10106         i++;
10107     }
10108     engineList[i] = engineMnemonic[i] = NULL;
10109     return i;
10110 }
10111
10112 // following implemented as macro to avoid type limitations
10113 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10114
10115 void
10116 SwapEngines (int n)
10117 {   // swap settings for first engine and other engine (so far only some selected options)
10118     int h;
10119     char *p;
10120     if(n == 0) return;
10121     SWAP(directory, p)
10122     SWAP(chessProgram, p)
10123     SWAP(isUCI, h)
10124     SWAP(hasOwnBookUCI, h)
10125     SWAP(protocolVersion, h)
10126     SWAP(reuse, h)
10127     SWAP(scoreIsAbsolute, h)
10128     SWAP(timeOdds, h)
10129     SWAP(logo, p)
10130     SWAP(pgnName, p)
10131     SWAP(pvSAN, h)
10132     SWAP(engOptions, p)
10133     SWAP(engInitString, p)
10134     SWAP(computerString, p)
10135     SWAP(features, p)
10136     SWAP(fenOverride, p)
10137     SWAP(NPS, h)
10138     SWAP(accumulateTC, h)
10139     SWAP(host, p)
10140 }
10141
10142 int
10143 SetPlayer (int player, char *p)
10144 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10145     int i;
10146     char buf[MSG_SIZ], *engineName;
10147     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10148     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10149     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10150     if(mnemonic[i]) {
10151         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10152         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10153         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10154         ParseArgsFromString(buf);
10155     }
10156     free(engineName);
10157     return i;
10158 }
10159
10160 char *recentEngines;
10161
10162 void
10163 RecentEngineEvent (int nr)
10164 {
10165     int n;
10166 //    SwapEngines(1); // bump first to second
10167 //    ReplaceEngine(&second, 1); // and load it there
10168     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10169     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10170     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10171         ReplaceEngine(&first, 0);
10172         FloatToFront(&appData.recentEngineList, command[n]);
10173     }
10174 }
10175
10176 int
10177 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10178 {   // determine players from game number
10179     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10180
10181     if(appData.tourneyType == 0) {
10182         roundsPerCycle = (nPlayers - 1) | 1;
10183         pairingsPerRound = nPlayers / 2;
10184     } else if(appData.tourneyType > 0) {
10185         roundsPerCycle = nPlayers - appData.tourneyType;
10186         pairingsPerRound = appData.tourneyType;
10187     }
10188     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10189     gamesPerCycle = gamesPerRound * roundsPerCycle;
10190     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10191     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10192     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10193     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10194     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10195     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10196
10197     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10198     if(appData.roundSync) *syncInterval = gamesPerRound;
10199
10200     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10201
10202     if(appData.tourneyType == 0) {
10203         if(curPairing == (nPlayers-1)/2 ) {
10204             *whitePlayer = curRound;
10205             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10206         } else {
10207             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10208             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10209             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10210             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10211         }
10212     } else if(appData.tourneyType > 1) {
10213         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10214         *whitePlayer = curRound + appData.tourneyType;
10215     } else if(appData.tourneyType > 0) {
10216         *whitePlayer = curPairing;
10217         *blackPlayer = curRound + appData.tourneyType;
10218     }
10219
10220     // take care of white/black alternation per round. 
10221     // For cycles and games this is already taken care of by default, derived from matchGame!
10222     return curRound & 1;
10223 }
10224
10225 int
10226 NextTourneyGame (int nr, int *swapColors)
10227 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10228     char *p, *q;
10229     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10230     FILE *tf;
10231     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10232     tf = fopen(appData.tourneyFile, "r");
10233     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10234     ParseArgsFromFile(tf); fclose(tf);
10235     InitTimeControls(); // TC might be altered from tourney file
10236
10237     nPlayers = CountPlayers(appData.participants); // count participants
10238     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10239     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10240
10241     if(syncInterval) {
10242         p = q = appData.results;
10243         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10244         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10245             DisplayMessage(_("Waiting for other game(s)"),"");
10246             waitingForGame = TRUE;
10247             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10248             return 0;
10249         }
10250         waitingForGame = FALSE;
10251     }
10252
10253     if(appData.tourneyType < 0) {
10254         if(nr>=0 && !pairingReceived) {
10255             char buf[1<<16];
10256             if(pairing.pr == NoProc) {
10257                 if(!appData.pairingEngine[0]) {
10258                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10259                     return 0;
10260                 }
10261                 StartChessProgram(&pairing); // starts the pairing engine
10262             }
10263             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10264             SendToProgram(buf, &pairing);
10265             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10266             SendToProgram(buf, &pairing);
10267             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10268         }
10269         pairingReceived = 0;                              // ... so we continue here 
10270         *swapColors = 0;
10271         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10272         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10273         matchGame = 1; roundNr = nr / syncInterval + 1;
10274     }
10275
10276     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10277
10278     // redefine engines, engine dir, etc.
10279     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10280     if(first.pr == NoProc) {
10281       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10282       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10283     }
10284     if(second.pr == NoProc) {
10285       SwapEngines(1);
10286       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10287       SwapEngines(1);         // and make that valid for second engine by swapping
10288       InitEngine(&second, 1);
10289     }
10290     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10291     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10292     return 1;
10293 }
10294
10295 void
10296 NextMatchGame ()
10297 {   // performs game initialization that does not invoke engines, and then tries to start the game
10298     int res, firstWhite, swapColors = 0;
10299     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10300     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
10301         char buf[MSG_SIZ];
10302         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10303         if(strcmp(buf, currentDebugFile)) { // name has changed
10304             FILE *f = fopen(buf, "w");
10305             if(f) { // if opening the new file failed, just keep using the old one
10306                 ASSIGN(currentDebugFile, buf);
10307                 fclose(debugFP);
10308                 debugFP = f;
10309             }
10310             if(appData.serverFileName) {
10311                 if(serverFP) fclose(serverFP);
10312                 serverFP = fopen(appData.serverFileName, "w");
10313                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10314                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10315             }
10316         }
10317     }
10318     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10319     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10320     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10321     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10322     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10323     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10324     Reset(FALSE, first.pr != NoProc);
10325     res = LoadGameOrPosition(matchGame); // setup game
10326     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10327     if(!res) return; // abort when bad game/pos file
10328     TwoMachinesEvent();
10329 }
10330
10331 void
10332 UserAdjudicationEvent (int result)
10333 {
10334     ChessMove gameResult = GameIsDrawn;
10335
10336     if( result > 0 ) {
10337         gameResult = WhiteWins;
10338     }
10339     else if( result < 0 ) {
10340         gameResult = BlackWins;
10341     }
10342
10343     if( gameMode == TwoMachinesPlay ) {
10344         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10345     }
10346 }
10347
10348
10349 // [HGM] save: calculate checksum of game to make games easily identifiable
10350 int
10351 StringCheckSum (char *s)
10352 {
10353         int i = 0;
10354         if(s==NULL) return 0;
10355         while(*s) i = i*259 + *s++;
10356         return i;
10357 }
10358
10359 int
10360 GameCheckSum ()
10361 {
10362         int i, sum=0;
10363         for(i=backwardMostMove; i<forwardMostMove; i++) {
10364                 sum += pvInfoList[i].depth;
10365                 sum += StringCheckSum(parseList[i]);
10366                 sum += StringCheckSum(commentList[i]);
10367                 sum *= 261;
10368         }
10369         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10370         return sum + StringCheckSum(commentList[i]);
10371 } // end of save patch
10372
10373 void
10374 GameEnds (ChessMove result, char *resultDetails, int whosays)
10375 {
10376     GameMode nextGameMode;
10377     int isIcsGame;
10378     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10379
10380     if(endingGame) return; /* [HGM] crash: forbid recursion */
10381     endingGame = 1;
10382     if(twoBoards) { // [HGM] dual: switch back to one board
10383         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10384         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10385     }
10386     if (appData.debugMode) {
10387       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10388               result, resultDetails ? resultDetails : "(null)", whosays);
10389     }
10390
10391     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10392
10393     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10394         /* If we are playing on ICS, the server decides when the
10395            game is over, but the engine can offer to draw, claim
10396            a draw, or resign.
10397          */
10398 #if ZIPPY
10399         if (appData.zippyPlay && first.initDone) {
10400             if (result == GameIsDrawn) {
10401                 /* In case draw still needs to be claimed */
10402                 SendToICS(ics_prefix);
10403                 SendToICS("draw\n");
10404             } else if (StrCaseStr(resultDetails, "resign")) {
10405                 SendToICS(ics_prefix);
10406                 SendToICS("resign\n");
10407             }
10408         }
10409 #endif
10410         endingGame = 0; /* [HGM] crash */
10411         return;
10412     }
10413
10414     /* If we're loading the game from a file, stop */
10415     if (whosays == GE_FILE) {
10416       (void) StopLoadGameTimer();
10417       gameFileFP = NULL;
10418     }
10419
10420     /* Cancel draw offers */
10421     first.offeredDraw = second.offeredDraw = 0;
10422
10423     /* If this is an ICS game, only ICS can really say it's done;
10424        if not, anyone can. */
10425     isIcsGame = (gameMode == IcsPlayingWhite ||
10426                  gameMode == IcsPlayingBlack ||
10427                  gameMode == IcsObserving    ||
10428                  gameMode == IcsExamining);
10429
10430     if (!isIcsGame || whosays == GE_ICS) {
10431         /* OK -- not an ICS game, or ICS said it was done */
10432         StopClocks();
10433         if (!isIcsGame && !appData.noChessProgram)
10434           SetUserThinkingEnables();
10435
10436         /* [HGM] if a machine claims the game end we verify this claim */
10437         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10438             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10439                 char claimer;
10440                 ChessMove trueResult = (ChessMove) -1;
10441
10442                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10443                                             first.twoMachinesColor[0] :
10444                                             second.twoMachinesColor[0] ;
10445
10446                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10447                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10448                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10449                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10450                 } else
10451                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10452                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10453                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10454                 } else
10455                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10456                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10457                 }
10458
10459                 // now verify win claims, but not in drop games, as we don't understand those yet
10460                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10461                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10462                     (result == WhiteWins && claimer == 'w' ||
10463                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10464                       if (appData.debugMode) {
10465                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10466                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10467                       }
10468                       if(result != trueResult) {
10469                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10470                               result = claimer == 'w' ? BlackWins : WhiteWins;
10471                               resultDetails = buf;
10472                       }
10473                 } else
10474                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10475                     && (forwardMostMove <= backwardMostMove ||
10476                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10477                         (claimer=='b')==(forwardMostMove&1))
10478                                                                                   ) {
10479                       /* [HGM] verify: draws that were not flagged are false claims */
10480                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10481                       result = claimer == 'w' ? BlackWins : WhiteWins;
10482                       resultDetails = buf;
10483                 }
10484                 /* (Claiming a loss is accepted no questions asked!) */
10485             }
10486             /* [HGM] bare: don't allow bare King to win */
10487             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10488                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10489                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10490                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10491                && result != GameIsDrawn)
10492             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10493                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10494                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10495                         if(p >= 0 && p <= (int)WhiteKing) k++;
10496                 }
10497                 if (appData.debugMode) {
10498                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10499                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10500                 }
10501                 if(k <= 1) {
10502                         result = GameIsDrawn;
10503                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10504                         resultDetails = buf;
10505                 }
10506             }
10507         }
10508
10509
10510         if(serverMoves != NULL && !loadFlag) { char c = '=';
10511             if(result==WhiteWins) c = '+';
10512             if(result==BlackWins) c = '-';
10513             if(resultDetails != NULL)
10514                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10515         }
10516         if (resultDetails != NULL) {
10517             gameInfo.result = result;
10518             gameInfo.resultDetails = StrSave(resultDetails);
10519
10520             /* display last move only if game was not loaded from file */
10521             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10522                 DisplayMove(currentMove - 1);
10523
10524             if (forwardMostMove != 0) {
10525                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10526                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10527                                                                 ) {
10528                     if (*appData.saveGameFile != NULLCHAR) {
10529                         SaveGameToFile(appData.saveGameFile, TRUE);
10530                     } else if (appData.autoSaveGames) {
10531                         AutoSaveGame();
10532                     }
10533                     if (*appData.savePositionFile != NULLCHAR) {
10534                         SavePositionToFile(appData.savePositionFile);
10535                     }
10536                 }
10537             }
10538
10539             /* Tell program how game ended in case it is learning */
10540             /* [HGM] Moved this to after saving the PGN, just in case */
10541             /* engine died and we got here through time loss. In that */
10542             /* case we will get a fatal error writing the pipe, which */
10543             /* would otherwise lose us the PGN.                       */
10544             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10545             /* output during GameEnds should never be fatal anymore   */
10546             if (gameMode == MachinePlaysWhite ||
10547                 gameMode == MachinePlaysBlack ||
10548                 gameMode == TwoMachinesPlay ||
10549                 gameMode == IcsPlayingWhite ||
10550                 gameMode == IcsPlayingBlack ||
10551                 gameMode == BeginningOfGame) {
10552                 char buf[MSG_SIZ];
10553                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10554                         resultDetails);
10555                 if (first.pr != NoProc) {
10556                     SendToProgram(buf, &first);
10557                 }
10558                 if (second.pr != NoProc &&
10559                     gameMode == TwoMachinesPlay) {
10560                     SendToProgram(buf, &second);
10561                 }
10562             }
10563         }
10564
10565         if (appData.icsActive) {
10566             if (appData.quietPlay &&
10567                 (gameMode == IcsPlayingWhite ||
10568                  gameMode == IcsPlayingBlack)) {
10569                 SendToICS(ics_prefix);
10570                 SendToICS("set shout 1\n");
10571             }
10572             nextGameMode = IcsIdle;
10573             ics_user_moved = FALSE;
10574             /* clean up premove.  It's ugly when the game has ended and the
10575              * premove highlights are still on the board.
10576              */
10577             if (gotPremove) {
10578               gotPremove = FALSE;
10579               ClearPremoveHighlights();
10580               DrawPosition(FALSE, boards[currentMove]);
10581             }
10582             if (whosays == GE_ICS) {
10583                 switch (result) {
10584                 case WhiteWins:
10585                     if (gameMode == IcsPlayingWhite)
10586                         PlayIcsWinSound();
10587                     else if(gameMode == IcsPlayingBlack)
10588                         PlayIcsLossSound();
10589                     break;
10590                 case BlackWins:
10591                     if (gameMode == IcsPlayingBlack)
10592                         PlayIcsWinSound();
10593                     else if(gameMode == IcsPlayingWhite)
10594                         PlayIcsLossSound();
10595                     break;
10596                 case GameIsDrawn:
10597                     PlayIcsDrawSound();
10598                     break;
10599                 default:
10600                     PlayIcsUnfinishedSound();
10601                 }
10602             }
10603         } else if (gameMode == EditGame ||
10604                    gameMode == PlayFromGameFile ||
10605                    gameMode == AnalyzeMode ||
10606                    gameMode == AnalyzeFile) {
10607             nextGameMode = gameMode;
10608         } else {
10609             nextGameMode = EndOfGame;
10610         }
10611         pausing = FALSE;
10612         ModeHighlight();
10613     } else {
10614         nextGameMode = gameMode;
10615     }
10616
10617     if (appData.noChessProgram) {
10618         gameMode = nextGameMode;
10619         ModeHighlight();
10620         endingGame = 0; /* [HGM] crash */
10621         return;
10622     }
10623
10624     if (first.reuse) {
10625         /* Put first chess program into idle state */
10626         if (first.pr != NoProc &&
10627             (gameMode == MachinePlaysWhite ||
10628              gameMode == MachinePlaysBlack ||
10629              gameMode == TwoMachinesPlay ||
10630              gameMode == IcsPlayingWhite ||
10631              gameMode == IcsPlayingBlack ||
10632              gameMode == BeginningOfGame)) {
10633             SendToProgram("force\n", &first);
10634             if (first.usePing) {
10635               char buf[MSG_SIZ];
10636               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10637               SendToProgram(buf, &first);
10638             }
10639         }
10640     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10641         /* Kill off first chess program */
10642         if (first.isr != NULL)
10643           RemoveInputSource(first.isr);
10644         first.isr = NULL;
10645
10646         if (first.pr != NoProc) {
10647             ExitAnalyzeMode();
10648             DoSleep( appData.delayBeforeQuit );
10649             SendToProgram("quit\n", &first);
10650             DoSleep( appData.delayAfterQuit );
10651             DestroyChildProcess(first.pr, first.useSigterm);
10652         }
10653         first.pr = NoProc;
10654     }
10655     if (second.reuse) {
10656         /* Put second chess program into idle state */
10657         if (second.pr != NoProc &&
10658             gameMode == TwoMachinesPlay) {
10659             SendToProgram("force\n", &second);
10660             if (second.usePing) {
10661               char buf[MSG_SIZ];
10662               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10663               SendToProgram(buf, &second);
10664             }
10665         }
10666     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10667         /* Kill off second chess program */
10668         if (second.isr != NULL)
10669           RemoveInputSource(second.isr);
10670         second.isr = NULL;
10671
10672         if (second.pr != NoProc) {
10673             DoSleep( appData.delayBeforeQuit );
10674             SendToProgram("quit\n", &second);
10675             DoSleep( appData.delayAfterQuit );
10676             DestroyChildProcess(second.pr, second.useSigterm);
10677         }
10678         second.pr = NoProc;
10679     }
10680
10681     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10682         char resChar = '=';
10683         switch (result) {
10684         case WhiteWins:
10685           resChar = '+';
10686           if (first.twoMachinesColor[0] == 'w') {
10687             first.matchWins++;
10688           } else {
10689             second.matchWins++;
10690           }
10691           break;
10692         case BlackWins:
10693           resChar = '-';
10694           if (first.twoMachinesColor[0] == 'b') {
10695             first.matchWins++;
10696           } else {
10697             second.matchWins++;
10698           }
10699           break;
10700         case GameUnfinished:
10701           resChar = ' ';
10702         default:
10703           break;
10704         }
10705
10706         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10707         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10708             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10709             ReserveGame(nextGame, resChar); // sets nextGame
10710             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10711             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10712         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10713
10714         if (nextGame <= appData.matchGames && !abortMatch) {
10715             gameMode = nextGameMode;
10716             matchGame = nextGame; // this will be overruled in tourney mode!
10717             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10718             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10719             endingGame = 0; /* [HGM] crash */
10720             return;
10721         } else {
10722             gameMode = nextGameMode;
10723             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10724                      first.tidy, second.tidy,
10725                      first.matchWins, second.matchWins,
10726                      appData.matchGames - (first.matchWins + second.matchWins));
10727             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10728             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10729             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10730             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10731                 first.twoMachinesColor = "black\n";
10732                 second.twoMachinesColor = "white\n";
10733             } else {
10734                 first.twoMachinesColor = "white\n";
10735                 second.twoMachinesColor = "black\n";
10736             }
10737         }
10738     }
10739     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10740         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10741       ExitAnalyzeMode();
10742     gameMode = nextGameMode;
10743     ModeHighlight();
10744     endingGame = 0;  /* [HGM] crash */
10745     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10746         if(matchMode == TRUE) { // match through command line: exit with or without popup
10747             if(ranking) {
10748                 ToNrEvent(forwardMostMove);
10749                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10750                 else ExitEvent(0);
10751             } else DisplayFatalError(buf, 0, 0);
10752         } else { // match through menu; just stop, with or without popup
10753             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10754             ModeHighlight();
10755             if(ranking){
10756                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10757             } else DisplayNote(buf);
10758       }
10759       if(ranking) free(ranking);
10760     }
10761 }
10762
10763 /* Assumes program was just initialized (initString sent).
10764    Leaves program in force mode. */
10765 void
10766 FeedMovesToProgram (ChessProgramState *cps, int upto)
10767 {
10768     int i;
10769
10770     if (appData.debugMode)
10771       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10772               startedFromSetupPosition ? "position and " : "",
10773               backwardMostMove, upto, cps->which);
10774     if(currentlyInitializedVariant != gameInfo.variant) {
10775       char buf[MSG_SIZ];
10776         // [HGM] variantswitch: make engine aware of new variant
10777         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10778                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10779         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10780         SendToProgram(buf, cps);
10781         currentlyInitializedVariant = gameInfo.variant;
10782     }
10783     SendToProgram("force\n", cps);
10784     if (startedFromSetupPosition) {
10785         SendBoard(cps, backwardMostMove);
10786     if (appData.debugMode) {
10787         fprintf(debugFP, "feedMoves\n");
10788     }
10789     }
10790     for (i = backwardMostMove; i < upto; i++) {
10791         SendMoveToProgram(i, cps);
10792     }
10793 }
10794
10795
10796 int
10797 ResurrectChessProgram ()
10798 {
10799      /* The chess program may have exited.
10800         If so, restart it and feed it all the moves made so far. */
10801     static int doInit = 0;
10802
10803     if (appData.noChessProgram) return 1;
10804
10805     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10806         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10807         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10808         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10809     } else {
10810         if (first.pr != NoProc) return 1;
10811         StartChessProgram(&first);
10812     }
10813     InitChessProgram(&first, FALSE);
10814     FeedMovesToProgram(&first, currentMove);
10815
10816     if (!first.sendTime) {
10817         /* can't tell gnuchess what its clock should read,
10818            so we bow to its notion. */
10819         ResetClocks();
10820         timeRemaining[0][currentMove] = whiteTimeRemaining;
10821         timeRemaining[1][currentMove] = blackTimeRemaining;
10822     }
10823
10824     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10825                 appData.icsEngineAnalyze) && first.analysisSupport) {
10826       SendToProgram("analyze\n", &first);
10827       first.analyzing = TRUE;
10828     }
10829     return 1;
10830 }
10831
10832 /*
10833  * Button procedures
10834  */
10835 void
10836 Reset (int redraw, int init)
10837 {
10838     int i;
10839
10840     if (appData.debugMode) {
10841         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10842                 redraw, init, gameMode);
10843     }
10844     CleanupTail(); // [HGM] vari: delete any stored variations
10845     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10846     pausing = pauseExamInvalid = FALSE;
10847     startedFromSetupPosition = blackPlaysFirst = FALSE;
10848     firstMove = TRUE;
10849     whiteFlag = blackFlag = FALSE;
10850     userOfferedDraw = FALSE;
10851     hintRequested = bookRequested = FALSE;
10852     first.maybeThinking = FALSE;
10853     second.maybeThinking = FALSE;
10854     first.bookSuspend = FALSE; // [HGM] book
10855     second.bookSuspend = FALSE;
10856     thinkOutput[0] = NULLCHAR;
10857     lastHint[0] = NULLCHAR;
10858     ClearGameInfo(&gameInfo);
10859     gameInfo.variant = StringToVariant(appData.variant);
10860     ics_user_moved = ics_clock_paused = FALSE;
10861     ics_getting_history = H_FALSE;
10862     ics_gamenum = -1;
10863     white_holding[0] = black_holding[0] = NULLCHAR;
10864     ClearProgramStats();
10865     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10866
10867     ResetFrontEnd();
10868     ClearHighlights();
10869     flipView = appData.flipView;
10870     ClearPremoveHighlights();
10871     gotPremove = FALSE;
10872     alarmSounded = FALSE;
10873
10874     GameEnds(EndOfFile, NULL, GE_PLAYER);
10875     if(appData.serverMovesName != NULL) {
10876         /* [HGM] prepare to make moves file for broadcasting */
10877         clock_t t = clock();
10878         if(serverMoves != NULL) fclose(serverMoves);
10879         serverMoves = fopen(appData.serverMovesName, "r");
10880         if(serverMoves != NULL) {
10881             fclose(serverMoves);
10882             /* delay 15 sec before overwriting, so all clients can see end */
10883             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10884         }
10885         serverMoves = fopen(appData.serverMovesName, "w");
10886     }
10887
10888     ExitAnalyzeMode();
10889     gameMode = BeginningOfGame;
10890     ModeHighlight();
10891     if(appData.icsActive) gameInfo.variant = VariantNormal;
10892     currentMove = forwardMostMove = backwardMostMove = 0;
10893     MarkTargetSquares(1);
10894     InitPosition(redraw);
10895     for (i = 0; i < MAX_MOVES; i++) {
10896         if (commentList[i] != NULL) {
10897             free(commentList[i]);
10898             commentList[i] = NULL;
10899         }
10900     }
10901     ResetClocks();
10902     timeRemaining[0][0] = whiteTimeRemaining;
10903     timeRemaining[1][0] = blackTimeRemaining;
10904
10905     if (first.pr == NoProc) {
10906         StartChessProgram(&first);
10907     }
10908     if (init) {
10909             InitChessProgram(&first, startedFromSetupPosition);
10910     }
10911     DisplayTitle("");
10912     DisplayMessage("", "");
10913     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10914     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10915     ClearMap();        // [HGM] exclude: invalidate map
10916 }
10917
10918 void
10919 AutoPlayGameLoop ()
10920 {
10921     for (;;) {
10922         if (!AutoPlayOneMove())
10923           return;
10924         if (matchMode || appData.timeDelay == 0)
10925           continue;
10926         if (appData.timeDelay < 0)
10927           return;
10928         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
10929         break;
10930     }
10931 }
10932
10933
10934 int
10935 AutoPlayOneMove ()
10936 {
10937     int fromX, fromY, toX, toY;
10938
10939     if (appData.debugMode) {
10940       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10941     }
10942
10943     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10944       return FALSE;
10945
10946     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10947       pvInfoList[currentMove].depth = programStats.depth;
10948       pvInfoList[currentMove].score = programStats.score;
10949       pvInfoList[currentMove].time  = 0;
10950       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10951     }
10952
10953     if (currentMove >= forwardMostMove) {
10954       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10955 //      gameMode = EndOfGame;
10956 //      ModeHighlight();
10957
10958       /* [AS] Clear current move marker at the end of a game */
10959       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10960
10961       return FALSE;
10962     }
10963
10964     toX = moveList[currentMove][2] - AAA;
10965     toY = moveList[currentMove][3] - ONE;
10966
10967     if (moveList[currentMove][1] == '@') {
10968         if (appData.highlightLastMove) {
10969             SetHighlights(-1, -1, toX, toY);
10970         }
10971     } else {
10972         fromX = moveList[currentMove][0] - AAA;
10973         fromY = moveList[currentMove][1] - ONE;
10974
10975         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10976
10977         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10978
10979         if (appData.highlightLastMove) {
10980             SetHighlights(fromX, fromY, toX, toY);
10981         }
10982     }
10983     DisplayMove(currentMove);
10984     SendMoveToProgram(currentMove++, &first);
10985     DisplayBothClocks();
10986     DrawPosition(FALSE, boards[currentMove]);
10987     // [HGM] PV info: always display, routine tests if empty
10988     DisplayComment(currentMove - 1, commentList[currentMove]);
10989     return TRUE;
10990 }
10991
10992
10993 int
10994 LoadGameOneMove (ChessMove readAhead)
10995 {
10996     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10997     char promoChar = NULLCHAR;
10998     ChessMove moveType;
10999     char move[MSG_SIZ];
11000     char *p, *q;
11001
11002     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11003         gameMode != AnalyzeMode && gameMode != Training) {
11004         gameFileFP = NULL;
11005         return FALSE;
11006     }
11007
11008     yyboardindex = forwardMostMove;
11009     if (readAhead != EndOfFile) {
11010       moveType = readAhead;
11011     } else {
11012       if (gameFileFP == NULL)
11013           return FALSE;
11014       moveType = (ChessMove) Myylex();
11015     }
11016
11017     done = FALSE;
11018     switch (moveType) {
11019       case Comment:
11020         if (appData.debugMode)
11021           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11022         p = yy_text;
11023
11024         /* append the comment but don't display it */
11025         AppendComment(currentMove, p, FALSE);
11026         return TRUE;
11027
11028       case WhiteCapturesEnPassant:
11029       case BlackCapturesEnPassant:
11030       case WhitePromotion:
11031       case BlackPromotion:
11032       case WhiteNonPromotion:
11033       case BlackNonPromotion:
11034       case NormalMove:
11035       case WhiteKingSideCastle:
11036       case WhiteQueenSideCastle:
11037       case BlackKingSideCastle:
11038       case BlackQueenSideCastle:
11039       case WhiteKingSideCastleWild:
11040       case WhiteQueenSideCastleWild:
11041       case BlackKingSideCastleWild:
11042       case BlackQueenSideCastleWild:
11043       /* PUSH Fabien */
11044       case WhiteHSideCastleFR:
11045       case WhiteASideCastleFR:
11046       case BlackHSideCastleFR:
11047       case BlackASideCastleFR:
11048       /* POP Fabien */
11049         if (appData.debugMode)
11050           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11051         fromX = currentMoveString[0] - AAA;
11052         fromY = currentMoveString[1] - ONE;
11053         toX = currentMoveString[2] - AAA;
11054         toY = currentMoveString[3] - ONE;
11055         promoChar = currentMoveString[4];
11056         break;
11057
11058       case WhiteDrop:
11059       case BlackDrop:
11060         if (appData.debugMode)
11061           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11062         fromX = moveType == WhiteDrop ?
11063           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11064         (int) CharToPiece(ToLower(currentMoveString[0]));
11065         fromY = DROP_RANK;
11066         toX = currentMoveString[2] - AAA;
11067         toY = currentMoveString[3] - ONE;
11068         break;
11069
11070       case WhiteWins:
11071       case BlackWins:
11072       case GameIsDrawn:
11073       case GameUnfinished:
11074         if (appData.debugMode)
11075           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11076         p = strchr(yy_text, '{');
11077         if (p == NULL) p = strchr(yy_text, '(');
11078         if (p == NULL) {
11079             p = yy_text;
11080             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11081         } else {
11082             q = strchr(p, *p == '{' ? '}' : ')');
11083             if (q != NULL) *q = NULLCHAR;
11084             p++;
11085         }
11086         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11087         GameEnds(moveType, p, GE_FILE);
11088         done = TRUE;
11089         if (cmailMsgLoaded) {
11090             ClearHighlights();
11091             flipView = WhiteOnMove(currentMove);
11092             if (moveType == GameUnfinished) flipView = !flipView;
11093             if (appData.debugMode)
11094               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11095         }
11096         break;
11097
11098       case EndOfFile:
11099         if (appData.debugMode)
11100           fprintf(debugFP, "Parser hit end of file\n");
11101         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11102           case MT_NONE:
11103           case MT_CHECK:
11104             break;
11105           case MT_CHECKMATE:
11106           case MT_STAINMATE:
11107             if (WhiteOnMove(currentMove)) {
11108                 GameEnds(BlackWins, "Black mates", GE_FILE);
11109             } else {
11110                 GameEnds(WhiteWins, "White mates", GE_FILE);
11111             }
11112             break;
11113           case MT_STALEMATE:
11114             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11115             break;
11116         }
11117         done = TRUE;
11118         break;
11119
11120       case MoveNumberOne:
11121         if (lastLoadGameStart == GNUChessGame) {
11122             /* GNUChessGames have numbers, but they aren't move numbers */
11123             if (appData.debugMode)
11124               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11125                       yy_text, (int) moveType);
11126             return LoadGameOneMove(EndOfFile); /* tail recursion */
11127         }
11128         /* else fall thru */
11129
11130       case XBoardGame:
11131       case GNUChessGame:
11132       case PGNTag:
11133         /* Reached start of next game in file */
11134         if (appData.debugMode)
11135           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11136         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11137           case MT_NONE:
11138           case MT_CHECK:
11139             break;
11140           case MT_CHECKMATE:
11141           case MT_STAINMATE:
11142             if (WhiteOnMove(currentMove)) {
11143                 GameEnds(BlackWins, "Black mates", GE_FILE);
11144             } else {
11145                 GameEnds(WhiteWins, "White mates", GE_FILE);
11146             }
11147             break;
11148           case MT_STALEMATE:
11149             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11150             break;
11151         }
11152         done = TRUE;
11153         break;
11154
11155       case PositionDiagram:     /* should not happen; ignore */
11156       case ElapsedTime:         /* ignore */
11157       case NAG:                 /* ignore */
11158         if (appData.debugMode)
11159           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11160                   yy_text, (int) moveType);
11161         return LoadGameOneMove(EndOfFile); /* tail recursion */
11162
11163       case IllegalMove:
11164         if (appData.testLegality) {
11165             if (appData.debugMode)
11166               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11167             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11168                     (forwardMostMove / 2) + 1,
11169                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11170             DisplayError(move, 0);
11171             done = TRUE;
11172         } else {
11173             if (appData.debugMode)
11174               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11175                       yy_text, currentMoveString);
11176             fromX = currentMoveString[0] - AAA;
11177             fromY = currentMoveString[1] - ONE;
11178             toX = currentMoveString[2] - AAA;
11179             toY = currentMoveString[3] - ONE;
11180             promoChar = currentMoveString[4];
11181         }
11182         break;
11183
11184       case AmbiguousMove:
11185         if (appData.debugMode)
11186           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11187         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11188                 (forwardMostMove / 2) + 1,
11189                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11190         DisplayError(move, 0);
11191         done = TRUE;
11192         break;
11193
11194       default:
11195       case ImpossibleMove:
11196         if (appData.debugMode)
11197           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11198         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11199                 (forwardMostMove / 2) + 1,
11200                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11201         DisplayError(move, 0);
11202         done = TRUE;
11203         break;
11204     }
11205
11206     if (done) {
11207         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11208             DrawPosition(FALSE, boards[currentMove]);
11209             DisplayBothClocks();
11210             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11211               DisplayComment(currentMove - 1, commentList[currentMove]);
11212         }
11213         (void) StopLoadGameTimer();
11214         gameFileFP = NULL;
11215         cmailOldMove = forwardMostMove;
11216         return FALSE;
11217     } else {
11218         /* currentMoveString is set as a side-effect of yylex */
11219
11220         thinkOutput[0] = NULLCHAR;
11221         MakeMove(fromX, fromY, toX, toY, promoChar);
11222         currentMove = forwardMostMove;
11223         return TRUE;
11224     }
11225 }
11226
11227 /* Load the nth game from the given file */
11228 int
11229 LoadGameFromFile (char *filename, int n, char *title, int useList)
11230 {
11231     FILE *f;
11232     char buf[MSG_SIZ];
11233
11234     if (strcmp(filename, "-") == 0) {
11235         f = stdin;
11236         title = "stdin";
11237     } else {
11238         f = fopen(filename, "rb");
11239         if (f == NULL) {
11240           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11241             DisplayError(buf, errno);
11242             return FALSE;
11243         }
11244     }
11245     if (fseek(f, 0, 0) == -1) {
11246         /* f is not seekable; probably a pipe */
11247         useList = FALSE;
11248     }
11249     if (useList && n == 0) {
11250         int error = GameListBuild(f);
11251         if (error) {
11252             DisplayError(_("Cannot build game list"), error);
11253         } else if (!ListEmpty(&gameList) &&
11254                    ((ListGame *) gameList.tailPred)->number > 1) {
11255             GameListPopUp(f, title);
11256             return TRUE;
11257         }
11258         GameListDestroy();
11259         n = 1;
11260     }
11261     if (n == 0) n = 1;
11262     return LoadGame(f, n, title, FALSE);
11263 }
11264
11265
11266 void
11267 MakeRegisteredMove ()
11268 {
11269     int fromX, fromY, toX, toY;
11270     char promoChar;
11271     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11272         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11273           case CMAIL_MOVE:
11274           case CMAIL_DRAW:
11275             if (appData.debugMode)
11276               fprintf(debugFP, "Restoring %s for game %d\n",
11277                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11278
11279             thinkOutput[0] = NULLCHAR;
11280             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11281             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11282             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11283             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11284             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11285             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11286             MakeMove(fromX, fromY, toX, toY, promoChar);
11287             ShowMove(fromX, fromY, toX, toY);
11288
11289             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11290               case MT_NONE:
11291               case MT_CHECK:
11292                 break;
11293
11294               case MT_CHECKMATE:
11295               case MT_STAINMATE:
11296                 if (WhiteOnMove(currentMove)) {
11297                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11298                 } else {
11299                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11300                 }
11301                 break;
11302
11303               case MT_STALEMATE:
11304                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11305                 break;
11306             }
11307
11308             break;
11309
11310           case CMAIL_RESIGN:
11311             if (WhiteOnMove(currentMove)) {
11312                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11313             } else {
11314                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11315             }
11316             break;
11317
11318           case CMAIL_ACCEPT:
11319             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11320             break;
11321
11322           default:
11323             break;
11324         }
11325     }
11326
11327     return;
11328 }
11329
11330 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11331 int
11332 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11333 {
11334     int retVal;
11335
11336     if (gameNumber > nCmailGames) {
11337         DisplayError(_("No more games in this message"), 0);
11338         return FALSE;
11339     }
11340     if (f == lastLoadGameFP) {
11341         int offset = gameNumber - lastLoadGameNumber;
11342         if (offset == 0) {
11343             cmailMsg[0] = NULLCHAR;
11344             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11345                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11346                 nCmailMovesRegistered--;
11347             }
11348             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11349             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11350                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11351             }
11352         } else {
11353             if (! RegisterMove()) return FALSE;
11354         }
11355     }
11356
11357     retVal = LoadGame(f, gameNumber, title, useList);
11358
11359     /* Make move registered during previous look at this game, if any */
11360     MakeRegisteredMove();
11361
11362     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11363         commentList[currentMove]
11364           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11365         DisplayComment(currentMove - 1, commentList[currentMove]);
11366     }
11367
11368     return retVal;
11369 }
11370
11371 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11372 int
11373 ReloadGame (int offset)
11374 {
11375     int gameNumber = lastLoadGameNumber + offset;
11376     if (lastLoadGameFP == NULL) {
11377         DisplayError(_("No game has been loaded yet"), 0);
11378         return FALSE;
11379     }
11380     if (gameNumber <= 0) {
11381         DisplayError(_("Can't back up any further"), 0);
11382         return FALSE;
11383     }
11384     if (cmailMsgLoaded) {
11385         return CmailLoadGame(lastLoadGameFP, gameNumber,
11386                              lastLoadGameTitle, lastLoadGameUseList);
11387     } else {
11388         return LoadGame(lastLoadGameFP, gameNumber,
11389                         lastLoadGameTitle, lastLoadGameUseList);
11390     }
11391 }
11392
11393 int keys[EmptySquare+1];
11394
11395 int
11396 PositionMatches (Board b1, Board b2)
11397 {
11398     int r, f, sum=0;
11399     switch(appData.searchMode) {
11400         case 1: return CompareWithRights(b1, b2);
11401         case 2:
11402             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11403                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11404             }
11405             return TRUE;
11406         case 3:
11407             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11408               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11409                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11410             }
11411             return sum==0;
11412         case 4:
11413             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11414                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11415             }
11416             return sum==0;
11417     }
11418     return TRUE;
11419 }
11420
11421 #define Q_PROMO  4
11422 #define Q_EP     3
11423 #define Q_BCASTL 2
11424 #define Q_WCASTL 1
11425
11426 int pieceList[256], quickBoard[256];
11427 ChessSquare pieceType[256] = { EmptySquare };
11428 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11429 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11430 int soughtTotal, turn;
11431 Boolean epOK, flipSearch;
11432
11433 typedef struct {
11434     unsigned char piece, to;
11435 } Move;
11436
11437 #define DSIZE (250000)
11438
11439 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11440 Move *moveDatabase = initialSpace;
11441 unsigned int movePtr, dataSize = DSIZE;
11442
11443 int
11444 MakePieceList (Board board, int *counts)
11445 {
11446     int r, f, n=Q_PROMO, total=0;
11447     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11448     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11449         int sq = f + (r<<4);
11450         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11451             quickBoard[sq] = ++n;
11452             pieceList[n] = sq;
11453             pieceType[n] = board[r][f];
11454             counts[board[r][f]]++;
11455             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11456             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11457             total++;
11458         }
11459     }
11460     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11461     return total;
11462 }
11463
11464 void
11465 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11466 {
11467     int sq = fromX + (fromY<<4);
11468     int piece = quickBoard[sq];
11469     quickBoard[sq] = 0;
11470     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11471     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11472         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11473         moveDatabase[movePtr++].piece = Q_WCASTL;
11474         quickBoard[sq] = piece;
11475         piece = quickBoard[from]; quickBoard[from] = 0;
11476         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11477     } else
11478     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11479         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11480         moveDatabase[movePtr++].piece = Q_BCASTL;
11481         quickBoard[sq] = piece;
11482         piece = quickBoard[from]; quickBoard[from] = 0;
11483         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11484     } else
11485     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11486         quickBoard[(fromY<<4)+toX] = 0;
11487         moveDatabase[movePtr].piece = Q_EP;
11488         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11489         moveDatabase[movePtr].to = sq;
11490     } else
11491     if(promoPiece != pieceType[piece]) {
11492         moveDatabase[movePtr++].piece = Q_PROMO;
11493         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11494     }
11495     moveDatabase[movePtr].piece = piece;
11496     quickBoard[sq] = piece;
11497     movePtr++;
11498 }
11499
11500 int
11501 PackGame (Board board)
11502 {
11503     Move *newSpace = NULL;
11504     moveDatabase[movePtr].piece = 0; // terminate previous game
11505     if(movePtr > dataSize) {
11506         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11507         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11508         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11509         if(newSpace) {
11510             int i;
11511             Move *p = moveDatabase, *q = newSpace;
11512             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11513             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11514             moveDatabase = newSpace;
11515         } else { // calloc failed, we must be out of memory. Too bad...
11516             dataSize = 0; // prevent calloc events for all subsequent games
11517             return 0;     // and signal this one isn't cached
11518         }
11519     }
11520     movePtr++;
11521     MakePieceList(board, counts);
11522     return movePtr;
11523 }
11524
11525 int
11526 QuickCompare (Board board, int *minCounts, int *maxCounts)
11527 {   // compare according to search mode
11528     int r, f;
11529     switch(appData.searchMode)
11530     {
11531       case 1: // exact position match
11532         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11533         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11534             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11535         }
11536         break;
11537       case 2: // can have extra material on empty squares
11538         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11539             if(board[r][f] == EmptySquare) continue;
11540             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11541         }
11542         break;
11543       case 3: // material with exact Pawn structure
11544         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11545             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11546             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11547         } // fall through to material comparison
11548       case 4: // exact material
11549         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11550         break;
11551       case 6: // material range with given imbalance
11552         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11553         // fall through to range comparison
11554       case 5: // material range
11555         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11556     }
11557     return TRUE;
11558 }
11559
11560 int
11561 QuickScan (Board board, Move *move)
11562 {   // reconstruct game,and compare all positions in it
11563     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11564     do {
11565         int piece = move->piece;
11566         int to = move->to, from = pieceList[piece];
11567         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11568           if(!piece) return -1;
11569           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11570             piece = (++move)->piece;
11571             from = pieceList[piece];
11572             counts[pieceType[piece]]--;
11573             pieceType[piece] = (ChessSquare) move->to;
11574             counts[move->to]++;
11575           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11576             counts[pieceType[quickBoard[to]]]--;
11577             quickBoard[to] = 0; total--;
11578             move++;
11579             continue;
11580           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11581             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11582             from  = pieceList[piece]; // so this must be King
11583             quickBoard[from] = 0;
11584             pieceList[piece] = to;
11585             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11586             quickBoard[from] = 0; // rook
11587             quickBoard[to] = piece;
11588             to = move->to; piece = move->piece;
11589             goto aftercastle;
11590           }
11591         }
11592         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11593         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11594         quickBoard[from] = 0;
11595       aftercastle:
11596         quickBoard[to] = piece;
11597         pieceList[piece] = to;
11598         cnt++; turn ^= 3;
11599         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11600            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11601            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11602                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11603           ) {
11604             static int lastCounts[EmptySquare+1];
11605             int i;
11606             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11607             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11608         } else stretch = 0;
11609         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11610         move++;
11611     } while(1);
11612 }
11613
11614 void
11615 InitSearch ()
11616 {
11617     int r, f;
11618     flipSearch = FALSE;
11619     CopyBoard(soughtBoard, boards[currentMove]);
11620     soughtTotal = MakePieceList(soughtBoard, maxSought);
11621     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11622     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11623     CopyBoard(reverseBoard, boards[currentMove]);
11624     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11625         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11626         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11627         reverseBoard[r][f] = piece;
11628     }
11629     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11630     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11631     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11632                  || (boards[currentMove][CASTLING][2] == NoRights || 
11633                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11634                  && (boards[currentMove][CASTLING][5] == NoRights || 
11635                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11636       ) {
11637         flipSearch = TRUE;
11638         CopyBoard(flipBoard, soughtBoard);
11639         CopyBoard(rotateBoard, reverseBoard);
11640         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11641             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11642             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11643         }
11644     }
11645     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11646     if(appData.searchMode >= 5) {
11647         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11648         MakePieceList(soughtBoard, minSought);
11649         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11650     }
11651     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11652         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11653 }
11654
11655 GameInfo dummyInfo;
11656
11657 int
11658 GameContainsPosition (FILE *f, ListGame *lg)
11659 {
11660     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11661     int fromX, fromY, toX, toY;
11662     char promoChar;
11663     static int initDone=FALSE;
11664
11665     // weed out games based on numerical tag comparison
11666     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11667     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11668     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11669     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11670     if(!initDone) {
11671         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11672         initDone = TRUE;
11673     }
11674     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11675     else CopyBoard(boards[scratch], initialPosition); // default start position
11676     if(lg->moves) {
11677         turn = btm + 1;
11678         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11679         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11680     }
11681     if(btm) plyNr++;
11682     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11683     fseek(f, lg->offset, 0);
11684     yynewfile(f);
11685     while(1) {
11686         yyboardindex = scratch;
11687         quickFlag = plyNr+1;
11688         next = Myylex();
11689         quickFlag = 0;
11690         switch(next) {
11691             case PGNTag:
11692                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11693             default:
11694                 continue;
11695
11696             case XBoardGame:
11697             case GNUChessGame:
11698                 if(plyNr) return -1; // after we have seen moves, this is for new game
11699               continue;
11700
11701             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11702             case ImpossibleMove:
11703             case WhiteWins: // game ends here with these four
11704             case BlackWins:
11705             case GameIsDrawn:
11706             case GameUnfinished:
11707                 return -1;
11708
11709             case IllegalMove:
11710                 if(appData.testLegality) return -1;
11711             case WhiteCapturesEnPassant:
11712             case BlackCapturesEnPassant:
11713             case WhitePromotion:
11714             case BlackPromotion:
11715             case WhiteNonPromotion:
11716             case BlackNonPromotion:
11717             case NormalMove:
11718             case WhiteKingSideCastle:
11719             case WhiteQueenSideCastle:
11720             case BlackKingSideCastle:
11721             case BlackQueenSideCastle:
11722             case WhiteKingSideCastleWild:
11723             case WhiteQueenSideCastleWild:
11724             case BlackKingSideCastleWild:
11725             case BlackQueenSideCastleWild:
11726             case WhiteHSideCastleFR:
11727             case WhiteASideCastleFR:
11728             case BlackHSideCastleFR:
11729             case BlackASideCastleFR:
11730                 fromX = currentMoveString[0] - AAA;
11731                 fromY = currentMoveString[1] - ONE;
11732                 toX = currentMoveString[2] - AAA;
11733                 toY = currentMoveString[3] - ONE;
11734                 promoChar = currentMoveString[4];
11735                 break;
11736             case WhiteDrop:
11737             case BlackDrop:
11738                 fromX = next == WhiteDrop ?
11739                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11740                   (int) CharToPiece(ToLower(currentMoveString[0]));
11741                 fromY = DROP_RANK;
11742                 toX = currentMoveString[2] - AAA;
11743                 toY = currentMoveString[3] - ONE;
11744                 promoChar = 0;
11745                 break;
11746         }
11747         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11748         plyNr++;
11749         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11750         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11751         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11752         if(appData.findMirror) {
11753             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11754             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11755         }
11756     }
11757 }
11758
11759 /* Load the nth game from open file f */
11760 int
11761 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11762 {
11763     ChessMove cm;
11764     char buf[MSG_SIZ];
11765     int gn = gameNumber;
11766     ListGame *lg = NULL;
11767     int numPGNTags = 0;
11768     int err, pos = -1;
11769     GameMode oldGameMode;
11770     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11771
11772     if (appData.debugMode)
11773         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11774
11775     if (gameMode == Training )
11776         SetTrainingModeOff();
11777
11778     oldGameMode = gameMode;
11779     if (gameMode != BeginningOfGame) {
11780       Reset(FALSE, TRUE);
11781     }
11782
11783     gameFileFP = f;
11784     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11785         fclose(lastLoadGameFP);
11786     }
11787
11788     if (useList) {
11789         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11790
11791         if (lg) {
11792             fseek(f, lg->offset, 0);
11793             GameListHighlight(gameNumber);
11794             pos = lg->position;
11795             gn = 1;
11796         }
11797         else {
11798             DisplayError(_("Game number out of range"), 0);
11799             return FALSE;
11800         }
11801     } else {
11802         GameListDestroy();
11803         if (fseek(f, 0, 0) == -1) {
11804             if (f == lastLoadGameFP ?
11805                 gameNumber == lastLoadGameNumber + 1 :
11806                 gameNumber == 1) {
11807                 gn = 1;
11808             } else {
11809                 DisplayError(_("Can't seek on game file"), 0);
11810                 return FALSE;
11811             }
11812         }
11813     }
11814     lastLoadGameFP = f;
11815     lastLoadGameNumber = gameNumber;
11816     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11817     lastLoadGameUseList = useList;
11818
11819     yynewfile(f);
11820
11821     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11822       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11823                 lg->gameInfo.black);
11824             DisplayTitle(buf);
11825     } else if (*title != NULLCHAR) {
11826         if (gameNumber > 1) {
11827           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11828             DisplayTitle(buf);
11829         } else {
11830             DisplayTitle(title);
11831         }
11832     }
11833
11834     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11835         gameMode = PlayFromGameFile;
11836         ModeHighlight();
11837     }
11838
11839     currentMove = forwardMostMove = backwardMostMove = 0;
11840     CopyBoard(boards[0], initialPosition);
11841     StopClocks();
11842
11843     /*
11844      * Skip the first gn-1 games in the file.
11845      * Also skip over anything that precedes an identifiable
11846      * start of game marker, to avoid being confused by
11847      * garbage at the start of the file.  Currently
11848      * recognized start of game markers are the move number "1",
11849      * the pattern "gnuchess .* game", the pattern
11850      * "^[#;%] [^ ]* game file", and a PGN tag block.
11851      * A game that starts with one of the latter two patterns
11852      * will also have a move number 1, possibly
11853      * following a position diagram.
11854      * 5-4-02: Let's try being more lenient and allowing a game to
11855      * start with an unnumbered move.  Does that break anything?
11856      */
11857     cm = lastLoadGameStart = EndOfFile;
11858     while (gn > 0) {
11859         yyboardindex = forwardMostMove;
11860         cm = (ChessMove) Myylex();
11861         switch (cm) {
11862           case EndOfFile:
11863             if (cmailMsgLoaded) {
11864                 nCmailGames = CMAIL_MAX_GAMES - gn;
11865             } else {
11866                 Reset(TRUE, TRUE);
11867                 DisplayError(_("Game not found in file"), 0);
11868             }
11869             return FALSE;
11870
11871           case GNUChessGame:
11872           case XBoardGame:
11873             gn--;
11874             lastLoadGameStart = cm;
11875             break;
11876
11877           case MoveNumberOne:
11878             switch (lastLoadGameStart) {
11879               case GNUChessGame:
11880               case XBoardGame:
11881               case PGNTag:
11882                 break;
11883               case MoveNumberOne:
11884               case EndOfFile:
11885                 gn--;           /* count this game */
11886                 lastLoadGameStart = cm;
11887                 break;
11888               default:
11889                 /* impossible */
11890                 break;
11891             }
11892             break;
11893
11894           case PGNTag:
11895             switch (lastLoadGameStart) {
11896               case GNUChessGame:
11897               case PGNTag:
11898               case MoveNumberOne:
11899               case EndOfFile:
11900                 gn--;           /* count this game */
11901                 lastLoadGameStart = cm;
11902                 break;
11903               case XBoardGame:
11904                 lastLoadGameStart = cm; /* game counted already */
11905                 break;
11906               default:
11907                 /* impossible */
11908                 break;
11909             }
11910             if (gn > 0) {
11911                 do {
11912                     yyboardindex = forwardMostMove;
11913                     cm = (ChessMove) Myylex();
11914                 } while (cm == PGNTag || cm == Comment);
11915             }
11916             break;
11917
11918           case WhiteWins:
11919           case BlackWins:
11920           case GameIsDrawn:
11921             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11922                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11923                     != CMAIL_OLD_RESULT) {
11924                     nCmailResults ++ ;
11925                     cmailResult[  CMAIL_MAX_GAMES
11926                                 - gn - 1] = CMAIL_OLD_RESULT;
11927                 }
11928             }
11929             break;
11930
11931           case NormalMove:
11932             /* Only a NormalMove can be at the start of a game
11933              * without a position diagram. */
11934             if (lastLoadGameStart == EndOfFile ) {
11935               gn--;
11936               lastLoadGameStart = MoveNumberOne;
11937             }
11938             break;
11939
11940           default:
11941             break;
11942         }
11943     }
11944
11945     if (appData.debugMode)
11946       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11947
11948     if (cm == XBoardGame) {
11949         /* Skip any header junk before position diagram and/or move 1 */
11950         for (;;) {
11951             yyboardindex = forwardMostMove;
11952             cm = (ChessMove) Myylex();
11953
11954             if (cm == EndOfFile ||
11955                 cm == GNUChessGame || cm == XBoardGame) {
11956                 /* Empty game; pretend end-of-file and handle later */
11957                 cm = EndOfFile;
11958                 break;
11959             }
11960
11961             if (cm == MoveNumberOne || cm == PositionDiagram ||
11962                 cm == PGNTag || cm == Comment)
11963               break;
11964         }
11965     } else if (cm == GNUChessGame) {
11966         if (gameInfo.event != NULL) {
11967             free(gameInfo.event);
11968         }
11969         gameInfo.event = StrSave(yy_text);
11970     }
11971
11972     startedFromSetupPosition = FALSE;
11973     while (cm == PGNTag) {
11974         if (appData.debugMode)
11975           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11976         err = ParsePGNTag(yy_text, &gameInfo);
11977         if (!err) numPGNTags++;
11978
11979         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11980         if(gameInfo.variant != oldVariant) {
11981             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11982             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11983             InitPosition(TRUE);
11984             oldVariant = gameInfo.variant;
11985             if (appData.debugMode)
11986               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11987         }
11988
11989
11990         if (gameInfo.fen != NULL) {
11991           Board initial_position;
11992           startedFromSetupPosition = TRUE;
11993           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11994             Reset(TRUE, TRUE);
11995             DisplayError(_("Bad FEN position in file"), 0);
11996             return FALSE;
11997           }
11998           CopyBoard(boards[0], initial_position);
11999           if (blackPlaysFirst) {
12000             currentMove = forwardMostMove = backwardMostMove = 1;
12001             CopyBoard(boards[1], initial_position);
12002             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12003             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12004             timeRemaining[0][1] = whiteTimeRemaining;
12005             timeRemaining[1][1] = blackTimeRemaining;
12006             if (commentList[0] != NULL) {
12007               commentList[1] = commentList[0];
12008               commentList[0] = NULL;
12009             }
12010           } else {
12011             currentMove = forwardMostMove = backwardMostMove = 0;
12012           }
12013           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12014           {   int i;
12015               initialRulePlies = FENrulePlies;
12016               for( i=0; i< nrCastlingRights; i++ )
12017                   initialRights[i] = initial_position[CASTLING][i];
12018           }
12019           yyboardindex = forwardMostMove;
12020           free(gameInfo.fen);
12021           gameInfo.fen = NULL;
12022         }
12023
12024         yyboardindex = forwardMostMove;
12025         cm = (ChessMove) Myylex();
12026
12027         /* Handle comments interspersed among the tags */
12028         while (cm == Comment) {
12029             char *p;
12030             if (appData.debugMode)
12031               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12032             p = yy_text;
12033             AppendComment(currentMove, p, FALSE);
12034             yyboardindex = forwardMostMove;
12035             cm = (ChessMove) Myylex();
12036         }
12037     }
12038
12039     /* don't rely on existence of Event tag since if game was
12040      * pasted from clipboard the Event tag may not exist
12041      */
12042     if (numPGNTags > 0){
12043         char *tags;
12044         if (gameInfo.variant == VariantNormal) {
12045           VariantClass v = StringToVariant(gameInfo.event);
12046           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12047           if(v < VariantShogi) gameInfo.variant = v;
12048         }
12049         if (!matchMode) {
12050           if( appData.autoDisplayTags ) {
12051             tags = PGNTags(&gameInfo);
12052             TagsPopUp(tags, CmailMsg());
12053             free(tags);
12054           }
12055         }
12056     } else {
12057         /* Make something up, but don't display it now */
12058         SetGameInfo();
12059         TagsPopDown();
12060     }
12061
12062     if (cm == PositionDiagram) {
12063         int i, j;
12064         char *p;
12065         Board initial_position;
12066
12067         if (appData.debugMode)
12068           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12069
12070         if (!startedFromSetupPosition) {
12071             p = yy_text;
12072             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12073               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12074                 switch (*p) {
12075                   case '{':
12076                   case '[':
12077                   case '-':
12078                   case ' ':
12079                   case '\t':
12080                   case '\n':
12081                   case '\r':
12082                     break;
12083                   default:
12084                     initial_position[i][j++] = CharToPiece(*p);
12085                     break;
12086                 }
12087             while (*p == ' ' || *p == '\t' ||
12088                    *p == '\n' || *p == '\r') p++;
12089
12090             if (strncmp(p, "black", strlen("black"))==0)
12091               blackPlaysFirst = TRUE;
12092             else
12093               blackPlaysFirst = FALSE;
12094             startedFromSetupPosition = TRUE;
12095
12096             CopyBoard(boards[0], initial_position);
12097             if (blackPlaysFirst) {
12098                 currentMove = forwardMostMove = backwardMostMove = 1;
12099                 CopyBoard(boards[1], initial_position);
12100                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12101                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12102                 timeRemaining[0][1] = whiteTimeRemaining;
12103                 timeRemaining[1][1] = blackTimeRemaining;
12104                 if (commentList[0] != NULL) {
12105                     commentList[1] = commentList[0];
12106                     commentList[0] = NULL;
12107                 }
12108             } else {
12109                 currentMove = forwardMostMove = backwardMostMove = 0;
12110             }
12111         }
12112         yyboardindex = forwardMostMove;
12113         cm = (ChessMove) Myylex();
12114     }
12115
12116     if (first.pr == NoProc) {
12117         StartChessProgram(&first);
12118     }
12119     InitChessProgram(&first, FALSE);
12120     SendToProgram("force\n", &first);
12121     if (startedFromSetupPosition) {
12122         SendBoard(&first, forwardMostMove);
12123     if (appData.debugMode) {
12124         fprintf(debugFP, "Load Game\n");
12125     }
12126         DisplayBothClocks();
12127     }
12128
12129     /* [HGM] server: flag to write setup moves in broadcast file as one */
12130     loadFlag = appData.suppressLoadMoves;
12131
12132     while (cm == Comment) {
12133         char *p;
12134         if (appData.debugMode)
12135           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12136         p = yy_text;
12137         AppendComment(currentMove, p, FALSE);
12138         yyboardindex = forwardMostMove;
12139         cm = (ChessMove) Myylex();
12140     }
12141
12142     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12143         cm == WhiteWins || cm == BlackWins ||
12144         cm == GameIsDrawn || cm == GameUnfinished) {
12145         DisplayMessage("", _("No moves in game"));
12146         if (cmailMsgLoaded) {
12147             if (appData.debugMode)
12148               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12149             ClearHighlights();
12150             flipView = FALSE;
12151         }
12152         DrawPosition(FALSE, boards[currentMove]);
12153         DisplayBothClocks();
12154         gameMode = EditGame;
12155         ModeHighlight();
12156         gameFileFP = NULL;
12157         cmailOldMove = 0;
12158         return TRUE;
12159     }
12160
12161     // [HGM] PV info: routine tests if comment empty
12162     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12163         DisplayComment(currentMove - 1, commentList[currentMove]);
12164     }
12165     if (!matchMode && appData.timeDelay != 0)
12166       DrawPosition(FALSE, boards[currentMove]);
12167
12168     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12169       programStats.ok_to_send = 1;
12170     }
12171
12172     /* if the first token after the PGN tags is a move
12173      * and not move number 1, retrieve it from the parser
12174      */
12175     if (cm != MoveNumberOne)
12176         LoadGameOneMove(cm);
12177
12178     /* load the remaining moves from the file */
12179     while (LoadGameOneMove(EndOfFile)) {
12180       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12181       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12182     }
12183
12184     /* rewind to the start of the game */
12185     currentMove = backwardMostMove;
12186
12187     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12188
12189     if (oldGameMode == AnalyzeFile ||
12190         oldGameMode == AnalyzeMode) {
12191       AnalyzeFileEvent();
12192     }
12193
12194     if (!matchMode && pos > 0) {
12195         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12196     } else
12197     if (matchMode || appData.timeDelay == 0) {
12198       ToEndEvent();
12199     } else if (appData.timeDelay > 0) {
12200       AutoPlayGameLoop();
12201     }
12202
12203     if (appData.debugMode)
12204         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12205
12206     loadFlag = 0; /* [HGM] true game starts */
12207     return TRUE;
12208 }
12209
12210 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12211 int
12212 ReloadPosition (int offset)
12213 {
12214     int positionNumber = lastLoadPositionNumber + offset;
12215     if (lastLoadPositionFP == NULL) {
12216         DisplayError(_("No position has been loaded yet"), 0);
12217         return FALSE;
12218     }
12219     if (positionNumber <= 0) {
12220         DisplayError(_("Can't back up any further"), 0);
12221         return FALSE;
12222     }
12223     return LoadPosition(lastLoadPositionFP, positionNumber,
12224                         lastLoadPositionTitle);
12225 }
12226
12227 /* Load the nth position from the given file */
12228 int
12229 LoadPositionFromFile (char *filename, int n, char *title)
12230 {
12231     FILE *f;
12232     char buf[MSG_SIZ];
12233
12234     if (strcmp(filename, "-") == 0) {
12235         return LoadPosition(stdin, n, "stdin");
12236     } else {
12237         f = fopen(filename, "rb");
12238         if (f == NULL) {
12239             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12240             DisplayError(buf, errno);
12241             return FALSE;
12242         } else {
12243             return LoadPosition(f, n, title);
12244         }
12245     }
12246 }
12247
12248 /* Load the nth position from the given open file, and close it */
12249 int
12250 LoadPosition (FILE *f, int positionNumber, char *title)
12251 {
12252     char *p, line[MSG_SIZ];
12253     Board initial_position;
12254     int i, j, fenMode, pn;
12255
12256     if (gameMode == Training )
12257         SetTrainingModeOff();
12258
12259     if (gameMode != BeginningOfGame) {
12260         Reset(FALSE, TRUE);
12261     }
12262     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12263         fclose(lastLoadPositionFP);
12264     }
12265     if (positionNumber == 0) positionNumber = 1;
12266     lastLoadPositionFP = f;
12267     lastLoadPositionNumber = positionNumber;
12268     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12269     if (first.pr == NoProc && !appData.noChessProgram) {
12270       StartChessProgram(&first);
12271       InitChessProgram(&first, FALSE);
12272     }
12273     pn = positionNumber;
12274     if (positionNumber < 0) {
12275         /* Negative position number means to seek to that byte offset */
12276         if (fseek(f, -positionNumber, 0) == -1) {
12277             DisplayError(_("Can't seek on position file"), 0);
12278             return FALSE;
12279         };
12280         pn = 1;
12281     } else {
12282         if (fseek(f, 0, 0) == -1) {
12283             if (f == lastLoadPositionFP ?
12284                 positionNumber == lastLoadPositionNumber + 1 :
12285                 positionNumber == 1) {
12286                 pn = 1;
12287             } else {
12288                 DisplayError(_("Can't seek on position file"), 0);
12289                 return FALSE;
12290             }
12291         }
12292     }
12293     /* See if this file is FEN or old-style xboard */
12294     if (fgets(line, MSG_SIZ, f) == NULL) {
12295         DisplayError(_("Position not found in file"), 0);
12296         return FALSE;
12297     }
12298     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12299     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12300
12301     if (pn >= 2) {
12302         if (fenMode || line[0] == '#') pn--;
12303         while (pn > 0) {
12304             /* skip positions before number pn */
12305             if (fgets(line, MSG_SIZ, f) == NULL) {
12306                 Reset(TRUE, TRUE);
12307                 DisplayError(_("Position not found in file"), 0);
12308                 return FALSE;
12309             }
12310             if (fenMode || line[0] == '#') pn--;
12311         }
12312     }
12313
12314     if (fenMode) {
12315         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12316             DisplayError(_("Bad FEN position in file"), 0);
12317             return FALSE;
12318         }
12319     } else {
12320         (void) fgets(line, MSG_SIZ, f);
12321         (void) fgets(line, MSG_SIZ, f);
12322
12323         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12324             (void) fgets(line, MSG_SIZ, f);
12325             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12326                 if (*p == ' ')
12327                   continue;
12328                 initial_position[i][j++] = CharToPiece(*p);
12329             }
12330         }
12331
12332         blackPlaysFirst = FALSE;
12333         if (!feof(f)) {
12334             (void) fgets(line, MSG_SIZ, f);
12335             if (strncmp(line, "black", strlen("black"))==0)
12336               blackPlaysFirst = TRUE;
12337         }
12338     }
12339     startedFromSetupPosition = TRUE;
12340
12341     CopyBoard(boards[0], initial_position);
12342     if (blackPlaysFirst) {
12343         currentMove = forwardMostMove = backwardMostMove = 1;
12344         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12345         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12346         CopyBoard(boards[1], initial_position);
12347         DisplayMessage("", _("Black to play"));
12348     } else {
12349         currentMove = forwardMostMove = backwardMostMove = 0;
12350         DisplayMessage("", _("White to play"));
12351     }
12352     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12353     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12354         SendToProgram("force\n", &first);
12355         SendBoard(&first, forwardMostMove);
12356     }
12357     if (appData.debugMode) {
12358 int i, j;
12359   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12360   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12361         fprintf(debugFP, "Load Position\n");
12362     }
12363
12364     if (positionNumber > 1) {
12365       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12366         DisplayTitle(line);
12367     } else {
12368         DisplayTitle(title);
12369     }
12370     gameMode = EditGame;
12371     ModeHighlight();
12372     ResetClocks();
12373     timeRemaining[0][1] = whiteTimeRemaining;
12374     timeRemaining[1][1] = blackTimeRemaining;
12375     DrawPosition(FALSE, boards[currentMove]);
12376
12377     return TRUE;
12378 }
12379
12380
12381 void
12382 CopyPlayerNameIntoFileName (char **dest, char *src)
12383 {
12384     while (*src != NULLCHAR && *src != ',') {
12385         if (*src == ' ') {
12386             *(*dest)++ = '_';
12387             src++;
12388         } else {
12389             *(*dest)++ = *src++;
12390         }
12391     }
12392 }
12393
12394 char *
12395 DefaultFileName (char *ext)
12396 {
12397     static char def[MSG_SIZ];
12398     char *p;
12399
12400     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12401         p = def;
12402         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12403         *p++ = '-';
12404         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12405         *p++ = '.';
12406         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12407     } else {
12408         def[0] = NULLCHAR;
12409     }
12410     return def;
12411 }
12412
12413 /* Save the current game to the given file */
12414 int
12415 SaveGameToFile (char *filename, int append)
12416 {
12417     FILE *f;
12418     char buf[MSG_SIZ];
12419     int result, i, t,tot=0;
12420
12421     if (strcmp(filename, "-") == 0) {
12422         return SaveGame(stdout, 0, NULL);
12423     } else {
12424         for(i=0; i<10; i++) { // upto 10 tries
12425              f = fopen(filename, append ? "a" : "w");
12426              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12427              if(f || errno != 13) break;
12428              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12429              tot += t;
12430         }
12431         if (f == NULL) {
12432             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12433             DisplayError(buf, errno);
12434             return FALSE;
12435         } else {
12436             safeStrCpy(buf, lastMsg, MSG_SIZ);
12437             DisplayMessage(_("Waiting for access to save file"), "");
12438             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12439             DisplayMessage(_("Saving game"), "");
12440             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12441             result = SaveGame(f, 0, NULL);
12442             DisplayMessage(buf, "");
12443             return result;
12444         }
12445     }
12446 }
12447
12448 char *
12449 SavePart (char *str)
12450 {
12451     static char buf[MSG_SIZ];
12452     char *p;
12453
12454     p = strchr(str, ' ');
12455     if (p == NULL) return str;
12456     strncpy(buf, str, p - str);
12457     buf[p - str] = NULLCHAR;
12458     return buf;
12459 }
12460
12461 #define PGN_MAX_LINE 75
12462
12463 #define PGN_SIDE_WHITE  0
12464 #define PGN_SIDE_BLACK  1
12465
12466 static int
12467 FindFirstMoveOutOfBook (int side)
12468 {
12469     int result = -1;
12470
12471     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12472         int index = backwardMostMove;
12473         int has_book_hit = 0;
12474
12475         if( (index % 2) != side ) {
12476             index++;
12477         }
12478
12479         while( index < forwardMostMove ) {
12480             /* Check to see if engine is in book */
12481             int depth = pvInfoList[index].depth;
12482             int score = pvInfoList[index].score;
12483             int in_book = 0;
12484
12485             if( depth <= 2 ) {
12486                 in_book = 1;
12487             }
12488             else if( score == 0 && depth == 63 ) {
12489                 in_book = 1; /* Zappa */
12490             }
12491             else if( score == 2 && depth == 99 ) {
12492                 in_book = 1; /* Abrok */
12493             }
12494
12495             has_book_hit += in_book;
12496
12497             if( ! in_book ) {
12498                 result = index;
12499
12500                 break;
12501             }
12502
12503             index += 2;
12504         }
12505     }
12506
12507     return result;
12508 }
12509
12510 void
12511 GetOutOfBookInfo (char * buf)
12512 {
12513     int oob[2];
12514     int i;
12515     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12516
12517     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12518     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12519
12520     *buf = '\0';
12521
12522     if( oob[0] >= 0 || oob[1] >= 0 ) {
12523         for( i=0; i<2; i++ ) {
12524             int idx = oob[i];
12525
12526             if( idx >= 0 ) {
12527                 if( i > 0 && oob[0] >= 0 ) {
12528                     strcat( buf, "   " );
12529                 }
12530
12531                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12532                 sprintf( buf+strlen(buf), "%s%.2f",
12533                     pvInfoList[idx].score >= 0 ? "+" : "",
12534                     pvInfoList[idx].score / 100.0 );
12535             }
12536         }
12537     }
12538 }
12539
12540 /* Save game in PGN style and close the file */
12541 int
12542 SaveGamePGN (FILE *f)
12543 {
12544     int i, offset, linelen, newblock;
12545 //    char *movetext;
12546     char numtext[32];
12547     int movelen, numlen, blank;
12548     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12549
12550     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12551
12552     PrintPGNTags(f, &gameInfo);
12553
12554     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12555
12556     if (backwardMostMove > 0 || startedFromSetupPosition) {
12557         char *fen = PositionToFEN(backwardMostMove, NULL);
12558         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12559         fprintf(f, "\n{--------------\n");
12560         PrintPosition(f, backwardMostMove);
12561         fprintf(f, "--------------}\n");
12562         free(fen);
12563     }
12564     else {
12565         /* [AS] Out of book annotation */
12566         if( appData.saveOutOfBookInfo ) {
12567             char buf[64];
12568
12569             GetOutOfBookInfo( buf );
12570
12571             if( buf[0] != '\0' ) {
12572                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12573             }
12574         }
12575
12576         fprintf(f, "\n");
12577     }
12578
12579     i = backwardMostMove;
12580     linelen = 0;
12581     newblock = TRUE;
12582
12583     while (i < forwardMostMove) {
12584         /* Print comments preceding this move */
12585         if (commentList[i] != NULL) {
12586             if (linelen > 0) fprintf(f, "\n");
12587             fprintf(f, "%s", commentList[i]);
12588             linelen = 0;
12589             newblock = TRUE;
12590         }
12591
12592         /* Format move number */
12593         if ((i % 2) == 0)
12594           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12595         else
12596           if (newblock)
12597             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12598           else
12599             numtext[0] = NULLCHAR;
12600
12601         numlen = strlen(numtext);
12602         newblock = FALSE;
12603
12604         /* Print move number */
12605         blank = linelen > 0 && numlen > 0;
12606         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12607             fprintf(f, "\n");
12608             linelen = 0;
12609             blank = 0;
12610         }
12611         if (blank) {
12612             fprintf(f, " ");
12613             linelen++;
12614         }
12615         fprintf(f, "%s", numtext);
12616         linelen += numlen;
12617
12618         /* Get move */
12619         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12620         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12621
12622         /* Print move */
12623         blank = linelen > 0 && movelen > 0;
12624         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12625             fprintf(f, "\n");
12626             linelen = 0;
12627             blank = 0;
12628         }
12629         if (blank) {
12630             fprintf(f, " ");
12631             linelen++;
12632         }
12633         fprintf(f, "%s", move_buffer);
12634         linelen += movelen;
12635
12636         /* [AS] Add PV info if present */
12637         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12638             /* [HGM] add time */
12639             char buf[MSG_SIZ]; int seconds;
12640
12641             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12642
12643             if( seconds <= 0)
12644               buf[0] = 0;
12645             else
12646               if( seconds < 30 )
12647                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12648               else
12649                 {
12650                   seconds = (seconds + 4)/10; // round to full seconds
12651                   if( seconds < 60 )
12652                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12653                   else
12654                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12655                 }
12656
12657             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12658                       pvInfoList[i].score >= 0 ? "+" : "",
12659                       pvInfoList[i].score / 100.0,
12660                       pvInfoList[i].depth,
12661                       buf );
12662
12663             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12664
12665             /* Print score/depth */
12666             blank = linelen > 0 && movelen > 0;
12667             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12668                 fprintf(f, "\n");
12669                 linelen = 0;
12670                 blank = 0;
12671             }
12672             if (blank) {
12673                 fprintf(f, " ");
12674                 linelen++;
12675             }
12676             fprintf(f, "%s", move_buffer);
12677             linelen += movelen;
12678         }
12679
12680         i++;
12681     }
12682
12683     /* Start a new line */
12684     if (linelen > 0) fprintf(f, "\n");
12685
12686     /* Print comments after last move */
12687     if (commentList[i] != NULL) {
12688         fprintf(f, "%s\n", commentList[i]);
12689     }
12690
12691     /* Print result */
12692     if (gameInfo.resultDetails != NULL &&
12693         gameInfo.resultDetails[0] != NULLCHAR) {
12694         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12695                 PGNResult(gameInfo.result));
12696     } else {
12697         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12698     }
12699
12700     fclose(f);
12701     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12702     return TRUE;
12703 }
12704
12705 /* Save game in old style and close the file */
12706 int
12707 SaveGameOldStyle (FILE *f)
12708 {
12709     int i, offset;
12710     time_t tm;
12711
12712     tm = time((time_t *) NULL);
12713
12714     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12715     PrintOpponents(f);
12716
12717     if (backwardMostMove > 0 || startedFromSetupPosition) {
12718         fprintf(f, "\n[--------------\n");
12719         PrintPosition(f, backwardMostMove);
12720         fprintf(f, "--------------]\n");
12721     } else {
12722         fprintf(f, "\n");
12723     }
12724
12725     i = backwardMostMove;
12726     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12727
12728     while (i < forwardMostMove) {
12729         if (commentList[i] != NULL) {
12730             fprintf(f, "[%s]\n", commentList[i]);
12731         }
12732
12733         if ((i % 2) == 1) {
12734             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12735             i++;
12736         } else {
12737             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12738             i++;
12739             if (commentList[i] != NULL) {
12740                 fprintf(f, "\n");
12741                 continue;
12742             }
12743             if (i >= forwardMostMove) {
12744                 fprintf(f, "\n");
12745                 break;
12746             }
12747             fprintf(f, "%s\n", parseList[i]);
12748             i++;
12749         }
12750     }
12751
12752     if (commentList[i] != NULL) {
12753         fprintf(f, "[%s]\n", commentList[i]);
12754     }
12755
12756     /* This isn't really the old style, but it's close enough */
12757     if (gameInfo.resultDetails != NULL &&
12758         gameInfo.resultDetails[0] != NULLCHAR) {
12759         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12760                 gameInfo.resultDetails);
12761     } else {
12762         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12763     }
12764
12765     fclose(f);
12766     return TRUE;
12767 }
12768
12769 /* Save the current game to open file f and close the file */
12770 int
12771 SaveGame (FILE *f, int dummy, char *dummy2)
12772 {
12773     if (gameMode == EditPosition) EditPositionDone(TRUE);
12774     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12775     if (appData.oldSaveStyle)
12776       return SaveGameOldStyle(f);
12777     else
12778       return SaveGamePGN(f);
12779 }
12780
12781 /* Save the current position to the given file */
12782 int
12783 SavePositionToFile (char *filename)
12784 {
12785     FILE *f;
12786     char buf[MSG_SIZ];
12787
12788     if (strcmp(filename, "-") == 0) {
12789         return SavePosition(stdout, 0, NULL);
12790     } else {
12791         f = fopen(filename, "a");
12792         if (f == NULL) {
12793             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12794             DisplayError(buf, errno);
12795             return FALSE;
12796         } else {
12797             safeStrCpy(buf, lastMsg, MSG_SIZ);
12798             DisplayMessage(_("Waiting for access to save file"), "");
12799             flock(fileno(f), LOCK_EX); // [HGM] lock
12800             DisplayMessage(_("Saving position"), "");
12801             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12802             SavePosition(f, 0, NULL);
12803             DisplayMessage(buf, "");
12804             return TRUE;
12805         }
12806     }
12807 }
12808
12809 /* Save the current position to the given open file and close the file */
12810 int
12811 SavePosition (FILE *f, int dummy, char *dummy2)
12812 {
12813     time_t tm;
12814     char *fen;
12815
12816     if (gameMode == EditPosition) EditPositionDone(TRUE);
12817     if (appData.oldSaveStyle) {
12818         tm = time((time_t *) NULL);
12819
12820         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12821         PrintOpponents(f);
12822         fprintf(f, "[--------------\n");
12823         PrintPosition(f, currentMove);
12824         fprintf(f, "--------------]\n");
12825     } else {
12826         fen = PositionToFEN(currentMove, NULL);
12827         fprintf(f, "%s\n", fen);
12828         free(fen);
12829     }
12830     fclose(f);
12831     return TRUE;
12832 }
12833
12834 void
12835 ReloadCmailMsgEvent (int unregister)
12836 {
12837 #if !WIN32
12838     static char *inFilename = NULL;
12839     static char *outFilename;
12840     int i;
12841     struct stat inbuf, outbuf;
12842     int status;
12843
12844     /* Any registered moves are unregistered if unregister is set, */
12845     /* i.e. invoked by the signal handler */
12846     if (unregister) {
12847         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12848             cmailMoveRegistered[i] = FALSE;
12849             if (cmailCommentList[i] != NULL) {
12850                 free(cmailCommentList[i]);
12851                 cmailCommentList[i] = NULL;
12852             }
12853         }
12854         nCmailMovesRegistered = 0;
12855     }
12856
12857     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12858         cmailResult[i] = CMAIL_NOT_RESULT;
12859     }
12860     nCmailResults = 0;
12861
12862     if (inFilename == NULL) {
12863         /* Because the filenames are static they only get malloced once  */
12864         /* and they never get freed                                      */
12865         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12866         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12867
12868         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12869         sprintf(outFilename, "%s.out", appData.cmailGameName);
12870     }
12871
12872     status = stat(outFilename, &outbuf);
12873     if (status < 0) {
12874         cmailMailedMove = FALSE;
12875     } else {
12876         status = stat(inFilename, &inbuf);
12877         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12878     }
12879
12880     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12881        counts the games, notes how each one terminated, etc.
12882
12883        It would be nice to remove this kludge and instead gather all
12884        the information while building the game list.  (And to keep it
12885        in the game list nodes instead of having a bunch of fixed-size
12886        parallel arrays.)  Note this will require getting each game's
12887        termination from the PGN tags, as the game list builder does
12888        not process the game moves.  --mann
12889        */
12890     cmailMsgLoaded = TRUE;
12891     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12892
12893     /* Load first game in the file or popup game menu */
12894     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12895
12896 #endif /* !WIN32 */
12897     return;
12898 }
12899
12900 int
12901 RegisterMove ()
12902 {
12903     FILE *f;
12904     char string[MSG_SIZ];
12905
12906     if (   cmailMailedMove
12907         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12908         return TRUE;            /* Allow free viewing  */
12909     }
12910
12911     /* Unregister move to ensure that we don't leave RegisterMove        */
12912     /* with the move registered when the conditions for registering no   */
12913     /* longer hold                                                       */
12914     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12915         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12916         nCmailMovesRegistered --;
12917
12918         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12919           {
12920               free(cmailCommentList[lastLoadGameNumber - 1]);
12921               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12922           }
12923     }
12924
12925     if (cmailOldMove == -1) {
12926         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12927         return FALSE;
12928     }
12929
12930     if (currentMove > cmailOldMove + 1) {
12931         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12932         return FALSE;
12933     }
12934
12935     if (currentMove < cmailOldMove) {
12936         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12937         return FALSE;
12938     }
12939
12940     if (forwardMostMove > currentMove) {
12941         /* Silently truncate extra moves */
12942         TruncateGame();
12943     }
12944
12945     if (   (currentMove == cmailOldMove + 1)
12946         || (   (currentMove == cmailOldMove)
12947             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12948                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12949         if (gameInfo.result != GameUnfinished) {
12950             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12951         }
12952
12953         if (commentList[currentMove] != NULL) {
12954             cmailCommentList[lastLoadGameNumber - 1]
12955               = StrSave(commentList[currentMove]);
12956         }
12957         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12958
12959         if (appData.debugMode)
12960           fprintf(debugFP, "Saving %s for game %d\n",
12961                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12962
12963         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12964
12965         f = fopen(string, "w");
12966         if (appData.oldSaveStyle) {
12967             SaveGameOldStyle(f); /* also closes the file */
12968
12969             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12970             f = fopen(string, "w");
12971             SavePosition(f, 0, NULL); /* also closes the file */
12972         } else {
12973             fprintf(f, "{--------------\n");
12974             PrintPosition(f, currentMove);
12975             fprintf(f, "--------------}\n\n");
12976
12977             SaveGame(f, 0, NULL); /* also closes the file*/
12978         }
12979
12980         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12981         nCmailMovesRegistered ++;
12982     } else if (nCmailGames == 1) {
12983         DisplayError(_("You have not made a move yet"), 0);
12984         return FALSE;
12985     }
12986
12987     return TRUE;
12988 }
12989
12990 void
12991 MailMoveEvent ()
12992 {
12993 #if !WIN32
12994     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12995     FILE *commandOutput;
12996     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12997     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12998     int nBuffers;
12999     int i;
13000     int archived;
13001     char *arcDir;
13002
13003     if (! cmailMsgLoaded) {
13004         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13005         return;
13006     }
13007
13008     if (nCmailGames == nCmailResults) {
13009         DisplayError(_("No unfinished games"), 0);
13010         return;
13011     }
13012
13013 #if CMAIL_PROHIBIT_REMAIL
13014     if (cmailMailedMove) {
13015       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);
13016         DisplayError(msg, 0);
13017         return;
13018     }
13019 #endif
13020
13021     if (! (cmailMailedMove || RegisterMove())) return;
13022
13023     if (   cmailMailedMove
13024         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13025       snprintf(string, MSG_SIZ, partCommandString,
13026                appData.debugMode ? " -v" : "", appData.cmailGameName);
13027         commandOutput = popen(string, "r");
13028
13029         if (commandOutput == NULL) {
13030             DisplayError(_("Failed to invoke cmail"), 0);
13031         } else {
13032             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13033                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13034             }
13035             if (nBuffers > 1) {
13036                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13037                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13038                 nBytes = MSG_SIZ - 1;
13039             } else {
13040                 (void) memcpy(msg, buffer, nBytes);
13041             }
13042             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13043
13044             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13045                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13046
13047                 archived = TRUE;
13048                 for (i = 0; i < nCmailGames; i ++) {
13049                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13050                         archived = FALSE;
13051                     }
13052                 }
13053                 if (   archived
13054                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13055                         != NULL)) {
13056                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13057                            arcDir,
13058                            appData.cmailGameName,
13059                            gameInfo.date);
13060                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13061                     cmailMsgLoaded = FALSE;
13062                 }
13063             }
13064
13065             DisplayInformation(msg);
13066             pclose(commandOutput);
13067         }
13068     } else {
13069         if ((*cmailMsg) != '\0') {
13070             DisplayInformation(cmailMsg);
13071         }
13072     }
13073
13074     return;
13075 #endif /* !WIN32 */
13076 }
13077
13078 char *
13079 CmailMsg ()
13080 {
13081 #if WIN32
13082     return NULL;
13083 #else
13084     int  prependComma = 0;
13085     char number[5];
13086     char string[MSG_SIZ];       /* Space for game-list */
13087     int  i;
13088
13089     if (!cmailMsgLoaded) return "";
13090
13091     if (cmailMailedMove) {
13092       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13093     } else {
13094         /* Create a list of games left */
13095       snprintf(string, MSG_SIZ, "[");
13096         for (i = 0; i < nCmailGames; i ++) {
13097             if (! (   cmailMoveRegistered[i]
13098                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13099                 if (prependComma) {
13100                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13101                 } else {
13102                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13103                     prependComma = 1;
13104                 }
13105
13106                 strcat(string, number);
13107             }
13108         }
13109         strcat(string, "]");
13110
13111         if (nCmailMovesRegistered + nCmailResults == 0) {
13112             switch (nCmailGames) {
13113               case 1:
13114                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13115                 break;
13116
13117               case 2:
13118                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13119                 break;
13120
13121               default:
13122                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13123                          nCmailGames);
13124                 break;
13125             }
13126         } else {
13127             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13128               case 1:
13129                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13130                          string);
13131                 break;
13132
13133               case 0:
13134                 if (nCmailResults == nCmailGames) {
13135                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13136                 } else {
13137                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13138                 }
13139                 break;
13140
13141               default:
13142                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13143                          string);
13144             }
13145         }
13146     }
13147     return cmailMsg;
13148 #endif /* WIN32 */
13149 }
13150
13151 void
13152 ResetGameEvent ()
13153 {
13154     if (gameMode == Training)
13155       SetTrainingModeOff();
13156
13157     Reset(TRUE, TRUE);
13158     cmailMsgLoaded = FALSE;
13159     if (appData.icsActive) {
13160       SendToICS(ics_prefix);
13161       SendToICS("refresh\n");
13162     }
13163 }
13164
13165 void
13166 ExitEvent (int status)
13167 {
13168     exiting++;
13169     if (exiting > 2) {
13170       /* Give up on clean exit */
13171       exit(status);
13172     }
13173     if (exiting > 1) {
13174       /* Keep trying for clean exit */
13175       return;
13176     }
13177
13178     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13179
13180     if (telnetISR != NULL) {
13181       RemoveInputSource(telnetISR);
13182     }
13183     if (icsPR != NoProc) {
13184       DestroyChildProcess(icsPR, TRUE);
13185     }
13186
13187     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13188     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13189
13190     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13191     /* make sure this other one finishes before killing it!                  */
13192     if(endingGame) { int count = 0;
13193         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13194         while(endingGame && count++ < 10) DoSleep(1);
13195         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13196     }
13197
13198     /* Kill off chess programs */
13199     if (first.pr != NoProc) {
13200         ExitAnalyzeMode();
13201
13202         DoSleep( appData.delayBeforeQuit );
13203         SendToProgram("quit\n", &first);
13204         DoSleep( appData.delayAfterQuit );
13205         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13206     }
13207     if (second.pr != NoProc) {
13208         DoSleep( appData.delayBeforeQuit );
13209         SendToProgram("quit\n", &second);
13210         DoSleep( appData.delayAfterQuit );
13211         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13212     }
13213     if (first.isr != NULL) {
13214         RemoveInputSource(first.isr);
13215     }
13216     if (second.isr != NULL) {
13217         RemoveInputSource(second.isr);
13218     }
13219
13220     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13221     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13222
13223     ShutDownFrontEnd();
13224     exit(status);
13225 }
13226
13227 void
13228 PauseEvent ()
13229 {
13230     if (appData.debugMode)
13231         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13232     if (pausing) {
13233         pausing = FALSE;
13234         ModeHighlight();
13235         if (gameMode == MachinePlaysWhite ||
13236             gameMode == MachinePlaysBlack) {
13237             StartClocks();
13238         } else {
13239             DisplayBothClocks();
13240         }
13241         if (gameMode == PlayFromGameFile) {
13242             if (appData.timeDelay >= 0)
13243                 AutoPlayGameLoop();
13244         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13245             Reset(FALSE, TRUE);
13246             SendToICS(ics_prefix);
13247             SendToICS("refresh\n");
13248         } else if (currentMove < forwardMostMove) {
13249             ForwardInner(forwardMostMove);
13250         }
13251         pauseExamInvalid = FALSE;
13252     } else {
13253         switch (gameMode) {
13254           default:
13255             return;
13256           case IcsExamining:
13257             pauseExamForwardMostMove = forwardMostMove;
13258             pauseExamInvalid = FALSE;
13259             /* fall through */
13260           case IcsObserving:
13261           case IcsPlayingWhite:
13262           case IcsPlayingBlack:
13263             pausing = TRUE;
13264             ModeHighlight();
13265             return;
13266           case PlayFromGameFile:
13267             (void) StopLoadGameTimer();
13268             pausing = TRUE;
13269             ModeHighlight();
13270             break;
13271           case BeginningOfGame:
13272             if (appData.icsActive) return;
13273             /* else fall through */
13274           case MachinePlaysWhite:
13275           case MachinePlaysBlack:
13276           case TwoMachinesPlay:
13277             if (forwardMostMove == 0)
13278               return;           /* don't pause if no one has moved */
13279             if ((gameMode == MachinePlaysWhite &&
13280                  !WhiteOnMove(forwardMostMove)) ||
13281                 (gameMode == MachinePlaysBlack &&
13282                  WhiteOnMove(forwardMostMove))) {
13283                 StopClocks();
13284             }
13285             pausing = TRUE;
13286             ModeHighlight();
13287             break;
13288         }
13289     }
13290 }
13291
13292 void
13293 EditCommentEvent ()
13294 {
13295     char title[MSG_SIZ];
13296
13297     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13298       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13299     } else {
13300       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13301                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13302                parseList[currentMove - 1]);
13303     }
13304
13305     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13306 }
13307
13308
13309 void
13310 EditTagsEvent ()
13311 {
13312     char *tags = PGNTags(&gameInfo);
13313     bookUp = FALSE;
13314     EditTagsPopUp(tags, NULL);
13315     free(tags);
13316 }
13317
13318 void
13319 AnalyzeModeEvent ()
13320 {
13321     if (appData.noChessProgram || gameMode == AnalyzeMode)
13322       return;
13323
13324     if (gameMode != AnalyzeFile) {
13325         if (!appData.icsEngineAnalyze) {
13326                EditGameEvent();
13327                if (gameMode != EditGame) return;
13328         }
13329         ResurrectChessProgram();
13330         SendToProgram("analyze\n", &first);
13331         first.analyzing = TRUE;
13332         /*first.maybeThinking = TRUE;*/
13333         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13334         EngineOutputPopUp();
13335     }
13336     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13337     pausing = FALSE;
13338     ModeHighlight();
13339     SetGameInfo();
13340
13341     StartAnalysisClock();
13342     GetTimeMark(&lastNodeCountTime);
13343     lastNodeCount = 0;
13344 }
13345
13346 void
13347 AnalyzeFileEvent ()
13348 {
13349     if (appData.noChessProgram || gameMode == AnalyzeFile)
13350       return;
13351
13352     if (gameMode != AnalyzeMode) {
13353         EditGameEvent();
13354         if (gameMode != EditGame) return;
13355         ResurrectChessProgram();
13356         SendToProgram("analyze\n", &first);
13357         first.analyzing = TRUE;
13358         /*first.maybeThinking = TRUE;*/
13359         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13360         EngineOutputPopUp();
13361     }
13362     gameMode = AnalyzeFile;
13363     pausing = FALSE;
13364     ModeHighlight();
13365     SetGameInfo();
13366
13367     StartAnalysisClock();
13368     GetTimeMark(&lastNodeCountTime);
13369     lastNodeCount = 0;
13370     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13371 }
13372
13373 void
13374 MachineWhiteEvent ()
13375 {
13376     char buf[MSG_SIZ];
13377     char *bookHit = NULL;
13378
13379     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13380       return;
13381
13382
13383     if (gameMode == PlayFromGameFile ||
13384         gameMode == TwoMachinesPlay  ||
13385         gameMode == Training         ||
13386         gameMode == AnalyzeMode      ||
13387         gameMode == EndOfGame)
13388         EditGameEvent();
13389
13390     if (gameMode == EditPosition)
13391         EditPositionDone(TRUE);
13392
13393     if (!WhiteOnMove(currentMove)) {
13394         DisplayError(_("It is not White's turn"), 0);
13395         return;
13396     }
13397
13398     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13399       ExitAnalyzeMode();
13400
13401     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13402         gameMode == AnalyzeFile)
13403         TruncateGame();
13404
13405     ResurrectChessProgram();    /* in case it isn't running */
13406     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13407         gameMode = MachinePlaysWhite;
13408         ResetClocks();
13409     } else
13410     gameMode = MachinePlaysWhite;
13411     pausing = FALSE;
13412     ModeHighlight();
13413     SetGameInfo();
13414     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13415     DisplayTitle(buf);
13416     if (first.sendName) {
13417       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13418       SendToProgram(buf, &first);
13419     }
13420     if (first.sendTime) {
13421       if (first.useColors) {
13422         SendToProgram("black\n", &first); /*gnu kludge*/
13423       }
13424       SendTimeRemaining(&first, TRUE);
13425     }
13426     if (first.useColors) {
13427       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13428     }
13429     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13430     SetMachineThinkingEnables();
13431     first.maybeThinking = TRUE;
13432     StartClocks();
13433     firstMove = FALSE;
13434
13435     if (appData.autoFlipView && !flipView) {
13436       flipView = !flipView;
13437       DrawPosition(FALSE, NULL);
13438       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13439     }
13440
13441     if(bookHit) { // [HGM] book: simulate book reply
13442         static char bookMove[MSG_SIZ]; // a bit generous?
13443
13444         programStats.nodes = programStats.depth = programStats.time =
13445         programStats.score = programStats.got_only_move = 0;
13446         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13447
13448         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13449         strcat(bookMove, bookHit);
13450         HandleMachineMove(bookMove, &first);
13451     }
13452 }
13453
13454 void
13455 MachineBlackEvent ()
13456 {
13457   char buf[MSG_SIZ];
13458   char *bookHit = NULL;
13459
13460     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13461         return;
13462
13463
13464     if (gameMode == PlayFromGameFile ||
13465         gameMode == TwoMachinesPlay  ||
13466         gameMode == Training         ||
13467         gameMode == AnalyzeMode      ||
13468         gameMode == EndOfGame)
13469         EditGameEvent();
13470
13471     if (gameMode == EditPosition)
13472         EditPositionDone(TRUE);
13473
13474     if (WhiteOnMove(currentMove)) {
13475         DisplayError(_("It is not Black's turn"), 0);
13476         return;
13477     }
13478
13479     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13480       ExitAnalyzeMode();
13481
13482     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13483         gameMode == AnalyzeFile)
13484         TruncateGame();
13485
13486     ResurrectChessProgram();    /* in case it isn't running */
13487     gameMode = MachinePlaysBlack;
13488     pausing = FALSE;
13489     ModeHighlight();
13490     SetGameInfo();
13491     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13492     DisplayTitle(buf);
13493     if (first.sendName) {
13494       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13495       SendToProgram(buf, &first);
13496     }
13497     if (first.sendTime) {
13498       if (first.useColors) {
13499         SendToProgram("white\n", &first); /*gnu kludge*/
13500       }
13501       SendTimeRemaining(&first, FALSE);
13502     }
13503     if (first.useColors) {
13504       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13505     }
13506     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13507     SetMachineThinkingEnables();
13508     first.maybeThinking = TRUE;
13509     StartClocks();
13510
13511     if (appData.autoFlipView && flipView) {
13512       flipView = !flipView;
13513       DrawPosition(FALSE, NULL);
13514       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13515     }
13516     if(bookHit) { // [HGM] book: simulate book reply
13517         static char bookMove[MSG_SIZ]; // a bit generous?
13518
13519         programStats.nodes = programStats.depth = programStats.time =
13520         programStats.score = programStats.got_only_move = 0;
13521         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13522
13523         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13524         strcat(bookMove, bookHit);
13525         HandleMachineMove(bookMove, &first);
13526     }
13527 }
13528
13529
13530 void
13531 DisplayTwoMachinesTitle ()
13532 {
13533     char buf[MSG_SIZ];
13534     if (appData.matchGames > 0) {
13535         if(appData.tourneyFile[0]) {
13536           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13537                    gameInfo.white, _("vs."), gameInfo.black,
13538                    nextGame+1, appData.matchGames+1,
13539                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13540         } else 
13541         if (first.twoMachinesColor[0] == 'w') {
13542           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13543                    gameInfo.white, _("vs."),  gameInfo.black,
13544                    first.matchWins, second.matchWins,
13545                    matchGame - 1 - (first.matchWins + second.matchWins));
13546         } else {
13547           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13548                    gameInfo.white, _("vs."), gameInfo.black,
13549                    second.matchWins, first.matchWins,
13550                    matchGame - 1 - (first.matchWins + second.matchWins));
13551         }
13552     } else {
13553       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13554     }
13555     DisplayTitle(buf);
13556 }
13557
13558 void
13559 SettingsMenuIfReady ()
13560 {
13561   if (second.lastPing != second.lastPong) {
13562     DisplayMessage("", _("Waiting for second chess program"));
13563     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13564     return;
13565   }
13566   ThawUI();
13567   DisplayMessage("", "");
13568   SettingsPopUp(&second);
13569 }
13570
13571 int
13572 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13573 {
13574     char buf[MSG_SIZ];
13575     if (cps->pr == NoProc) {
13576         StartChessProgram(cps);
13577         if (cps->protocolVersion == 1) {
13578           retry();
13579         } else {
13580           /* kludge: allow timeout for initial "feature" command */
13581           FreezeUI();
13582           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13583           DisplayMessage("", buf);
13584           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13585         }
13586         return 1;
13587     }
13588     return 0;
13589 }
13590
13591 void
13592 TwoMachinesEvent P((void))
13593 {
13594     int i;
13595     char buf[MSG_SIZ];
13596     ChessProgramState *onmove;
13597     char *bookHit = NULL;
13598     static int stalling = 0;
13599     TimeMark now;
13600     long wait;
13601
13602     if (appData.noChessProgram) return;
13603
13604     switch (gameMode) {
13605       case TwoMachinesPlay:
13606         return;
13607       case MachinePlaysWhite:
13608       case MachinePlaysBlack:
13609         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13610             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13611             return;
13612         }
13613         /* fall through */
13614       case BeginningOfGame:
13615       case PlayFromGameFile:
13616       case EndOfGame:
13617         EditGameEvent();
13618         if (gameMode != EditGame) return;
13619         break;
13620       case EditPosition:
13621         EditPositionDone(TRUE);
13622         break;
13623       case AnalyzeMode:
13624       case AnalyzeFile:
13625         ExitAnalyzeMode();
13626         break;
13627       case EditGame:
13628       default:
13629         break;
13630     }
13631
13632 //    forwardMostMove = currentMove;
13633     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13634
13635     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13636
13637     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13638     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13639       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13640       return;
13641     }
13642
13643     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13644         DisplayError("second engine does not play this", 0);
13645         return;
13646     }
13647
13648     if(!stalling) {
13649       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13650       SendToProgram("force\n", &second);
13651       stalling = 1;
13652       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13653       return;
13654     }
13655     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13656     if(appData.matchPause>10000 || appData.matchPause<10)
13657                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13658     wait = SubtractTimeMarks(&now, &pauseStart);
13659     if(wait < appData.matchPause) {
13660         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13661         return;
13662     }
13663     // we are now committed to starting the game
13664     stalling = 0;
13665     DisplayMessage("", "");
13666     if (startedFromSetupPosition) {
13667         SendBoard(&second, backwardMostMove);
13668     if (appData.debugMode) {
13669         fprintf(debugFP, "Two Machines\n");
13670     }
13671     }
13672     for (i = backwardMostMove; i < forwardMostMove; i++) {
13673         SendMoveToProgram(i, &second);
13674     }
13675
13676     gameMode = TwoMachinesPlay;
13677     pausing = FALSE;
13678     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13679     SetGameInfo();
13680     DisplayTwoMachinesTitle();
13681     firstMove = TRUE;
13682     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13683         onmove = &first;
13684     } else {
13685         onmove = &second;
13686     }
13687     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13688     SendToProgram(first.computerString, &first);
13689     if (first.sendName) {
13690       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13691       SendToProgram(buf, &first);
13692     }
13693     SendToProgram(second.computerString, &second);
13694     if (second.sendName) {
13695       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13696       SendToProgram(buf, &second);
13697     }
13698
13699     ResetClocks();
13700     if (!first.sendTime || !second.sendTime) {
13701         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13702         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13703     }
13704     if (onmove->sendTime) {
13705       if (onmove->useColors) {
13706         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13707       }
13708       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13709     }
13710     if (onmove->useColors) {
13711       SendToProgram(onmove->twoMachinesColor, onmove);
13712     }
13713     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13714 //    SendToProgram("go\n", onmove);
13715     onmove->maybeThinking = TRUE;
13716     SetMachineThinkingEnables();
13717
13718     StartClocks();
13719
13720     if(bookHit) { // [HGM] book: simulate book reply
13721         static char bookMove[MSG_SIZ]; // a bit generous?
13722
13723         programStats.nodes = programStats.depth = programStats.time =
13724         programStats.score = programStats.got_only_move = 0;
13725         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13726
13727         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13728         strcat(bookMove, bookHit);
13729         savedMessage = bookMove; // args for deferred call
13730         savedState = onmove;
13731         ScheduleDelayedEvent(DeferredBookMove, 1);
13732     }
13733 }
13734
13735 void
13736 TrainingEvent ()
13737 {
13738     if (gameMode == Training) {
13739       SetTrainingModeOff();
13740       gameMode = PlayFromGameFile;
13741       DisplayMessage("", _("Training mode off"));
13742     } else {
13743       gameMode = Training;
13744       animateTraining = appData.animate;
13745
13746       /* make sure we are not already at the end of the game */
13747       if (currentMove < forwardMostMove) {
13748         SetTrainingModeOn();
13749         DisplayMessage("", _("Training mode on"));
13750       } else {
13751         gameMode = PlayFromGameFile;
13752         DisplayError(_("Already at end of game"), 0);
13753       }
13754     }
13755     ModeHighlight();
13756 }
13757
13758 void
13759 IcsClientEvent ()
13760 {
13761     if (!appData.icsActive) return;
13762     switch (gameMode) {
13763       case IcsPlayingWhite:
13764       case IcsPlayingBlack:
13765       case IcsObserving:
13766       case IcsIdle:
13767       case BeginningOfGame:
13768       case IcsExamining:
13769         return;
13770
13771       case EditGame:
13772         break;
13773
13774       case EditPosition:
13775         EditPositionDone(TRUE);
13776         break;
13777
13778       case AnalyzeMode:
13779       case AnalyzeFile:
13780         ExitAnalyzeMode();
13781         break;
13782
13783       default:
13784         EditGameEvent();
13785         break;
13786     }
13787
13788     gameMode = IcsIdle;
13789     ModeHighlight();
13790     return;
13791 }
13792
13793 void
13794 EditGameEvent ()
13795 {
13796     int i;
13797
13798     switch (gameMode) {
13799       case Training:
13800         SetTrainingModeOff();
13801         break;
13802       case MachinePlaysWhite:
13803       case MachinePlaysBlack:
13804       case BeginningOfGame:
13805         SendToProgram("force\n", &first);
13806         SetUserThinkingEnables();
13807         break;
13808       case PlayFromGameFile:
13809         (void) StopLoadGameTimer();
13810         if (gameFileFP != NULL) {
13811             gameFileFP = NULL;
13812         }
13813         break;
13814       case EditPosition:
13815         EditPositionDone(TRUE);
13816         break;
13817       case AnalyzeMode:
13818       case AnalyzeFile:
13819         ExitAnalyzeMode();
13820         SendToProgram("force\n", &first);
13821         break;
13822       case TwoMachinesPlay:
13823         GameEnds(EndOfFile, NULL, GE_PLAYER);
13824         ResurrectChessProgram();
13825         SetUserThinkingEnables();
13826         break;
13827       case EndOfGame:
13828         ResurrectChessProgram();
13829         break;
13830       case IcsPlayingBlack:
13831       case IcsPlayingWhite:
13832         DisplayError(_("Warning: You are still playing a game"), 0);
13833         break;
13834       case IcsObserving:
13835         DisplayError(_("Warning: You are still observing a game"), 0);
13836         break;
13837       case IcsExamining:
13838         DisplayError(_("Warning: You are still examining a game"), 0);
13839         break;
13840       case IcsIdle:
13841         break;
13842       case EditGame:
13843       default:
13844         return;
13845     }
13846
13847     pausing = FALSE;
13848     StopClocks();
13849     first.offeredDraw = second.offeredDraw = 0;
13850
13851     if (gameMode == PlayFromGameFile) {
13852         whiteTimeRemaining = timeRemaining[0][currentMove];
13853         blackTimeRemaining = timeRemaining[1][currentMove];
13854         DisplayTitle("");
13855     }
13856
13857     if (gameMode == MachinePlaysWhite ||
13858         gameMode == MachinePlaysBlack ||
13859         gameMode == TwoMachinesPlay ||
13860         gameMode == EndOfGame) {
13861         i = forwardMostMove;
13862         while (i > currentMove) {
13863             SendToProgram("undo\n", &first);
13864             i--;
13865         }
13866         if(!adjustedClock) {
13867         whiteTimeRemaining = timeRemaining[0][currentMove];
13868         blackTimeRemaining = timeRemaining[1][currentMove];
13869         DisplayBothClocks();
13870         }
13871         if (whiteFlag || blackFlag) {
13872             whiteFlag = blackFlag = 0;
13873         }
13874         DisplayTitle("");
13875     }
13876
13877     gameMode = EditGame;
13878     ModeHighlight();
13879     SetGameInfo();
13880 }
13881
13882
13883 void
13884 EditPositionEvent ()
13885 {
13886     if (gameMode == EditPosition) {
13887         EditGameEvent();
13888         return;
13889     }
13890
13891     EditGameEvent();
13892     if (gameMode != EditGame) return;
13893
13894     gameMode = EditPosition;
13895     ModeHighlight();
13896     SetGameInfo();
13897     if (currentMove > 0)
13898       CopyBoard(boards[0], boards[currentMove]);
13899
13900     blackPlaysFirst = !WhiteOnMove(currentMove);
13901     ResetClocks();
13902     currentMove = forwardMostMove = backwardMostMove = 0;
13903     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13904     DisplayMove(-1);
13905     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13906 }
13907
13908 void
13909 ExitAnalyzeMode ()
13910 {
13911     /* [DM] icsEngineAnalyze - possible call from other functions */
13912     if (appData.icsEngineAnalyze) {
13913         appData.icsEngineAnalyze = FALSE;
13914
13915         DisplayMessage("",_("Close ICS engine analyze..."));
13916     }
13917     if (first.analysisSupport && first.analyzing) {
13918       SendToProgram("exit\n", &first);
13919       first.analyzing = FALSE;
13920     }
13921     thinkOutput[0] = NULLCHAR;
13922 }
13923
13924 void
13925 EditPositionDone (Boolean fakeRights)
13926 {
13927     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13928
13929     startedFromSetupPosition = TRUE;
13930     InitChessProgram(&first, FALSE);
13931     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13932       boards[0][EP_STATUS] = EP_NONE;
13933       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13934     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13935         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
13936         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13937       } else boards[0][CASTLING][2] = NoRights;
13938     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13939         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
13940         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13941       } else boards[0][CASTLING][5] = NoRights;
13942     }
13943     SendToProgram("force\n", &first);
13944     if (blackPlaysFirst) {
13945         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13946         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13947         currentMove = forwardMostMove = backwardMostMove = 1;
13948         CopyBoard(boards[1], boards[0]);
13949     } else {
13950         currentMove = forwardMostMove = backwardMostMove = 0;
13951     }
13952     SendBoard(&first, forwardMostMove);
13953     if (appData.debugMode) {
13954         fprintf(debugFP, "EditPosDone\n");
13955     }
13956     DisplayTitle("");
13957     DisplayMessage("", "");
13958     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13959     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13960     gameMode = EditGame;
13961     ModeHighlight();
13962     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13963     ClearHighlights(); /* [AS] */
13964 }
13965
13966 /* Pause for `ms' milliseconds */
13967 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13968 void
13969 TimeDelay (long ms)
13970 {
13971     TimeMark m1, m2;
13972
13973     GetTimeMark(&m1);
13974     do {
13975         GetTimeMark(&m2);
13976     } while (SubtractTimeMarks(&m2, &m1) < ms);
13977 }
13978
13979 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13980 void
13981 SendMultiLineToICS (char *buf)
13982 {
13983     char temp[MSG_SIZ+1], *p;
13984     int len;
13985
13986     len = strlen(buf);
13987     if (len > MSG_SIZ)
13988       len = MSG_SIZ;
13989
13990     strncpy(temp, buf, len);
13991     temp[len] = 0;
13992
13993     p = temp;
13994     while (*p) {
13995         if (*p == '\n' || *p == '\r')
13996           *p = ' ';
13997         ++p;
13998     }
13999
14000     strcat(temp, "\n");
14001     SendToICS(temp);
14002     SendToPlayer(temp, strlen(temp));
14003 }
14004
14005 void
14006 SetWhiteToPlayEvent ()
14007 {
14008     if (gameMode == EditPosition) {
14009         blackPlaysFirst = FALSE;
14010         DisplayBothClocks();    /* works because currentMove is 0 */
14011     } else if (gameMode == IcsExamining) {
14012         SendToICS(ics_prefix);
14013         SendToICS("tomove white\n");
14014     }
14015 }
14016
14017 void
14018 SetBlackToPlayEvent ()
14019 {
14020     if (gameMode == EditPosition) {
14021         blackPlaysFirst = TRUE;
14022         currentMove = 1;        /* kludge */
14023         DisplayBothClocks();
14024         currentMove = 0;
14025     } else if (gameMode == IcsExamining) {
14026         SendToICS(ics_prefix);
14027         SendToICS("tomove black\n");
14028     }
14029 }
14030
14031 void
14032 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14033 {
14034     char buf[MSG_SIZ];
14035     ChessSquare piece = boards[0][y][x];
14036
14037     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14038
14039     switch (selection) {
14040       case ClearBoard:
14041         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14042             SendToICS(ics_prefix);
14043             SendToICS("bsetup clear\n");
14044         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14045             SendToICS(ics_prefix);
14046             SendToICS("clearboard\n");
14047         } else {
14048             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14049                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14050                 for (y = 0; y < BOARD_HEIGHT; y++) {
14051                     if (gameMode == IcsExamining) {
14052                         if (boards[currentMove][y][x] != EmptySquare) {
14053                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14054                                     AAA + x, ONE + y);
14055                             SendToICS(buf);
14056                         }
14057                     } else {
14058                         boards[0][y][x] = p;
14059                     }
14060                 }
14061             }
14062         }
14063         if (gameMode == EditPosition) {
14064             DrawPosition(FALSE, boards[0]);
14065         }
14066         break;
14067
14068       case WhitePlay:
14069         SetWhiteToPlayEvent();
14070         break;
14071
14072       case BlackPlay:
14073         SetBlackToPlayEvent();
14074         break;
14075
14076       case EmptySquare:
14077         if (gameMode == IcsExamining) {
14078             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14079             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14080             SendToICS(buf);
14081         } else {
14082             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14083                 if(x == BOARD_LEFT-2) {
14084                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14085                     boards[0][y][1] = 0;
14086                 } else
14087                 if(x == BOARD_RGHT+1) {
14088                     if(y >= gameInfo.holdingsSize) break;
14089                     boards[0][y][BOARD_WIDTH-2] = 0;
14090                 } else break;
14091             }
14092             boards[0][y][x] = EmptySquare;
14093             DrawPosition(FALSE, boards[0]);
14094         }
14095         break;
14096
14097       case PromotePiece:
14098         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14099            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14100             selection = (ChessSquare) (PROMOTED piece);
14101         } else if(piece == EmptySquare) selection = WhiteSilver;
14102         else selection = (ChessSquare)((int)piece - 1);
14103         goto defaultlabel;
14104
14105       case DemotePiece:
14106         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14107            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14108             selection = (ChessSquare) (DEMOTED piece);
14109         } else if(piece == EmptySquare) selection = BlackSilver;
14110         else selection = (ChessSquare)((int)piece + 1);
14111         goto defaultlabel;
14112
14113       case WhiteQueen:
14114       case BlackQueen:
14115         if(gameInfo.variant == VariantShatranj ||
14116            gameInfo.variant == VariantXiangqi  ||
14117            gameInfo.variant == VariantCourier  ||
14118            gameInfo.variant == VariantMakruk     )
14119             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14120         goto defaultlabel;
14121
14122       case WhiteKing:
14123       case BlackKing:
14124         if(gameInfo.variant == VariantXiangqi)
14125             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14126         if(gameInfo.variant == VariantKnightmate)
14127             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14128       default:
14129         defaultlabel:
14130         if (gameMode == IcsExamining) {
14131             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14132             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14133                      PieceToChar(selection), AAA + x, ONE + y);
14134             SendToICS(buf);
14135         } else {
14136             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14137                 int n;
14138                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14139                     n = PieceToNumber(selection - BlackPawn);
14140                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14141                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14142                     boards[0][BOARD_HEIGHT-1-n][1]++;
14143                 } else
14144                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14145                     n = PieceToNumber(selection);
14146                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14147                     boards[0][n][BOARD_WIDTH-1] = selection;
14148                     boards[0][n][BOARD_WIDTH-2]++;
14149                 }
14150             } else
14151             boards[0][y][x] = selection;
14152             DrawPosition(TRUE, boards[0]);
14153             ClearHighlights();
14154             fromX = fromY = -1;
14155         }
14156         break;
14157     }
14158 }
14159
14160
14161 void
14162 DropMenuEvent (ChessSquare selection, int x, int y)
14163 {
14164     ChessMove moveType;
14165
14166     switch (gameMode) {
14167       case IcsPlayingWhite:
14168       case MachinePlaysBlack:
14169         if (!WhiteOnMove(currentMove)) {
14170             DisplayMoveError(_("It is Black's turn"));
14171             return;
14172         }
14173         moveType = WhiteDrop;
14174         break;
14175       case IcsPlayingBlack:
14176       case MachinePlaysWhite:
14177         if (WhiteOnMove(currentMove)) {
14178             DisplayMoveError(_("It is White's turn"));
14179             return;
14180         }
14181         moveType = BlackDrop;
14182         break;
14183       case EditGame:
14184         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14185         break;
14186       default:
14187         return;
14188     }
14189
14190     if (moveType == BlackDrop && selection < BlackPawn) {
14191       selection = (ChessSquare) ((int) selection
14192                                  + (int) BlackPawn - (int) WhitePawn);
14193     }
14194     if (boards[currentMove][y][x] != EmptySquare) {
14195         DisplayMoveError(_("That square is occupied"));
14196         return;
14197     }
14198
14199     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14200 }
14201
14202 void
14203 AcceptEvent ()
14204 {
14205     /* Accept a pending offer of any kind from opponent */
14206
14207     if (appData.icsActive) {
14208         SendToICS(ics_prefix);
14209         SendToICS("accept\n");
14210     } else if (cmailMsgLoaded) {
14211         if (currentMove == cmailOldMove &&
14212             commentList[cmailOldMove] != NULL &&
14213             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14214                    "Black offers a draw" : "White offers a draw")) {
14215             TruncateGame();
14216             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14217             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14218         } else {
14219             DisplayError(_("There is no pending offer on this move"), 0);
14220             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14221         }
14222     } else {
14223         /* Not used for offers from chess program */
14224     }
14225 }
14226
14227 void
14228 DeclineEvent ()
14229 {
14230     /* Decline a pending offer of any kind from opponent */
14231
14232     if (appData.icsActive) {
14233         SendToICS(ics_prefix);
14234         SendToICS("decline\n");
14235     } else if (cmailMsgLoaded) {
14236         if (currentMove == cmailOldMove &&
14237             commentList[cmailOldMove] != NULL &&
14238             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14239                    "Black offers a draw" : "White offers a draw")) {
14240 #ifdef NOTDEF
14241             AppendComment(cmailOldMove, "Draw declined", TRUE);
14242             DisplayComment(cmailOldMove - 1, "Draw declined");
14243 #endif /*NOTDEF*/
14244         } else {
14245             DisplayError(_("There is no pending offer on this move"), 0);
14246         }
14247     } else {
14248         /* Not used for offers from chess program */
14249     }
14250 }
14251
14252 void
14253 RematchEvent ()
14254 {
14255     /* Issue ICS rematch command */
14256     if (appData.icsActive) {
14257         SendToICS(ics_prefix);
14258         SendToICS("rematch\n");
14259     }
14260 }
14261
14262 void
14263 CallFlagEvent ()
14264 {
14265     /* Call your opponent's flag (claim a win on time) */
14266     if (appData.icsActive) {
14267         SendToICS(ics_prefix);
14268         SendToICS("flag\n");
14269     } else {
14270         switch (gameMode) {
14271           default:
14272             return;
14273           case MachinePlaysWhite:
14274             if (whiteFlag) {
14275                 if (blackFlag)
14276                   GameEnds(GameIsDrawn, "Both players ran out of time",
14277                            GE_PLAYER);
14278                 else
14279                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14280             } else {
14281                 DisplayError(_("Your opponent is not out of time"), 0);
14282             }
14283             break;
14284           case MachinePlaysBlack:
14285             if (blackFlag) {
14286                 if (whiteFlag)
14287                   GameEnds(GameIsDrawn, "Both players ran out of time",
14288                            GE_PLAYER);
14289                 else
14290                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14291             } else {
14292                 DisplayError(_("Your opponent is not out of time"), 0);
14293             }
14294             break;
14295         }
14296     }
14297 }
14298
14299 void
14300 ClockClick (int which)
14301 {       // [HGM] code moved to back-end from winboard.c
14302         if(which) { // black clock
14303           if (gameMode == EditPosition || gameMode == IcsExamining) {
14304             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14305             SetBlackToPlayEvent();
14306           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14307           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14308           } else if (shiftKey) {
14309             AdjustClock(which, -1);
14310           } else if (gameMode == IcsPlayingWhite ||
14311                      gameMode == MachinePlaysBlack) {
14312             CallFlagEvent();
14313           }
14314         } else { // white clock
14315           if (gameMode == EditPosition || gameMode == IcsExamining) {
14316             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14317             SetWhiteToPlayEvent();
14318           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14319           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14320           } else if (shiftKey) {
14321             AdjustClock(which, -1);
14322           } else if (gameMode == IcsPlayingBlack ||
14323                    gameMode == MachinePlaysWhite) {
14324             CallFlagEvent();
14325           }
14326         }
14327 }
14328
14329 void
14330 DrawEvent ()
14331 {
14332     /* Offer draw or accept pending draw offer from opponent */
14333
14334     if (appData.icsActive) {
14335         /* Note: tournament rules require draw offers to be
14336            made after you make your move but before you punch
14337            your clock.  Currently ICS doesn't let you do that;
14338            instead, you immediately punch your clock after making
14339            a move, but you can offer a draw at any time. */
14340
14341         SendToICS(ics_prefix);
14342         SendToICS("draw\n");
14343         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14344     } else if (cmailMsgLoaded) {
14345         if (currentMove == cmailOldMove &&
14346             commentList[cmailOldMove] != NULL &&
14347             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14348                    "Black offers a draw" : "White offers a draw")) {
14349             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14350             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14351         } else if (currentMove == cmailOldMove + 1) {
14352             char *offer = WhiteOnMove(cmailOldMove) ?
14353               "White offers a draw" : "Black offers a draw";
14354             AppendComment(currentMove, offer, TRUE);
14355             DisplayComment(currentMove - 1, offer);
14356             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14357         } else {
14358             DisplayError(_("You must make your move before offering a draw"), 0);
14359             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14360         }
14361     } else if (first.offeredDraw) {
14362         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14363     } else {
14364         if (first.sendDrawOffers) {
14365             SendToProgram("draw\n", &first);
14366             userOfferedDraw = TRUE;
14367         }
14368     }
14369 }
14370
14371 void
14372 AdjournEvent ()
14373 {
14374     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14375
14376     if (appData.icsActive) {
14377         SendToICS(ics_prefix);
14378         SendToICS("adjourn\n");
14379     } else {
14380         /* Currently GNU Chess doesn't offer or accept Adjourns */
14381     }
14382 }
14383
14384
14385 void
14386 AbortEvent ()
14387 {
14388     /* Offer Abort or accept pending Abort offer from opponent */
14389
14390     if (appData.icsActive) {
14391         SendToICS(ics_prefix);
14392         SendToICS("abort\n");
14393     } else {
14394         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14395     }
14396 }
14397
14398 void
14399 ResignEvent ()
14400 {
14401     /* Resign.  You can do this even if it's not your turn. */
14402
14403     if (appData.icsActive) {
14404         SendToICS(ics_prefix);
14405         SendToICS("resign\n");
14406     } else {
14407         switch (gameMode) {
14408           case MachinePlaysWhite:
14409             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14410             break;
14411           case MachinePlaysBlack:
14412             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14413             break;
14414           case EditGame:
14415             if (cmailMsgLoaded) {
14416                 TruncateGame();
14417                 if (WhiteOnMove(cmailOldMove)) {
14418                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14419                 } else {
14420                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14421                 }
14422                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14423             }
14424             break;
14425           default:
14426             break;
14427         }
14428     }
14429 }
14430
14431
14432 void
14433 StopObservingEvent ()
14434 {
14435     /* Stop observing current games */
14436     SendToICS(ics_prefix);
14437     SendToICS("unobserve\n");
14438 }
14439
14440 void
14441 StopExaminingEvent ()
14442 {
14443     /* Stop observing current game */
14444     SendToICS(ics_prefix);
14445     SendToICS("unexamine\n");
14446 }
14447
14448 void
14449 ForwardInner (int target)
14450 {
14451     int limit; int oldSeekGraphUp = seekGraphUp;
14452
14453     if (appData.debugMode)
14454         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14455                 target, currentMove, forwardMostMove);
14456
14457     if (gameMode == EditPosition)
14458       return;
14459
14460     seekGraphUp = FALSE;
14461     MarkTargetSquares(1);
14462
14463     if (gameMode == PlayFromGameFile && !pausing)
14464       PauseEvent();
14465
14466     if (gameMode == IcsExamining && pausing)
14467       limit = pauseExamForwardMostMove;
14468     else
14469       limit = forwardMostMove;
14470
14471     if (target > limit) target = limit;
14472
14473     if (target > 0 && moveList[target - 1][0]) {
14474         int fromX, fromY, toX, toY;
14475         toX = moveList[target - 1][2] - AAA;
14476         toY = moveList[target - 1][3] - ONE;
14477         if (moveList[target - 1][1] == '@') {
14478             if (appData.highlightLastMove) {
14479                 SetHighlights(-1, -1, toX, toY);
14480             }
14481         } else {
14482             fromX = moveList[target - 1][0] - AAA;
14483             fromY = moveList[target - 1][1] - ONE;
14484             if (target == currentMove + 1) {
14485                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14486             }
14487             if (appData.highlightLastMove) {
14488                 SetHighlights(fromX, fromY, toX, toY);
14489             }
14490         }
14491     }
14492     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14493         gameMode == Training || gameMode == PlayFromGameFile ||
14494         gameMode == AnalyzeFile) {
14495         while (currentMove < target) {
14496             SendMoveToProgram(currentMove++, &first);
14497         }
14498     } else {
14499         currentMove = target;
14500     }
14501
14502     if (gameMode == EditGame || gameMode == EndOfGame) {
14503         whiteTimeRemaining = timeRemaining[0][currentMove];
14504         blackTimeRemaining = timeRemaining[1][currentMove];
14505     }
14506     DisplayBothClocks();
14507     DisplayMove(currentMove - 1);
14508     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14509     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14510     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14511         DisplayComment(currentMove - 1, commentList[currentMove]);
14512     }
14513     ClearMap(); // [HGM] exclude: invalidate map
14514 }
14515
14516
14517 void
14518 ForwardEvent ()
14519 {
14520     if (gameMode == IcsExamining && !pausing) {
14521         SendToICS(ics_prefix);
14522         SendToICS("forward\n");
14523     } else {
14524         ForwardInner(currentMove + 1);
14525     }
14526 }
14527
14528 void
14529 ToEndEvent ()
14530 {
14531     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14532         /* to optimze, we temporarily turn off analysis mode while we feed
14533          * the remaining moves to the engine. Otherwise we get analysis output
14534          * after each move.
14535          */
14536         if (first.analysisSupport) {
14537           SendToProgram("exit\nforce\n", &first);
14538           first.analyzing = FALSE;
14539         }
14540     }
14541
14542     if (gameMode == IcsExamining && !pausing) {
14543         SendToICS(ics_prefix);
14544         SendToICS("forward 999999\n");
14545     } else {
14546         ForwardInner(forwardMostMove);
14547     }
14548
14549     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14550         /* we have fed all the moves, so reactivate analysis mode */
14551         SendToProgram("analyze\n", &first);
14552         first.analyzing = TRUE;
14553         /*first.maybeThinking = TRUE;*/
14554         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14555     }
14556 }
14557
14558 void
14559 BackwardInner (int target)
14560 {
14561     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14562
14563     if (appData.debugMode)
14564         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14565                 target, currentMove, forwardMostMove);
14566
14567     if (gameMode == EditPosition) return;
14568     seekGraphUp = FALSE;
14569     MarkTargetSquares(1);
14570     if (currentMove <= backwardMostMove) {
14571         ClearHighlights();
14572         DrawPosition(full_redraw, boards[currentMove]);
14573         return;
14574     }
14575     if (gameMode == PlayFromGameFile && !pausing)
14576       PauseEvent();
14577
14578     if (moveList[target][0]) {
14579         int fromX, fromY, toX, toY;
14580         toX = moveList[target][2] - AAA;
14581         toY = moveList[target][3] - ONE;
14582         if (moveList[target][1] == '@') {
14583             if (appData.highlightLastMove) {
14584                 SetHighlights(-1, -1, toX, toY);
14585             }
14586         } else {
14587             fromX = moveList[target][0] - AAA;
14588             fromY = moveList[target][1] - ONE;
14589             if (target == currentMove - 1) {
14590                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14591             }
14592             if (appData.highlightLastMove) {
14593                 SetHighlights(fromX, fromY, toX, toY);
14594             }
14595         }
14596     }
14597     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14598         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14599         while (currentMove > target) {
14600             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14601                 // null move cannot be undone. Reload program with move history before it.
14602                 int i;
14603                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14604                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14605                 }
14606                 SendBoard(&first, i); 
14607                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14608                 break;
14609             }
14610             SendToProgram("undo\n", &first);
14611             currentMove--;
14612         }
14613     } else {
14614         currentMove = target;
14615     }
14616
14617     if (gameMode == EditGame || gameMode == EndOfGame) {
14618         whiteTimeRemaining = timeRemaining[0][currentMove];
14619         blackTimeRemaining = timeRemaining[1][currentMove];
14620     }
14621     DisplayBothClocks();
14622     DisplayMove(currentMove - 1);
14623     DrawPosition(full_redraw, boards[currentMove]);
14624     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14625     // [HGM] PV info: routine tests if comment empty
14626     DisplayComment(currentMove - 1, commentList[currentMove]);
14627     ClearMap(); // [HGM] exclude: invalidate map
14628 }
14629
14630 void
14631 BackwardEvent ()
14632 {
14633     if (gameMode == IcsExamining && !pausing) {
14634         SendToICS(ics_prefix);
14635         SendToICS("backward\n");
14636     } else {
14637         BackwardInner(currentMove - 1);
14638     }
14639 }
14640
14641 void
14642 ToStartEvent ()
14643 {
14644     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14645         /* to optimize, we temporarily turn off analysis mode while we undo
14646          * all the moves. Otherwise we get analysis output after each undo.
14647          */
14648         if (first.analysisSupport) {
14649           SendToProgram("exit\nforce\n", &first);
14650           first.analyzing = FALSE;
14651         }
14652     }
14653
14654     if (gameMode == IcsExamining && !pausing) {
14655         SendToICS(ics_prefix);
14656         SendToICS("backward 999999\n");
14657     } else {
14658         BackwardInner(backwardMostMove);
14659     }
14660
14661     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14662         /* we have fed all the moves, so reactivate analysis mode */
14663         SendToProgram("analyze\n", &first);
14664         first.analyzing = TRUE;
14665         /*first.maybeThinking = TRUE;*/
14666         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14667     }
14668 }
14669
14670 void
14671 ToNrEvent (int to)
14672 {
14673   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14674   if (to >= forwardMostMove) to = forwardMostMove;
14675   if (to <= backwardMostMove) to = backwardMostMove;
14676   if (to < currentMove) {
14677     BackwardInner(to);
14678   } else {
14679     ForwardInner(to);
14680   }
14681 }
14682
14683 void
14684 RevertEvent (Boolean annotate)
14685 {
14686     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14687         return;
14688     }
14689     if (gameMode != IcsExamining) {
14690         DisplayError(_("You are not examining a game"), 0);
14691         return;
14692     }
14693     if (pausing) {
14694         DisplayError(_("You can't revert while pausing"), 0);
14695         return;
14696     }
14697     SendToICS(ics_prefix);
14698     SendToICS("revert\n");
14699 }
14700
14701 void
14702 RetractMoveEvent ()
14703 {
14704     switch (gameMode) {
14705       case MachinePlaysWhite:
14706       case MachinePlaysBlack:
14707         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14708             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14709             return;
14710         }
14711         if (forwardMostMove < 2) return;
14712         currentMove = forwardMostMove = forwardMostMove - 2;
14713         whiteTimeRemaining = timeRemaining[0][currentMove];
14714         blackTimeRemaining = timeRemaining[1][currentMove];
14715         DisplayBothClocks();
14716         DisplayMove(currentMove - 1);
14717         ClearHighlights();/*!! could figure this out*/
14718         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14719         SendToProgram("remove\n", &first);
14720         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14721         break;
14722
14723       case BeginningOfGame:
14724       default:
14725         break;
14726
14727       case IcsPlayingWhite:
14728       case IcsPlayingBlack:
14729         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14730             SendToICS(ics_prefix);
14731             SendToICS("takeback 2\n");
14732         } else {
14733             SendToICS(ics_prefix);
14734             SendToICS("takeback 1\n");
14735         }
14736         break;
14737     }
14738 }
14739
14740 void
14741 MoveNowEvent ()
14742 {
14743     ChessProgramState *cps;
14744
14745     switch (gameMode) {
14746       case MachinePlaysWhite:
14747         if (!WhiteOnMove(forwardMostMove)) {
14748             DisplayError(_("It is your turn"), 0);
14749             return;
14750         }
14751         cps = &first;
14752         break;
14753       case MachinePlaysBlack:
14754         if (WhiteOnMove(forwardMostMove)) {
14755             DisplayError(_("It is your turn"), 0);
14756             return;
14757         }
14758         cps = &first;
14759         break;
14760       case TwoMachinesPlay:
14761         if (WhiteOnMove(forwardMostMove) ==
14762             (first.twoMachinesColor[0] == 'w')) {
14763             cps = &first;
14764         } else {
14765             cps = &second;
14766         }
14767         break;
14768       case BeginningOfGame:
14769       default:
14770         return;
14771     }
14772     SendToProgram("?\n", cps);
14773 }
14774
14775 void
14776 TruncateGameEvent ()
14777 {
14778     EditGameEvent();
14779     if (gameMode != EditGame) return;
14780     TruncateGame();
14781 }
14782
14783 void
14784 TruncateGame ()
14785 {
14786     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14787     if (forwardMostMove > currentMove) {
14788         if (gameInfo.resultDetails != NULL) {
14789             free(gameInfo.resultDetails);
14790             gameInfo.resultDetails = NULL;
14791             gameInfo.result = GameUnfinished;
14792         }
14793         forwardMostMove = currentMove;
14794         HistorySet(parseList, backwardMostMove, forwardMostMove,
14795                    currentMove-1);
14796     }
14797 }
14798
14799 void
14800 HintEvent ()
14801 {
14802     if (appData.noChessProgram) return;
14803     switch (gameMode) {
14804       case MachinePlaysWhite:
14805         if (WhiteOnMove(forwardMostMove)) {
14806             DisplayError(_("Wait until your turn"), 0);
14807             return;
14808         }
14809         break;
14810       case BeginningOfGame:
14811       case MachinePlaysBlack:
14812         if (!WhiteOnMove(forwardMostMove)) {
14813             DisplayError(_("Wait until your turn"), 0);
14814             return;
14815         }
14816         break;
14817       default:
14818         DisplayError(_("No hint available"), 0);
14819         return;
14820     }
14821     SendToProgram("hint\n", &first);
14822     hintRequested = TRUE;
14823 }
14824
14825 void
14826 BookEvent ()
14827 {
14828     if (appData.noChessProgram) return;
14829     switch (gameMode) {
14830       case MachinePlaysWhite:
14831         if (WhiteOnMove(forwardMostMove)) {
14832             DisplayError(_("Wait until your turn"), 0);
14833             return;
14834         }
14835         break;
14836       case BeginningOfGame:
14837       case MachinePlaysBlack:
14838         if (!WhiteOnMove(forwardMostMove)) {
14839             DisplayError(_("Wait until your turn"), 0);
14840             return;
14841         }
14842         break;
14843       case EditPosition:
14844         EditPositionDone(TRUE);
14845         break;
14846       case TwoMachinesPlay:
14847         return;
14848       default:
14849         break;
14850     }
14851     SendToProgram("bk\n", &first);
14852     bookOutput[0] = NULLCHAR;
14853     bookRequested = TRUE;
14854 }
14855
14856 void
14857 AboutGameEvent ()
14858 {
14859     char *tags = PGNTags(&gameInfo);
14860     TagsPopUp(tags, CmailMsg());
14861     free(tags);
14862 }
14863
14864 /* end button procedures */
14865
14866 void
14867 PrintPosition (FILE *fp, int move)
14868 {
14869     int i, j;
14870
14871     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14872         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14873             char c = PieceToChar(boards[move][i][j]);
14874             fputc(c == 'x' ? '.' : c, fp);
14875             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14876         }
14877     }
14878     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14879       fprintf(fp, "white to play\n");
14880     else
14881       fprintf(fp, "black to play\n");
14882 }
14883
14884 void
14885 PrintOpponents (FILE *fp)
14886 {
14887     if (gameInfo.white != NULL) {
14888         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14889     } else {
14890         fprintf(fp, "\n");
14891     }
14892 }
14893
14894 /* Find last component of program's own name, using some heuristics */
14895 void
14896 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14897 {
14898     char *p, *q, c;
14899     int local = (strcmp(host, "localhost") == 0);
14900     while (!local && (p = strchr(prog, ';')) != NULL) {
14901         p++;
14902         while (*p == ' ') p++;
14903         prog = p;
14904     }
14905     if (*prog == '"' || *prog == '\'') {
14906         q = strchr(prog + 1, *prog);
14907     } else {
14908         q = strchr(prog, ' ');
14909     }
14910     if (q == NULL) q = prog + strlen(prog);
14911     p = q;
14912     while (p >= prog && *p != '/' && *p != '\\') p--;
14913     p++;
14914     if(p == prog && *p == '"') p++;
14915     c = *q; *q = 0;
14916     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14917     memcpy(buf, p, q - p);
14918     buf[q - p] = NULLCHAR;
14919     if (!local) {
14920         strcat(buf, "@");
14921         strcat(buf, host);
14922     }
14923 }
14924
14925 char *
14926 TimeControlTagValue ()
14927 {
14928     char buf[MSG_SIZ];
14929     if (!appData.clockMode) {
14930       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14931     } else if (movesPerSession > 0) {
14932       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14933     } else if (timeIncrement == 0) {
14934       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14935     } else {
14936       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14937     }
14938     return StrSave(buf);
14939 }
14940
14941 void
14942 SetGameInfo ()
14943 {
14944     /* This routine is used only for certain modes */
14945     VariantClass v = gameInfo.variant;
14946     ChessMove r = GameUnfinished;
14947     char *p = NULL;
14948
14949     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14950         r = gameInfo.result;
14951         p = gameInfo.resultDetails;
14952         gameInfo.resultDetails = NULL;
14953     }
14954     ClearGameInfo(&gameInfo);
14955     gameInfo.variant = v;
14956
14957     switch (gameMode) {
14958       case MachinePlaysWhite:
14959         gameInfo.event = StrSave( appData.pgnEventHeader );
14960         gameInfo.site = StrSave(HostName());
14961         gameInfo.date = PGNDate();
14962         gameInfo.round = StrSave("-");
14963         gameInfo.white = StrSave(first.tidy);
14964         gameInfo.black = StrSave(UserName());
14965         gameInfo.timeControl = TimeControlTagValue();
14966         break;
14967
14968       case MachinePlaysBlack:
14969         gameInfo.event = StrSave( appData.pgnEventHeader );
14970         gameInfo.site = StrSave(HostName());
14971         gameInfo.date = PGNDate();
14972         gameInfo.round = StrSave("-");
14973         gameInfo.white = StrSave(UserName());
14974         gameInfo.black = StrSave(first.tidy);
14975         gameInfo.timeControl = TimeControlTagValue();
14976         break;
14977
14978       case TwoMachinesPlay:
14979         gameInfo.event = StrSave( appData.pgnEventHeader );
14980         gameInfo.site = StrSave(HostName());
14981         gameInfo.date = PGNDate();
14982         if (roundNr > 0) {
14983             char buf[MSG_SIZ];
14984             snprintf(buf, MSG_SIZ, "%d", roundNr);
14985             gameInfo.round = StrSave(buf);
14986         } else {
14987             gameInfo.round = StrSave("-");
14988         }
14989         if (first.twoMachinesColor[0] == 'w') {
14990             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14991             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14992         } else {
14993             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14994             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14995         }
14996         gameInfo.timeControl = TimeControlTagValue();
14997         break;
14998
14999       case EditGame:
15000         gameInfo.event = StrSave("Edited game");
15001         gameInfo.site = StrSave(HostName());
15002         gameInfo.date = PGNDate();
15003         gameInfo.round = StrSave("-");
15004         gameInfo.white = StrSave("-");
15005         gameInfo.black = StrSave("-");
15006         gameInfo.result = r;
15007         gameInfo.resultDetails = p;
15008         break;
15009
15010       case EditPosition:
15011         gameInfo.event = StrSave("Edited position");
15012         gameInfo.site = StrSave(HostName());
15013         gameInfo.date = PGNDate();
15014         gameInfo.round = StrSave("-");
15015         gameInfo.white = StrSave("-");
15016         gameInfo.black = StrSave("-");
15017         break;
15018
15019       case IcsPlayingWhite:
15020       case IcsPlayingBlack:
15021       case IcsObserving:
15022       case IcsExamining:
15023         break;
15024
15025       case PlayFromGameFile:
15026         gameInfo.event = StrSave("Game from non-PGN file");
15027         gameInfo.site = StrSave(HostName());
15028         gameInfo.date = PGNDate();
15029         gameInfo.round = StrSave("-");
15030         gameInfo.white = StrSave("?");
15031         gameInfo.black = StrSave("?");
15032         break;
15033
15034       default:
15035         break;
15036     }
15037 }
15038
15039 void
15040 ReplaceComment (int index, char *text)
15041 {
15042     int len;
15043     char *p;
15044     float score;
15045
15046     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15047        pvInfoList[index-1].depth == len &&
15048        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15049        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15050     while (*text == '\n') text++;
15051     len = strlen(text);
15052     while (len > 0 && text[len - 1] == '\n') len--;
15053
15054     if (commentList[index] != NULL)
15055       free(commentList[index]);
15056
15057     if (len == 0) {
15058         commentList[index] = NULL;
15059         return;
15060     }
15061   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15062       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15063       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15064     commentList[index] = (char *) malloc(len + 2);
15065     strncpy(commentList[index], text, len);
15066     commentList[index][len] = '\n';
15067     commentList[index][len + 1] = NULLCHAR;
15068   } else {
15069     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15070     char *p;
15071     commentList[index] = (char *) malloc(len + 7);
15072     safeStrCpy(commentList[index], "{\n", 3);
15073     safeStrCpy(commentList[index]+2, text, len+1);
15074     commentList[index][len+2] = NULLCHAR;
15075     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15076     strcat(commentList[index], "\n}\n");
15077   }
15078 }
15079
15080 void
15081 CrushCRs (char *text)
15082 {
15083   char *p = text;
15084   char *q = text;
15085   char ch;
15086
15087   do {
15088     ch = *p++;
15089     if (ch == '\r') continue;
15090     *q++ = ch;
15091   } while (ch != '\0');
15092 }
15093
15094 void
15095 AppendComment (int index, char *text, Boolean addBraces)
15096 /* addBraces  tells if we should add {} */
15097 {
15098     int oldlen, len;
15099     char *old;
15100
15101 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15102     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15103
15104     CrushCRs(text);
15105     while (*text == '\n') text++;
15106     len = strlen(text);
15107     while (len > 0 && text[len - 1] == '\n') len--;
15108     text[len] = NULLCHAR;
15109
15110     if (len == 0) return;
15111
15112     if (commentList[index] != NULL) {
15113       Boolean addClosingBrace = addBraces;
15114         old = commentList[index];
15115         oldlen = strlen(old);
15116         while(commentList[index][oldlen-1] ==  '\n')
15117           commentList[index][--oldlen] = NULLCHAR;
15118         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15119         safeStrCpy(commentList[index], old, oldlen + len + 6);
15120         free(old);
15121         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15122         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15123           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15124           while (*text == '\n') { text++; len--; }
15125           commentList[index][--oldlen] = NULLCHAR;
15126       }
15127         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15128         else          strcat(commentList[index], "\n");
15129         strcat(commentList[index], text);
15130         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15131         else          strcat(commentList[index], "\n");
15132     } else {
15133         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15134         if(addBraces)
15135           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15136         else commentList[index][0] = NULLCHAR;
15137         strcat(commentList[index], text);
15138         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15139         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15140     }
15141 }
15142
15143 static char *
15144 FindStr (char * text, char * sub_text)
15145 {
15146     char * result = strstr( text, sub_text );
15147
15148     if( result != NULL ) {
15149         result += strlen( sub_text );
15150     }
15151
15152     return result;
15153 }
15154
15155 /* [AS] Try to extract PV info from PGN comment */
15156 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15157 char *
15158 GetInfoFromComment (int index, char * text)
15159 {
15160     char * sep = text, *p;
15161
15162     if( text != NULL && index > 0 ) {
15163         int score = 0;
15164         int depth = 0;
15165         int time = -1, sec = 0, deci;
15166         char * s_eval = FindStr( text, "[%eval " );
15167         char * s_emt = FindStr( text, "[%emt " );
15168
15169         if( s_eval != NULL || s_emt != NULL ) {
15170             /* New style */
15171             char delim;
15172
15173             if( s_eval != NULL ) {
15174                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15175                     return text;
15176                 }
15177
15178                 if( delim != ']' ) {
15179                     return text;
15180                 }
15181             }
15182
15183             if( s_emt != NULL ) {
15184             }
15185                 return text;
15186         }
15187         else {
15188             /* We expect something like: [+|-]nnn.nn/dd */
15189             int score_lo = 0;
15190
15191             if(*text != '{') return text; // [HGM] braces: must be normal comment
15192
15193             sep = strchr( text, '/' );
15194             if( sep == NULL || sep < (text+4) ) {
15195                 return text;
15196             }
15197
15198             p = text;
15199             if(p[1] == '(') { // comment starts with PV
15200                p = strchr(p, ')'); // locate end of PV
15201                if(p == NULL || sep < p+5) return text;
15202                // at this point we have something like "{(.*) +0.23/6 ..."
15203                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15204                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15205                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15206             }
15207             time = -1; sec = -1; deci = -1;
15208             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15209                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15210                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15211                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15212                 return text;
15213             }
15214
15215             if( score_lo < 0 || score_lo >= 100 ) {
15216                 return text;
15217             }
15218
15219             if(sec >= 0) time = 600*time + 10*sec; else
15220             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15221
15222             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15223
15224             /* [HGM] PV time: now locate end of PV info */
15225             while( *++sep >= '0' && *sep <= '9'); // strip depth
15226             if(time >= 0)
15227             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15228             if(sec >= 0)
15229             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15230             if(deci >= 0)
15231             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15232             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15233         }
15234
15235         if( depth <= 0 ) {
15236             return text;
15237         }
15238
15239         if( time < 0 ) {
15240             time = -1;
15241         }
15242
15243         pvInfoList[index-1].depth = depth;
15244         pvInfoList[index-1].score = score;
15245         pvInfoList[index-1].time  = 10*time; // centi-sec
15246         if(*sep == '}') *sep = 0; else *--sep = '{';
15247         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15248     }
15249     return sep;
15250 }
15251
15252 void
15253 SendToProgram (char *message, ChessProgramState *cps)
15254 {
15255     int count, outCount, error;
15256     char buf[MSG_SIZ];
15257
15258     if (cps->pr == NoProc) return;
15259     Attention(cps);
15260
15261     if (appData.debugMode) {
15262         TimeMark now;
15263         GetTimeMark(&now);
15264         fprintf(debugFP, "%ld >%-6s: %s",
15265                 SubtractTimeMarks(&now, &programStartTime),
15266                 cps->which, message);
15267         if(serverFP)
15268             fprintf(serverFP, "%ld >%-6s: %s",
15269                 SubtractTimeMarks(&now, &programStartTime),
15270                 cps->which, message), fflush(serverFP);
15271     }
15272
15273     count = strlen(message);
15274     outCount = OutputToProcess(cps->pr, message, count, &error);
15275     if (outCount < count && !exiting
15276                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15277       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15278       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15279         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15280             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15281                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15282                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15283                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15284             } else {
15285                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15286                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15287                 gameInfo.result = res;
15288             }
15289             gameInfo.resultDetails = StrSave(buf);
15290         }
15291         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15292         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15293     }
15294 }
15295
15296 void
15297 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15298 {
15299     char *end_str;
15300     char buf[MSG_SIZ];
15301     ChessProgramState *cps = (ChessProgramState *)closure;
15302
15303     if (isr != cps->isr) return; /* Killed intentionally */
15304     if (count <= 0) {
15305         if (count == 0) {
15306             RemoveInputSource(cps->isr);
15307             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15308                     _(cps->which), cps->program);
15309             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15310             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15311                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15312                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15313                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15314                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15315                 } else {
15316                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15317                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15318                     gameInfo.result = res;
15319                 }
15320                 gameInfo.resultDetails = StrSave(buf);
15321             }
15322             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15323             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15324         } else {
15325             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15326                     _(cps->which), cps->program);
15327             RemoveInputSource(cps->isr);
15328
15329             /* [AS] Program is misbehaving badly... kill it */
15330             if( count == -2 ) {
15331                 DestroyChildProcess( cps->pr, 9 );
15332                 cps->pr = NoProc;
15333             }
15334
15335             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15336         }
15337         return;
15338     }
15339
15340     if ((end_str = strchr(message, '\r')) != NULL)
15341       *end_str = NULLCHAR;
15342     if ((end_str = strchr(message, '\n')) != NULL)
15343       *end_str = NULLCHAR;
15344
15345     if (appData.debugMode) {
15346         TimeMark now; int print = 1;
15347         char *quote = ""; char c; int i;
15348
15349         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15350                 char start = message[0];
15351                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15352                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15353                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15354                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15355                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15356                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15357                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15358                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15359                    sscanf(message, "hint: %c", &c)!=1 && 
15360                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15361                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15362                     print = (appData.engineComments >= 2);
15363                 }
15364                 message[0] = start; // restore original message
15365         }
15366         if(print) {
15367                 GetTimeMark(&now);
15368                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15369                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15370                         quote,
15371                         message);
15372                 if(serverFP)
15373                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15374                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15375                         quote,
15376                         message), fflush(serverFP);
15377         }
15378     }
15379
15380     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15381     if (appData.icsEngineAnalyze) {
15382         if (strstr(message, "whisper") != NULL ||
15383              strstr(message, "kibitz") != NULL ||
15384             strstr(message, "tellics") != NULL) return;
15385     }
15386
15387     HandleMachineMove(message, cps);
15388 }
15389
15390
15391 void
15392 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15393 {
15394     char buf[MSG_SIZ];
15395     int seconds;
15396
15397     if( timeControl_2 > 0 ) {
15398         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15399             tc = timeControl_2;
15400         }
15401     }
15402     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15403     inc /= cps->timeOdds;
15404     st  /= cps->timeOdds;
15405
15406     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15407
15408     if (st > 0) {
15409       /* Set exact time per move, normally using st command */
15410       if (cps->stKludge) {
15411         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15412         seconds = st % 60;
15413         if (seconds == 0) {
15414           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15415         } else {
15416           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15417         }
15418       } else {
15419         snprintf(buf, MSG_SIZ, "st %d\n", st);
15420       }
15421     } else {
15422       /* Set conventional or incremental time control, using level command */
15423       if (seconds == 0) {
15424         /* Note old gnuchess bug -- minutes:seconds used to not work.
15425            Fixed in later versions, but still avoid :seconds
15426            when seconds is 0. */
15427         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15428       } else {
15429         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15430                  seconds, inc/1000.);
15431       }
15432     }
15433     SendToProgram(buf, cps);
15434
15435     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15436     /* Orthogonally, limit search to given depth */
15437     if (sd > 0) {
15438       if (cps->sdKludge) {
15439         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15440       } else {
15441         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15442       }
15443       SendToProgram(buf, cps);
15444     }
15445
15446     if(cps->nps >= 0) { /* [HGM] nps */
15447         if(cps->supportsNPS == FALSE)
15448           cps->nps = -1; // don't use if engine explicitly says not supported!
15449         else {
15450           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15451           SendToProgram(buf, cps);
15452         }
15453     }
15454 }
15455
15456 ChessProgramState *
15457 WhitePlayer ()
15458 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15459 {
15460     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15461        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15462         return &second;
15463     return &first;
15464 }
15465
15466 void
15467 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15468 {
15469     char message[MSG_SIZ];
15470     long time, otime;
15471
15472     /* Note: this routine must be called when the clocks are stopped
15473        or when they have *just* been set or switched; otherwise
15474        it will be off by the time since the current tick started.
15475     */
15476     if (machineWhite) {
15477         time = whiteTimeRemaining / 10;
15478         otime = blackTimeRemaining / 10;
15479     } else {
15480         time = blackTimeRemaining / 10;
15481         otime = whiteTimeRemaining / 10;
15482     }
15483     /* [HGM] translate opponent's time by time-odds factor */
15484     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15485
15486     if (time <= 0) time = 1;
15487     if (otime <= 0) otime = 1;
15488
15489     snprintf(message, MSG_SIZ, "time %ld\n", time);
15490     SendToProgram(message, cps);
15491
15492     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15493     SendToProgram(message, cps);
15494 }
15495
15496 int
15497 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15498 {
15499   char buf[MSG_SIZ];
15500   int len = strlen(name);
15501   int val;
15502
15503   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15504     (*p) += len + 1;
15505     sscanf(*p, "%d", &val);
15506     *loc = (val != 0);
15507     while (**p && **p != ' ')
15508       (*p)++;
15509     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15510     SendToProgram(buf, cps);
15511     return TRUE;
15512   }
15513   return FALSE;
15514 }
15515
15516 int
15517 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15518 {
15519   char buf[MSG_SIZ];
15520   int len = strlen(name);
15521   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15522     (*p) += len + 1;
15523     sscanf(*p, "%d", loc);
15524     while (**p && **p != ' ') (*p)++;
15525     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15526     SendToProgram(buf, cps);
15527     return TRUE;
15528   }
15529   return FALSE;
15530 }
15531
15532 int
15533 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15534 {
15535   char buf[MSG_SIZ];
15536   int len = strlen(name);
15537   if (strncmp((*p), name, len) == 0
15538       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15539     (*p) += len + 2;
15540     sscanf(*p, "%[^\"]", loc);
15541     while (**p && **p != '\"') (*p)++;
15542     if (**p == '\"') (*p)++;
15543     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15544     SendToProgram(buf, cps);
15545     return TRUE;
15546   }
15547   return FALSE;
15548 }
15549
15550 int
15551 ParseOption (Option *opt, ChessProgramState *cps)
15552 // [HGM] options: process the string that defines an engine option, and determine
15553 // name, type, default value, and allowed value range
15554 {
15555         char *p, *q, buf[MSG_SIZ];
15556         int n, min = (-1)<<31, max = 1<<31, def;
15557
15558         if(p = strstr(opt->name, " -spin ")) {
15559             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15560             if(max < min) max = min; // enforce consistency
15561             if(def < min) def = min;
15562             if(def > max) def = max;
15563             opt->value = def;
15564             opt->min = min;
15565             opt->max = max;
15566             opt->type = Spin;
15567         } else if((p = strstr(opt->name, " -slider "))) {
15568             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15569             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15570             if(max < min) max = min; // enforce consistency
15571             if(def < min) def = min;
15572             if(def > max) def = max;
15573             opt->value = def;
15574             opt->min = min;
15575             opt->max = max;
15576             opt->type = Spin; // Slider;
15577         } else if((p = strstr(opt->name, " -string "))) {
15578             opt->textValue = p+9;
15579             opt->type = TextBox;
15580         } else if((p = strstr(opt->name, " -file "))) {
15581             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15582             opt->textValue = p+7;
15583             opt->type = FileName; // FileName;
15584         } else if((p = strstr(opt->name, " -path "))) {
15585             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15586             opt->textValue = p+7;
15587             opt->type = PathName; // PathName;
15588         } else if(p = strstr(opt->name, " -check ")) {
15589             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15590             opt->value = (def != 0);
15591             opt->type = CheckBox;
15592         } else if(p = strstr(opt->name, " -combo ")) {
15593             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15594             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15595             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15596             opt->value = n = 0;
15597             while(q = StrStr(q, " /// ")) {
15598                 n++; *q = 0;    // count choices, and null-terminate each of them
15599                 q += 5;
15600                 if(*q == '*') { // remember default, which is marked with * prefix
15601                     q++;
15602                     opt->value = n;
15603                 }
15604                 cps->comboList[cps->comboCnt++] = q;
15605             }
15606             cps->comboList[cps->comboCnt++] = NULL;
15607             opt->max = n + 1;
15608             opt->type = ComboBox;
15609         } else if(p = strstr(opt->name, " -button")) {
15610             opt->type = Button;
15611         } else if(p = strstr(opt->name, " -save")) {
15612             opt->type = SaveButton;
15613         } else return FALSE;
15614         *p = 0; // terminate option name
15615         // now look if the command-line options define a setting for this engine option.
15616         if(cps->optionSettings && cps->optionSettings[0])
15617             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15618         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15619           snprintf(buf, MSG_SIZ, "option %s", p);
15620                 if(p = strstr(buf, ",")) *p = 0;
15621                 if(q = strchr(buf, '=')) switch(opt->type) {
15622                     case ComboBox:
15623                         for(n=0; n<opt->max; n++)
15624                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15625                         break;
15626                     case TextBox:
15627                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15628                         break;
15629                     case Spin:
15630                     case CheckBox:
15631                         opt->value = atoi(q+1);
15632                     default:
15633                         break;
15634                 }
15635                 strcat(buf, "\n");
15636                 SendToProgram(buf, cps);
15637         }
15638         return TRUE;
15639 }
15640
15641 void
15642 FeatureDone (ChessProgramState *cps, int val)
15643 {
15644   DelayedEventCallback cb = GetDelayedEvent();
15645   if ((cb == InitBackEnd3 && cps == &first) ||
15646       (cb == SettingsMenuIfReady && cps == &second) ||
15647       (cb == LoadEngine) ||
15648       (cb == TwoMachinesEventIfReady)) {
15649     CancelDelayedEvent();
15650     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15651   }
15652   cps->initDone = val;
15653 }
15654
15655 /* Parse feature command from engine */
15656 void
15657 ParseFeatures (char *args, ChessProgramState *cps)
15658 {
15659   char *p = args;
15660   char *q;
15661   int val;
15662   char buf[MSG_SIZ];
15663
15664   for (;;) {
15665     while (*p == ' ') p++;
15666     if (*p == NULLCHAR) return;
15667
15668     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15669     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15670     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15671     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15672     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15673     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15674     if (BoolFeature(&p, "reuse", &val, cps)) {
15675       /* Engine can disable reuse, but can't enable it if user said no */
15676       if (!val) cps->reuse = FALSE;
15677       continue;
15678     }
15679     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15680     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15681       if (gameMode == TwoMachinesPlay) {
15682         DisplayTwoMachinesTitle();
15683       } else {
15684         DisplayTitle("");
15685       }
15686       continue;
15687     }
15688     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15689     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15690     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15691     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15692     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15693     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15694     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15695     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15696     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15697     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15698     if (IntFeature(&p, "done", &val, cps)) {
15699       FeatureDone(cps, val);
15700       continue;
15701     }
15702     /* Added by Tord: */
15703     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15704     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15705     /* End of additions by Tord */
15706
15707     /* [HGM] added features: */
15708     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15709     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15710     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15711     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15712     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15713     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15714     if (StringFeature(&p, "option", buf, cps)) {
15715         FREE(cps->option[cps->nrOptions].name);
15716         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15717         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15718         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15719           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15720             SendToProgram(buf, cps);
15721             continue;
15722         }
15723         if(cps->nrOptions >= MAX_OPTIONS) {
15724             cps->nrOptions--;
15725             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15726             DisplayError(buf, 0);
15727         }
15728         continue;
15729     }
15730     /* End of additions by HGM */
15731
15732     /* unknown feature: complain and skip */
15733     q = p;
15734     while (*q && *q != '=') q++;
15735     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15736     SendToProgram(buf, cps);
15737     p = q;
15738     if (*p == '=') {
15739       p++;
15740       if (*p == '\"') {
15741         p++;
15742         while (*p && *p != '\"') p++;
15743         if (*p == '\"') p++;
15744       } else {
15745         while (*p && *p != ' ') p++;
15746       }
15747     }
15748   }
15749
15750 }
15751
15752 void
15753 PeriodicUpdatesEvent (int newState)
15754 {
15755     if (newState == appData.periodicUpdates)
15756       return;
15757
15758     appData.periodicUpdates=newState;
15759
15760     /* Display type changes, so update it now */
15761 //    DisplayAnalysis();
15762
15763     /* Get the ball rolling again... */
15764     if (newState) {
15765         AnalysisPeriodicEvent(1);
15766         StartAnalysisClock();
15767     }
15768 }
15769
15770 void
15771 PonderNextMoveEvent (int newState)
15772 {
15773     if (newState == appData.ponderNextMove) return;
15774     if (gameMode == EditPosition) EditPositionDone(TRUE);
15775     if (newState) {
15776         SendToProgram("hard\n", &first);
15777         if (gameMode == TwoMachinesPlay) {
15778             SendToProgram("hard\n", &second);
15779         }
15780     } else {
15781         SendToProgram("easy\n", &first);
15782         thinkOutput[0] = NULLCHAR;
15783         if (gameMode == TwoMachinesPlay) {
15784             SendToProgram("easy\n", &second);
15785         }
15786     }
15787     appData.ponderNextMove = newState;
15788 }
15789
15790 void
15791 NewSettingEvent (int option, int *feature, char *command, int value)
15792 {
15793     char buf[MSG_SIZ];
15794
15795     if (gameMode == EditPosition) EditPositionDone(TRUE);
15796     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15797     if(feature == NULL || *feature) SendToProgram(buf, &first);
15798     if (gameMode == TwoMachinesPlay) {
15799         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15800     }
15801 }
15802
15803 void
15804 ShowThinkingEvent ()
15805 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15806 {
15807     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15808     int newState = appData.showThinking
15809         // [HGM] thinking: other features now need thinking output as well
15810         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15811
15812     if (oldState == newState) return;
15813     oldState = newState;
15814     if (gameMode == EditPosition) EditPositionDone(TRUE);
15815     if (oldState) {
15816         SendToProgram("post\n", &first);
15817         if (gameMode == TwoMachinesPlay) {
15818             SendToProgram("post\n", &second);
15819         }
15820     } else {
15821         SendToProgram("nopost\n", &first);
15822         thinkOutput[0] = NULLCHAR;
15823         if (gameMode == TwoMachinesPlay) {
15824             SendToProgram("nopost\n", &second);
15825         }
15826     }
15827 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15828 }
15829
15830 void
15831 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15832 {
15833   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15834   if (pr == NoProc) return;
15835   AskQuestion(title, question, replyPrefix, pr);
15836 }
15837
15838 void
15839 TypeInEvent (char firstChar)
15840 {
15841     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15842         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15843         gameMode == AnalyzeMode || gameMode == EditGame || 
15844         gameMode == EditPosition || gameMode == IcsExamining ||
15845         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15846         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15847                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15848                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15849         gameMode == Training) PopUpMoveDialog(firstChar);
15850 }
15851
15852 void
15853 TypeInDoneEvent (char *move)
15854 {
15855         Board board;
15856         int n, fromX, fromY, toX, toY;
15857         char promoChar;
15858         ChessMove moveType;
15859
15860         // [HGM] FENedit
15861         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15862                 EditPositionPasteFEN(move);
15863                 return;
15864         }
15865         // [HGM] movenum: allow move number to be typed in any mode
15866         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15867           ToNrEvent(2*n-1);
15868           return;
15869         }
15870         // undocumented kludge: allow command-line option to be typed in!
15871         // (potentially fatal, and does not implement the effect of the option.)
15872         // should only be used for options that are values on which future decisions will be made,
15873         // and definitely not on options that would be used during initialization.
15874         if(strstr(move, "!!! -") == move) {
15875             ParseArgsFromString(move+4);
15876             return;
15877         }
15878
15879       if (gameMode != EditGame && currentMove != forwardMostMove && 
15880         gameMode != Training) {
15881         DisplayMoveError(_("Displayed move is not current"));
15882       } else {
15883         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15884           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15885         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15886         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15887           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15888           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15889         } else {
15890           DisplayMoveError(_("Could not parse move"));
15891         }
15892       }
15893 }
15894
15895 void
15896 DisplayMove (int moveNumber)
15897 {
15898     char message[MSG_SIZ];
15899     char res[MSG_SIZ];
15900     char cpThinkOutput[MSG_SIZ];
15901
15902     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15903
15904     if (moveNumber == forwardMostMove - 1 ||
15905         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15906
15907         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15908
15909         if (strchr(cpThinkOutput, '\n')) {
15910             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15911         }
15912     } else {
15913         *cpThinkOutput = NULLCHAR;
15914     }
15915
15916     /* [AS] Hide thinking from human user */
15917     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15918         *cpThinkOutput = NULLCHAR;
15919         if( thinkOutput[0] != NULLCHAR ) {
15920             int i;
15921
15922             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15923                 cpThinkOutput[i] = '.';
15924             }
15925             cpThinkOutput[i] = NULLCHAR;
15926             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15927         }
15928     }
15929
15930     if (moveNumber == forwardMostMove - 1 &&
15931         gameInfo.resultDetails != NULL) {
15932         if (gameInfo.resultDetails[0] == NULLCHAR) {
15933           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15934         } else {
15935           snprintf(res, MSG_SIZ, " {%s} %s",
15936                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15937         }
15938     } else {
15939         res[0] = NULLCHAR;
15940     }
15941
15942     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15943         DisplayMessage(res, cpThinkOutput);
15944     } else {
15945       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15946                 WhiteOnMove(moveNumber) ? " " : ".. ",
15947                 parseList[moveNumber], res);
15948         DisplayMessage(message, cpThinkOutput);
15949     }
15950 }
15951
15952 void
15953 DisplayComment (int moveNumber, char *text)
15954 {
15955     char title[MSG_SIZ];
15956
15957     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15958       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15959     } else {
15960       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15961               WhiteOnMove(moveNumber) ? " " : ".. ",
15962               parseList[moveNumber]);
15963     }
15964     if (text != NULL && (appData.autoDisplayComment || commentUp))
15965         CommentPopUp(title, text);
15966 }
15967
15968 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15969  * might be busy thinking or pondering.  It can be omitted if your
15970  * gnuchess is configured to stop thinking immediately on any user
15971  * input.  However, that gnuchess feature depends on the FIONREAD
15972  * ioctl, which does not work properly on some flavors of Unix.
15973  */
15974 void
15975 Attention (ChessProgramState *cps)
15976 {
15977 #if ATTENTION
15978     if (!cps->useSigint) return;
15979     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15980     switch (gameMode) {
15981       case MachinePlaysWhite:
15982       case MachinePlaysBlack:
15983       case TwoMachinesPlay:
15984       case IcsPlayingWhite:
15985       case IcsPlayingBlack:
15986       case AnalyzeMode:
15987       case AnalyzeFile:
15988         /* Skip if we know it isn't thinking */
15989         if (!cps->maybeThinking) return;
15990         if (appData.debugMode)
15991           fprintf(debugFP, "Interrupting %s\n", cps->which);
15992         InterruptChildProcess(cps->pr);
15993         cps->maybeThinking = FALSE;
15994         break;
15995       default:
15996         break;
15997     }
15998 #endif /*ATTENTION*/
15999 }
16000
16001 int
16002 CheckFlags ()
16003 {
16004     if (whiteTimeRemaining <= 0) {
16005         if (!whiteFlag) {
16006             whiteFlag = TRUE;
16007             if (appData.icsActive) {
16008                 if (appData.autoCallFlag &&
16009                     gameMode == IcsPlayingBlack && !blackFlag) {
16010                   SendToICS(ics_prefix);
16011                   SendToICS("flag\n");
16012                 }
16013             } else {
16014                 if (blackFlag) {
16015                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16016                 } else {
16017                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16018                     if (appData.autoCallFlag) {
16019                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16020                         return TRUE;
16021                     }
16022                 }
16023             }
16024         }
16025     }
16026     if (blackTimeRemaining <= 0) {
16027         if (!blackFlag) {
16028             blackFlag = TRUE;
16029             if (appData.icsActive) {
16030                 if (appData.autoCallFlag &&
16031                     gameMode == IcsPlayingWhite && !whiteFlag) {
16032                   SendToICS(ics_prefix);
16033                   SendToICS("flag\n");
16034                 }
16035             } else {
16036                 if (whiteFlag) {
16037                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16038                 } else {
16039                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16040                     if (appData.autoCallFlag) {
16041                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16042                         return TRUE;
16043                     }
16044                 }
16045             }
16046         }
16047     }
16048     return FALSE;
16049 }
16050
16051 void
16052 CheckTimeControl ()
16053 {
16054     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16055         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16056
16057     /*
16058      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16059      */
16060     if ( !WhiteOnMove(forwardMostMove) ) {
16061         /* White made time control */
16062         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16063         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16064         /* [HGM] time odds: correct new time quota for time odds! */
16065                                             / WhitePlayer()->timeOdds;
16066         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16067     } else {
16068         lastBlack -= blackTimeRemaining;
16069         /* Black made time control */
16070         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16071                                             / WhitePlayer()->other->timeOdds;
16072         lastWhite = whiteTimeRemaining;
16073     }
16074 }
16075
16076 void
16077 DisplayBothClocks ()
16078 {
16079     int wom = gameMode == EditPosition ?
16080       !blackPlaysFirst : WhiteOnMove(currentMove);
16081     DisplayWhiteClock(whiteTimeRemaining, wom);
16082     DisplayBlackClock(blackTimeRemaining, !wom);
16083 }
16084
16085
16086 /* Timekeeping seems to be a portability nightmare.  I think everyone
16087    has ftime(), but I'm really not sure, so I'm including some ifdefs
16088    to use other calls if you don't.  Clocks will be less accurate if
16089    you have neither ftime nor gettimeofday.
16090 */
16091
16092 /* VS 2008 requires the #include outside of the function */
16093 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16094 #include <sys/timeb.h>
16095 #endif
16096
16097 /* Get the current time as a TimeMark */
16098 void
16099 GetTimeMark (TimeMark *tm)
16100 {
16101 #if HAVE_GETTIMEOFDAY
16102
16103     struct timeval timeVal;
16104     struct timezone timeZone;
16105
16106     gettimeofday(&timeVal, &timeZone);
16107     tm->sec = (long) timeVal.tv_sec;
16108     tm->ms = (int) (timeVal.tv_usec / 1000L);
16109
16110 #else /*!HAVE_GETTIMEOFDAY*/
16111 #if HAVE_FTIME
16112
16113 // include <sys/timeb.h> / moved to just above start of function
16114     struct timeb timeB;
16115
16116     ftime(&timeB);
16117     tm->sec = (long) timeB.time;
16118     tm->ms = (int) timeB.millitm;
16119
16120 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16121     tm->sec = (long) time(NULL);
16122     tm->ms = 0;
16123 #endif
16124 #endif
16125 }
16126
16127 /* Return the difference in milliseconds between two
16128    time marks.  We assume the difference will fit in a long!
16129 */
16130 long
16131 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16132 {
16133     return 1000L*(tm2->sec - tm1->sec) +
16134            (long) (tm2->ms - tm1->ms);
16135 }
16136
16137
16138 /*
16139  * Code to manage the game clocks.
16140  *
16141  * In tournament play, black starts the clock and then white makes a move.
16142  * We give the human user a slight advantage if he is playing white---the
16143  * clocks don't run until he makes his first move, so it takes zero time.
16144  * Also, we don't account for network lag, so we could get out of sync
16145  * with GNU Chess's clock -- but then, referees are always right.
16146  */
16147
16148 static TimeMark tickStartTM;
16149 static long intendedTickLength;
16150
16151 long
16152 NextTickLength (long timeRemaining)
16153 {
16154     long nominalTickLength, nextTickLength;
16155
16156     if (timeRemaining > 0L && timeRemaining <= 10000L)
16157       nominalTickLength = 100L;
16158     else
16159       nominalTickLength = 1000L;
16160     nextTickLength = timeRemaining % nominalTickLength;
16161     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16162
16163     return nextTickLength;
16164 }
16165
16166 /* Adjust clock one minute up or down */
16167 void
16168 AdjustClock (Boolean which, int dir)
16169 {
16170     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16171     if(which) blackTimeRemaining += 60000*dir;
16172     else      whiteTimeRemaining += 60000*dir;
16173     DisplayBothClocks();
16174     adjustedClock = TRUE;
16175 }
16176
16177 /* Stop clocks and reset to a fresh time control */
16178 void
16179 ResetClocks ()
16180 {
16181     (void) StopClockTimer();
16182     if (appData.icsActive) {
16183         whiteTimeRemaining = blackTimeRemaining = 0;
16184     } else if (searchTime) {
16185         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16186         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16187     } else { /* [HGM] correct new time quote for time odds */
16188         whiteTC = blackTC = fullTimeControlString;
16189         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16190         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16191     }
16192     if (whiteFlag || blackFlag) {
16193         DisplayTitle("");
16194         whiteFlag = blackFlag = FALSE;
16195     }
16196     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16197     DisplayBothClocks();
16198     adjustedClock = FALSE;
16199 }
16200
16201 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16202
16203 /* Decrement running clock by amount of time that has passed */
16204 void
16205 DecrementClocks ()
16206 {
16207     long timeRemaining;
16208     long lastTickLength, fudge;
16209     TimeMark now;
16210
16211     if (!appData.clockMode) return;
16212     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16213
16214     GetTimeMark(&now);
16215
16216     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16217
16218     /* Fudge if we woke up a little too soon */
16219     fudge = intendedTickLength - lastTickLength;
16220     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16221
16222     if (WhiteOnMove(forwardMostMove)) {
16223         if(whiteNPS >= 0) lastTickLength = 0;
16224         timeRemaining = whiteTimeRemaining -= lastTickLength;
16225         if(timeRemaining < 0 && !appData.icsActive) {
16226             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16227             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16228                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16229                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16230             }
16231         }
16232         DisplayWhiteClock(whiteTimeRemaining - fudge,
16233                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16234     } else {
16235         if(blackNPS >= 0) lastTickLength = 0;
16236         timeRemaining = blackTimeRemaining -= lastTickLength;
16237         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16238             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16239             if(suddenDeath) {
16240                 blackStartMove = forwardMostMove;
16241                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16242             }
16243         }
16244         DisplayBlackClock(blackTimeRemaining - fudge,
16245                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16246     }
16247     if (CheckFlags()) return;
16248
16249     if(twoBoards) { // count down secondary board's clocks as well
16250         activePartnerTime -= lastTickLength;
16251         partnerUp = 1;
16252         if(activePartner == 'W')
16253             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16254         else
16255             DisplayBlackClock(activePartnerTime, TRUE);
16256         partnerUp = 0;
16257     }
16258
16259     tickStartTM = now;
16260     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16261     StartClockTimer(intendedTickLength);
16262
16263     /* if the time remaining has fallen below the alarm threshold, sound the
16264      * alarm. if the alarm has sounded and (due to a takeback or time control
16265      * with increment) the time remaining has increased to a level above the
16266      * threshold, reset the alarm so it can sound again.
16267      */
16268
16269     if (appData.icsActive && appData.icsAlarm) {
16270
16271         /* make sure we are dealing with the user's clock */
16272         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16273                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16274            )) return;
16275
16276         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16277             alarmSounded = FALSE;
16278         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16279             PlayAlarmSound();
16280             alarmSounded = TRUE;
16281         }
16282     }
16283 }
16284
16285
16286 /* A player has just moved, so stop the previously running
16287    clock and (if in clock mode) start the other one.
16288    We redisplay both clocks in case we're in ICS mode, because
16289    ICS gives us an update to both clocks after every move.
16290    Note that this routine is called *after* forwardMostMove
16291    is updated, so the last fractional tick must be subtracted
16292    from the color that is *not* on move now.
16293 */
16294 void
16295 SwitchClocks (int newMoveNr)
16296 {
16297     long lastTickLength;
16298     TimeMark now;
16299     int flagged = FALSE;
16300
16301     GetTimeMark(&now);
16302
16303     if (StopClockTimer() && appData.clockMode) {
16304         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16305         if (!WhiteOnMove(forwardMostMove)) {
16306             if(blackNPS >= 0) lastTickLength = 0;
16307             blackTimeRemaining -= lastTickLength;
16308            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16309 //         if(pvInfoList[forwardMostMove].time == -1)
16310                  pvInfoList[forwardMostMove].time =               // use GUI time
16311                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16312         } else {
16313            if(whiteNPS >= 0) lastTickLength = 0;
16314            whiteTimeRemaining -= lastTickLength;
16315            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16316 //         if(pvInfoList[forwardMostMove].time == -1)
16317                  pvInfoList[forwardMostMove].time =
16318                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16319         }
16320         flagged = CheckFlags();
16321     }
16322     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16323     CheckTimeControl();
16324
16325     if (flagged || !appData.clockMode) return;
16326
16327     switch (gameMode) {
16328       case MachinePlaysBlack:
16329       case MachinePlaysWhite:
16330       case BeginningOfGame:
16331         if (pausing) return;
16332         break;
16333
16334       case EditGame:
16335       case PlayFromGameFile:
16336       case IcsExamining:
16337         return;
16338
16339       default:
16340         break;
16341     }
16342
16343     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16344         if(WhiteOnMove(forwardMostMove))
16345              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16346         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16347     }
16348
16349     tickStartTM = now;
16350     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16351       whiteTimeRemaining : blackTimeRemaining);
16352     StartClockTimer(intendedTickLength);
16353 }
16354
16355
16356 /* Stop both clocks */
16357 void
16358 StopClocks ()
16359 {
16360     long lastTickLength;
16361     TimeMark now;
16362
16363     if (!StopClockTimer()) return;
16364     if (!appData.clockMode) return;
16365
16366     GetTimeMark(&now);
16367
16368     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16369     if (WhiteOnMove(forwardMostMove)) {
16370         if(whiteNPS >= 0) lastTickLength = 0;
16371         whiteTimeRemaining -= lastTickLength;
16372         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16373     } else {
16374         if(blackNPS >= 0) lastTickLength = 0;
16375         blackTimeRemaining -= lastTickLength;
16376         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16377     }
16378     CheckFlags();
16379 }
16380
16381 /* Start clock of player on move.  Time may have been reset, so
16382    if clock is already running, stop and restart it. */
16383 void
16384 StartClocks ()
16385 {
16386     (void) StopClockTimer(); /* in case it was running already */
16387     DisplayBothClocks();
16388     if (CheckFlags()) return;
16389
16390     if (!appData.clockMode) return;
16391     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16392
16393     GetTimeMark(&tickStartTM);
16394     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16395       whiteTimeRemaining : blackTimeRemaining);
16396
16397    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16398     whiteNPS = blackNPS = -1;
16399     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16400        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16401         whiteNPS = first.nps;
16402     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16403        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16404         blackNPS = first.nps;
16405     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16406         whiteNPS = second.nps;
16407     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16408         blackNPS = second.nps;
16409     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16410
16411     StartClockTimer(intendedTickLength);
16412 }
16413
16414 char *
16415 TimeString (long ms)
16416 {
16417     long second, minute, hour, day;
16418     char *sign = "";
16419     static char buf[32];
16420
16421     if (ms > 0 && ms <= 9900) {
16422       /* convert milliseconds to tenths, rounding up */
16423       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16424
16425       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16426       return buf;
16427     }
16428
16429     /* convert milliseconds to seconds, rounding up */
16430     /* use floating point to avoid strangeness of integer division
16431        with negative dividends on many machines */
16432     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16433
16434     if (second < 0) {
16435         sign = "-";
16436         second = -second;
16437     }
16438
16439     day = second / (60 * 60 * 24);
16440     second = second % (60 * 60 * 24);
16441     hour = second / (60 * 60);
16442     second = second % (60 * 60);
16443     minute = second / 60;
16444     second = second % 60;
16445
16446     if (day > 0)
16447       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16448               sign, day, hour, minute, second);
16449     else if (hour > 0)
16450       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16451     else
16452       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16453
16454     return buf;
16455 }
16456
16457
16458 /*
16459  * This is necessary because some C libraries aren't ANSI C compliant yet.
16460  */
16461 char *
16462 StrStr (char *string, char *match)
16463 {
16464     int i, length;
16465
16466     length = strlen(match);
16467
16468     for (i = strlen(string) - length; i >= 0; i--, string++)
16469       if (!strncmp(match, string, length))
16470         return string;
16471
16472     return NULL;
16473 }
16474
16475 char *
16476 StrCaseStr (char *string, char *match)
16477 {
16478     int i, j, length;
16479
16480     length = strlen(match);
16481
16482     for (i = strlen(string) - length; i >= 0; i--, string++) {
16483         for (j = 0; j < length; j++) {
16484             if (ToLower(match[j]) != ToLower(string[j]))
16485               break;
16486         }
16487         if (j == length) return string;
16488     }
16489
16490     return NULL;
16491 }
16492
16493 #ifndef _amigados
16494 int
16495 StrCaseCmp (char *s1, char *s2)
16496 {
16497     char c1, c2;
16498
16499     for (;;) {
16500         c1 = ToLower(*s1++);
16501         c2 = ToLower(*s2++);
16502         if (c1 > c2) return 1;
16503         if (c1 < c2) return -1;
16504         if (c1 == NULLCHAR) return 0;
16505     }
16506 }
16507
16508
16509 int
16510 ToLower (int c)
16511 {
16512     return isupper(c) ? tolower(c) : c;
16513 }
16514
16515
16516 int
16517 ToUpper (int c)
16518 {
16519     return islower(c) ? toupper(c) : c;
16520 }
16521 #endif /* !_amigados    */
16522
16523 char *
16524 StrSave (char *s)
16525 {
16526   char *ret;
16527
16528   if ((ret = (char *) malloc(strlen(s) + 1)))
16529     {
16530       safeStrCpy(ret, s, strlen(s)+1);
16531     }
16532   return ret;
16533 }
16534
16535 char *
16536 StrSavePtr (char *s, char **savePtr)
16537 {
16538     if (*savePtr) {
16539         free(*savePtr);
16540     }
16541     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16542       safeStrCpy(*savePtr, s, strlen(s)+1);
16543     }
16544     return(*savePtr);
16545 }
16546
16547 char *
16548 PGNDate ()
16549 {
16550     time_t clock;
16551     struct tm *tm;
16552     char buf[MSG_SIZ];
16553
16554     clock = time((time_t *)NULL);
16555     tm = localtime(&clock);
16556     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16557             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16558     return StrSave(buf);
16559 }
16560
16561
16562 char *
16563 PositionToFEN (int move, char *overrideCastling)
16564 {
16565     int i, j, fromX, fromY, toX, toY;
16566     int whiteToPlay;
16567     char buf[MSG_SIZ];
16568     char *p, *q;
16569     int emptycount;
16570     ChessSquare piece;
16571
16572     whiteToPlay = (gameMode == EditPosition) ?
16573       !blackPlaysFirst : (move % 2 == 0);
16574     p = buf;
16575
16576     /* Piece placement data */
16577     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16578         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16579         emptycount = 0;
16580         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16581             if (boards[move][i][j] == EmptySquare) {
16582                 emptycount++;
16583             } else { ChessSquare piece = boards[move][i][j];
16584                 if (emptycount > 0) {
16585                     if(emptycount<10) /* [HGM] can be >= 10 */
16586                         *p++ = '0' + emptycount;
16587                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16588                     emptycount = 0;
16589                 }
16590                 if(PieceToChar(piece) == '+') {
16591                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16592                     *p++ = '+';
16593                     piece = (ChessSquare)(DEMOTED piece);
16594                 }
16595                 *p++ = PieceToChar(piece);
16596                 if(p[-1] == '~') {
16597                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16598                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16599                     *p++ = '~';
16600                 }
16601             }
16602         }
16603         if (emptycount > 0) {
16604             if(emptycount<10) /* [HGM] can be >= 10 */
16605                 *p++ = '0' + emptycount;
16606             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16607             emptycount = 0;
16608         }
16609         *p++ = '/';
16610     }
16611     *(p - 1) = ' ';
16612
16613     /* [HGM] print Crazyhouse or Shogi holdings */
16614     if( gameInfo.holdingsWidth ) {
16615         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16616         q = p;
16617         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16618             piece = boards[move][i][BOARD_WIDTH-1];
16619             if( piece != EmptySquare )
16620               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16621                   *p++ = PieceToChar(piece);
16622         }
16623         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16624             piece = boards[move][BOARD_HEIGHT-i-1][0];
16625             if( piece != EmptySquare )
16626               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16627                   *p++ = PieceToChar(piece);
16628         }
16629
16630         if( q == p ) *p++ = '-';
16631         *p++ = ']';
16632         *p++ = ' ';
16633     }
16634
16635     /* Active color */
16636     *p++ = whiteToPlay ? 'w' : 'b';
16637     *p++ = ' ';
16638
16639   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16640     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16641   } else {
16642   if(nrCastlingRights) {
16643      q = p;
16644      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16645        /* [HGM] write directly from rights */
16646            if(boards[move][CASTLING][2] != NoRights &&
16647               boards[move][CASTLING][0] != NoRights   )
16648                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16649            if(boards[move][CASTLING][2] != NoRights &&
16650               boards[move][CASTLING][1] != NoRights   )
16651                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16652            if(boards[move][CASTLING][5] != NoRights &&
16653               boards[move][CASTLING][3] != NoRights   )
16654                 *p++ = boards[move][CASTLING][3] + AAA;
16655            if(boards[move][CASTLING][5] != NoRights &&
16656               boards[move][CASTLING][4] != NoRights   )
16657                 *p++ = boards[move][CASTLING][4] + AAA;
16658      } else {
16659
16660         /* [HGM] write true castling rights */
16661         if( nrCastlingRights == 6 ) {
16662             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16663                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16664             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16665                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16666             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16667                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16668             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16669                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16670         }
16671      }
16672      if (q == p) *p++ = '-'; /* No castling rights */
16673      *p++ = ' ';
16674   }
16675
16676   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16677      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16678     /* En passant target square */
16679     if (move > backwardMostMove) {
16680         fromX = moveList[move - 1][0] - AAA;
16681         fromY = moveList[move - 1][1] - ONE;
16682         toX = moveList[move - 1][2] - AAA;
16683         toY = moveList[move - 1][3] - ONE;
16684         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16685             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16686             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16687             fromX == toX) {
16688             /* 2-square pawn move just happened */
16689             *p++ = toX + AAA;
16690             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16691         } else {
16692             *p++ = '-';
16693         }
16694     } else if(move == backwardMostMove) {
16695         // [HGM] perhaps we should always do it like this, and forget the above?
16696         if((signed char)boards[move][EP_STATUS] >= 0) {
16697             *p++ = boards[move][EP_STATUS] + AAA;
16698             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16699         } else {
16700             *p++ = '-';
16701         }
16702     } else {
16703         *p++ = '-';
16704     }
16705     *p++ = ' ';
16706   }
16707   }
16708
16709     /* [HGM] find reversible plies */
16710     {   int i = 0, j=move;
16711
16712         if (appData.debugMode) { int k;
16713             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16714             for(k=backwardMostMove; k<=forwardMostMove; k++)
16715                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16716
16717         }
16718
16719         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16720         if( j == backwardMostMove ) i += initialRulePlies;
16721         sprintf(p, "%d ", i);
16722         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16723     }
16724     /* Fullmove number */
16725     sprintf(p, "%d", (move / 2) + 1);
16726
16727     return StrSave(buf);
16728 }
16729
16730 Boolean
16731 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16732 {
16733     int i, j;
16734     char *p, c;
16735     int emptycount;
16736     ChessSquare piece;
16737
16738     p = fen;
16739
16740     /* [HGM] by default clear Crazyhouse holdings, if present */
16741     if(gameInfo.holdingsWidth) {
16742        for(i=0; i<BOARD_HEIGHT; i++) {
16743            board[i][0]             = EmptySquare; /* black holdings */
16744            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16745            board[i][1]             = (ChessSquare) 0; /* black counts */
16746            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16747        }
16748     }
16749
16750     /* Piece placement data */
16751     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16752         j = 0;
16753         for (;;) {
16754             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16755                 if (*p == '/') p++;
16756                 emptycount = gameInfo.boardWidth - j;
16757                 while (emptycount--)
16758                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16759                 break;
16760 #if(BOARD_FILES >= 10)
16761             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16762                 p++; emptycount=10;
16763                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16764                 while (emptycount--)
16765                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16766 #endif
16767             } else if (isdigit(*p)) {
16768                 emptycount = *p++ - '0';
16769                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16770                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16771                 while (emptycount--)
16772                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16773             } else if (*p == '+' || isalpha(*p)) {
16774                 if (j >= gameInfo.boardWidth) return FALSE;
16775                 if(*p=='+') {
16776                     piece = CharToPiece(*++p);
16777                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16778                     piece = (ChessSquare) (PROMOTED piece ); p++;
16779                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16780                 } else piece = CharToPiece(*p++);
16781
16782                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16783                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16784                     piece = (ChessSquare) (PROMOTED piece);
16785                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16786                     p++;
16787                 }
16788                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16789             } else {
16790                 return FALSE;
16791             }
16792         }
16793     }
16794     while (*p == '/' || *p == ' ') p++;
16795
16796     /* [HGM] look for Crazyhouse holdings here */
16797     while(*p==' ') p++;
16798     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16799         if(*p == '[') p++;
16800         if(*p == '-' ) p++; /* empty holdings */ else {
16801             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16802             /* if we would allow FEN reading to set board size, we would   */
16803             /* have to add holdings and shift the board read so far here   */
16804             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16805                 p++;
16806                 if((int) piece >= (int) BlackPawn ) {
16807                     i = (int)piece - (int)BlackPawn;
16808                     i = PieceToNumber((ChessSquare)i);
16809                     if( i >= gameInfo.holdingsSize ) return FALSE;
16810                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16811                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16812                 } else {
16813                     i = (int)piece - (int)WhitePawn;
16814                     i = PieceToNumber((ChessSquare)i);
16815                     if( i >= gameInfo.holdingsSize ) return FALSE;
16816                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16817                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16818                 }
16819             }
16820         }
16821         if(*p == ']') p++;
16822     }
16823
16824     while(*p == ' ') p++;
16825
16826     /* Active color */
16827     c = *p++;
16828     if(appData.colorNickNames) {
16829       if( c == appData.colorNickNames[0] ) c = 'w'; else
16830       if( c == appData.colorNickNames[1] ) c = 'b';
16831     }
16832     switch (c) {
16833       case 'w':
16834         *blackPlaysFirst = FALSE;
16835         break;
16836       case 'b':
16837         *blackPlaysFirst = TRUE;
16838         break;
16839       default:
16840         return FALSE;
16841     }
16842
16843     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16844     /* return the extra info in global variiables             */
16845
16846     /* set defaults in case FEN is incomplete */
16847     board[EP_STATUS] = EP_UNKNOWN;
16848     for(i=0; i<nrCastlingRights; i++ ) {
16849         board[CASTLING][i] =
16850             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16851     }   /* assume possible unless obviously impossible */
16852     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16853     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16854     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16855                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16856     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16857     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16858     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16859                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16860     FENrulePlies = 0;
16861
16862     while(*p==' ') p++;
16863     if(nrCastlingRights) {
16864       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16865           /* castling indicator present, so default becomes no castlings */
16866           for(i=0; i<nrCastlingRights; i++ ) {
16867                  board[CASTLING][i] = NoRights;
16868           }
16869       }
16870       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16871              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16872              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16873              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16874         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
16875
16876         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16877             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16878             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16879         }
16880         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16881             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16882         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16883                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16884         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16885                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16886         switch(c) {
16887           case'K':
16888               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16889               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16890               board[CASTLING][2] = whiteKingFile;
16891               break;
16892           case'Q':
16893               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16894               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16895               board[CASTLING][2] = whiteKingFile;
16896               break;
16897           case'k':
16898               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16899               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16900               board[CASTLING][5] = blackKingFile;
16901               break;
16902           case'q':
16903               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16904               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16905               board[CASTLING][5] = blackKingFile;
16906           case '-':
16907               break;
16908           default: /* FRC castlings */
16909               if(c >= 'a') { /* black rights */
16910                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16911                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16912                   if(i == BOARD_RGHT) break;
16913                   board[CASTLING][5] = i;
16914                   c -= AAA;
16915                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16916                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16917                   if(c > i)
16918                       board[CASTLING][3] = c;
16919                   else
16920                       board[CASTLING][4] = c;
16921               } else { /* white rights */
16922                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16923                     if(board[0][i] == WhiteKing) break;
16924                   if(i == BOARD_RGHT) break;
16925                   board[CASTLING][2] = i;
16926                   c -= AAA - 'a' + 'A';
16927                   if(board[0][c] >= WhiteKing) break;
16928                   if(c > i)
16929                       board[CASTLING][0] = c;
16930                   else
16931                       board[CASTLING][1] = c;
16932               }
16933         }
16934       }
16935       for(i=0; i<nrCastlingRights; i++)
16936         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16937     if (appData.debugMode) {
16938         fprintf(debugFP, "FEN castling rights:");
16939         for(i=0; i<nrCastlingRights; i++)
16940         fprintf(debugFP, " %d", board[CASTLING][i]);
16941         fprintf(debugFP, "\n");
16942     }
16943
16944       while(*p==' ') p++;
16945     }
16946
16947     /* read e.p. field in games that know e.p. capture */
16948     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16949        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16950       if(*p=='-') {
16951         p++; board[EP_STATUS] = EP_NONE;
16952       } else {
16953          char c = *p++ - AAA;
16954
16955          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16956          if(*p >= '0' && *p <='9') p++;
16957          board[EP_STATUS] = c;
16958       }
16959     }
16960
16961
16962     if(sscanf(p, "%d", &i) == 1) {
16963         FENrulePlies = i; /* 50-move ply counter */
16964         /* (The move number is still ignored)    */
16965     }
16966
16967     return TRUE;
16968 }
16969
16970 void
16971 EditPositionPasteFEN (char *fen)
16972 {
16973   if (fen != NULL) {
16974     Board initial_position;
16975
16976     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16977       DisplayError(_("Bad FEN position in clipboard"), 0);
16978       return ;
16979     } else {
16980       int savedBlackPlaysFirst = blackPlaysFirst;
16981       EditPositionEvent();
16982       blackPlaysFirst = savedBlackPlaysFirst;
16983       CopyBoard(boards[0], initial_position);
16984       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16985       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16986       DisplayBothClocks();
16987       DrawPosition(FALSE, boards[currentMove]);
16988     }
16989   }
16990 }
16991
16992 static char cseq[12] = "\\   ";
16993
16994 Boolean
16995 set_cont_sequence (char *new_seq)
16996 {
16997     int len;
16998     Boolean ret;
16999
17000     // handle bad attempts to set the sequence
17001         if (!new_seq)
17002                 return 0; // acceptable error - no debug
17003
17004     len = strlen(new_seq);
17005     ret = (len > 0) && (len < sizeof(cseq));
17006     if (ret)
17007       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17008     else if (appData.debugMode)
17009       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17010     return ret;
17011 }
17012
17013 /*
17014     reformat a source message so words don't cross the width boundary.  internal
17015     newlines are not removed.  returns the wrapped size (no null character unless
17016     included in source message).  If dest is NULL, only calculate the size required
17017     for the dest buffer.  lp argument indicats line position upon entry, and it's
17018     passed back upon exit.
17019 */
17020 int
17021 wrap (char *dest, char *src, int count, int width, int *lp)
17022 {
17023     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17024
17025     cseq_len = strlen(cseq);
17026     old_line = line = *lp;
17027     ansi = len = clen = 0;
17028
17029     for (i=0; i < count; i++)
17030     {
17031         if (src[i] == '\033')
17032             ansi = 1;
17033
17034         // if we hit the width, back up
17035         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17036         {
17037             // store i & len in case the word is too long
17038             old_i = i, old_len = len;
17039
17040             // find the end of the last word
17041             while (i && src[i] != ' ' && src[i] != '\n')
17042             {
17043                 i--;
17044                 len--;
17045             }
17046
17047             // word too long?  restore i & len before splitting it
17048             if ((old_i-i+clen) >= width)
17049             {
17050                 i = old_i;
17051                 len = old_len;
17052             }
17053
17054             // extra space?
17055             if (i && src[i-1] == ' ')
17056                 len--;
17057
17058             if (src[i] != ' ' && src[i] != '\n')
17059             {
17060                 i--;
17061                 if (len)
17062                     len--;
17063             }
17064
17065             // now append the newline and continuation sequence
17066             if (dest)
17067                 dest[len] = '\n';
17068             len++;
17069             if (dest)
17070                 strncpy(dest+len, cseq, cseq_len);
17071             len += cseq_len;
17072             line = cseq_len;
17073             clen = cseq_len;
17074             continue;
17075         }
17076
17077         if (dest)
17078             dest[len] = src[i];
17079         len++;
17080         if (!ansi)
17081             line++;
17082         if (src[i] == '\n')
17083             line = 0;
17084         if (src[i] == 'm')
17085             ansi = 0;
17086     }
17087     if (dest && appData.debugMode)
17088     {
17089         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17090             count, width, line, len, *lp);
17091         show_bytes(debugFP, src, count);
17092         fprintf(debugFP, "\ndest: ");
17093         show_bytes(debugFP, dest, len);
17094         fprintf(debugFP, "\n");
17095     }
17096     *lp = dest ? line : old_line;
17097
17098     return len;
17099 }
17100
17101 // [HGM] vari: routines for shelving variations
17102 Boolean modeRestore = FALSE;
17103
17104 void
17105 PushInner (int firstMove, int lastMove)
17106 {
17107         int i, j, nrMoves = lastMove - firstMove;
17108
17109         // push current tail of game on stack
17110         savedResult[storedGames] = gameInfo.result;
17111         savedDetails[storedGames] = gameInfo.resultDetails;
17112         gameInfo.resultDetails = NULL;
17113         savedFirst[storedGames] = firstMove;
17114         savedLast [storedGames] = lastMove;
17115         savedFramePtr[storedGames] = framePtr;
17116         framePtr -= nrMoves; // reserve space for the boards
17117         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17118             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17119             for(j=0; j<MOVE_LEN; j++)
17120                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17121             for(j=0; j<2*MOVE_LEN; j++)
17122                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17123             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17124             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17125             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17126             pvInfoList[firstMove+i-1].depth = 0;
17127             commentList[framePtr+i] = commentList[firstMove+i];
17128             commentList[firstMove+i] = NULL;
17129         }
17130
17131         storedGames++;
17132         forwardMostMove = firstMove; // truncate game so we can start variation
17133 }
17134
17135 void
17136 PushTail (int firstMove, int lastMove)
17137 {
17138         if(appData.icsActive) { // only in local mode
17139                 forwardMostMove = currentMove; // mimic old ICS behavior
17140                 return;
17141         }
17142         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17143
17144         PushInner(firstMove, lastMove);
17145         if(storedGames == 1) GreyRevert(FALSE);
17146         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17147 }
17148
17149 void
17150 PopInner (Boolean annotate)
17151 {
17152         int i, j, nrMoves;
17153         char buf[8000], moveBuf[20];
17154
17155         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17156         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17157         nrMoves = savedLast[storedGames] - currentMove;
17158         if(annotate) {
17159                 int cnt = 10;
17160                 if(!WhiteOnMove(currentMove))
17161                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17162                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17163                 for(i=currentMove; i<forwardMostMove; i++) {
17164                         if(WhiteOnMove(i))
17165                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17166                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17167                         strcat(buf, moveBuf);
17168                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17169                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17170                 }
17171                 strcat(buf, ")");
17172         }
17173         for(i=1; i<=nrMoves; i++) { // copy last variation back
17174             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17175             for(j=0; j<MOVE_LEN; j++)
17176                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17177             for(j=0; j<2*MOVE_LEN; j++)
17178                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17179             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17180             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17181             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17182             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17183             commentList[currentMove+i] = commentList[framePtr+i];
17184             commentList[framePtr+i] = NULL;
17185         }
17186         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17187         framePtr = savedFramePtr[storedGames];
17188         gameInfo.result = savedResult[storedGames];
17189         if(gameInfo.resultDetails != NULL) {
17190             free(gameInfo.resultDetails);
17191       }
17192         gameInfo.resultDetails = savedDetails[storedGames];
17193         forwardMostMove = currentMove + nrMoves;
17194 }
17195
17196 Boolean
17197 PopTail (Boolean annotate)
17198 {
17199         if(appData.icsActive) return FALSE; // only in local mode
17200         if(!storedGames) return FALSE; // sanity
17201         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17202
17203         PopInner(annotate);
17204         if(currentMove < forwardMostMove) ForwardEvent(); else
17205         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17206
17207         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17208         return TRUE;
17209 }
17210
17211 void
17212 CleanupTail ()
17213 {       // remove all shelved variations
17214         int i;
17215         for(i=0; i<storedGames; i++) {
17216             if(savedDetails[i])
17217                 free(savedDetails[i]);
17218             savedDetails[i] = NULL;
17219         }
17220         for(i=framePtr; i<MAX_MOVES; i++) {
17221                 if(commentList[i]) free(commentList[i]);
17222                 commentList[i] = NULL;
17223         }
17224         framePtr = MAX_MOVES-1;
17225         storedGames = 0;
17226 }
17227
17228 void
17229 LoadVariation (int index, char *text)
17230 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17231         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17232         int level = 0, move;
17233
17234         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17235         // first find outermost bracketing variation
17236         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17237             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17238                 if(*p == '{') wait = '}'; else
17239                 if(*p == '[') wait = ']'; else
17240                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17241                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17242             }
17243             if(*p == wait) wait = NULLCHAR; // closing ]} found
17244             p++;
17245         }
17246         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17247         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17248         end[1] = NULLCHAR; // clip off comment beyond variation
17249         ToNrEvent(currentMove-1);
17250         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17251         // kludge: use ParsePV() to append variation to game
17252         move = currentMove;
17253         ParsePV(start, TRUE, TRUE);
17254         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17255         ClearPremoveHighlights();
17256         CommentPopDown();
17257         ToNrEvent(currentMove+1);
17258 }
17259