Shuffle prototypes to correct header, or add them there
[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) 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, signed 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(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 ? 0 : 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 ? 0 : 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     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13958     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13959     gameMode = EditGame;
13960     ModeHighlight();
13961     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13962     ClearHighlights(); /* [AS] */
13963 }
13964
13965 /* Pause for `ms' milliseconds */
13966 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13967 void
13968 TimeDelay (long ms)
13969 {
13970     TimeMark m1, m2;
13971
13972     GetTimeMark(&m1);
13973     do {
13974         GetTimeMark(&m2);
13975     } while (SubtractTimeMarks(&m2, &m1) < ms);
13976 }
13977
13978 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13979 void
13980 SendMultiLineToICS (char *buf)
13981 {
13982     char temp[MSG_SIZ+1], *p;
13983     int len;
13984
13985     len = strlen(buf);
13986     if (len > MSG_SIZ)
13987       len = MSG_SIZ;
13988
13989     strncpy(temp, buf, len);
13990     temp[len] = 0;
13991
13992     p = temp;
13993     while (*p) {
13994         if (*p == '\n' || *p == '\r')
13995           *p = ' ';
13996         ++p;
13997     }
13998
13999     strcat(temp, "\n");
14000     SendToICS(temp);
14001     SendToPlayer(temp, strlen(temp));
14002 }
14003
14004 void
14005 SetWhiteToPlayEvent ()
14006 {
14007     if (gameMode == EditPosition) {
14008         blackPlaysFirst = FALSE;
14009         DisplayBothClocks();    /* works because currentMove is 0 */
14010     } else if (gameMode == IcsExamining) {
14011         SendToICS(ics_prefix);
14012         SendToICS("tomove white\n");
14013     }
14014 }
14015
14016 void
14017 SetBlackToPlayEvent ()
14018 {
14019     if (gameMode == EditPosition) {
14020         blackPlaysFirst = TRUE;
14021         currentMove = 1;        /* kludge */
14022         DisplayBothClocks();
14023         currentMove = 0;
14024     } else if (gameMode == IcsExamining) {
14025         SendToICS(ics_prefix);
14026         SendToICS("tomove black\n");
14027     }
14028 }
14029
14030 void
14031 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14032 {
14033     char buf[MSG_SIZ];
14034     ChessSquare piece = boards[0][y][x];
14035
14036     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14037
14038     switch (selection) {
14039       case ClearBoard:
14040         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14041             SendToICS(ics_prefix);
14042             SendToICS("bsetup clear\n");
14043         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14044             SendToICS(ics_prefix);
14045             SendToICS("clearboard\n");
14046         } else {
14047             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14048                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14049                 for (y = 0; y < BOARD_HEIGHT; y++) {
14050                     if (gameMode == IcsExamining) {
14051                         if (boards[currentMove][y][x] != EmptySquare) {
14052                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14053                                     AAA + x, ONE + y);
14054                             SendToICS(buf);
14055                         }
14056                     } else {
14057                         boards[0][y][x] = p;
14058                     }
14059                 }
14060             }
14061         }
14062         if (gameMode == EditPosition) {
14063             DrawPosition(FALSE, boards[0]);
14064         }
14065         break;
14066
14067       case WhitePlay:
14068         SetWhiteToPlayEvent();
14069         break;
14070
14071       case BlackPlay:
14072         SetBlackToPlayEvent();
14073         break;
14074
14075       case EmptySquare:
14076         if (gameMode == IcsExamining) {
14077             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14078             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14079             SendToICS(buf);
14080         } else {
14081             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14082                 if(x == BOARD_LEFT-2) {
14083                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14084                     boards[0][y][1] = 0;
14085                 } else
14086                 if(x == BOARD_RGHT+1) {
14087                     if(y >= gameInfo.holdingsSize) break;
14088                     boards[0][y][BOARD_WIDTH-2] = 0;
14089                 } else break;
14090             }
14091             boards[0][y][x] = EmptySquare;
14092             DrawPosition(FALSE, boards[0]);
14093         }
14094         break;
14095
14096       case PromotePiece:
14097         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14098            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14099             selection = (ChessSquare) (PROMOTED piece);
14100         } else if(piece == EmptySquare) selection = WhiteSilver;
14101         else selection = (ChessSquare)((int)piece - 1);
14102         goto defaultlabel;
14103
14104       case DemotePiece:
14105         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14106            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14107             selection = (ChessSquare) (DEMOTED piece);
14108         } else if(piece == EmptySquare) selection = BlackSilver;
14109         else selection = (ChessSquare)((int)piece + 1);
14110         goto defaultlabel;
14111
14112       case WhiteQueen:
14113       case BlackQueen:
14114         if(gameInfo.variant == VariantShatranj ||
14115            gameInfo.variant == VariantXiangqi  ||
14116            gameInfo.variant == VariantCourier  ||
14117            gameInfo.variant == VariantMakruk     )
14118             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14119         goto defaultlabel;
14120
14121       case WhiteKing:
14122       case BlackKing:
14123         if(gameInfo.variant == VariantXiangqi)
14124             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14125         if(gameInfo.variant == VariantKnightmate)
14126             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14127       default:
14128         defaultlabel:
14129         if (gameMode == IcsExamining) {
14130             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14131             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14132                      PieceToChar(selection), AAA + x, ONE + y);
14133             SendToICS(buf);
14134         } else {
14135             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14136                 int n;
14137                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14138                     n = PieceToNumber(selection - BlackPawn);
14139                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14140                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14141                     boards[0][BOARD_HEIGHT-1-n][1]++;
14142                 } else
14143                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14144                     n = PieceToNumber(selection);
14145                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14146                     boards[0][n][BOARD_WIDTH-1] = selection;
14147                     boards[0][n][BOARD_WIDTH-2]++;
14148                 }
14149             } else
14150             boards[0][y][x] = selection;
14151             DrawPosition(TRUE, boards[0]);
14152             ClearHighlights();
14153             fromX = fromY = -1;
14154         }
14155         break;
14156     }
14157 }
14158
14159
14160 void
14161 DropMenuEvent (ChessSquare selection, int x, int y)
14162 {
14163     ChessMove moveType;
14164
14165     switch (gameMode) {
14166       case IcsPlayingWhite:
14167       case MachinePlaysBlack:
14168         if (!WhiteOnMove(currentMove)) {
14169             DisplayMoveError(_("It is Black's turn"));
14170             return;
14171         }
14172         moveType = WhiteDrop;
14173         break;
14174       case IcsPlayingBlack:
14175       case MachinePlaysWhite:
14176         if (WhiteOnMove(currentMove)) {
14177             DisplayMoveError(_("It is White's turn"));
14178             return;
14179         }
14180         moveType = BlackDrop;
14181         break;
14182       case EditGame:
14183         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14184         break;
14185       default:
14186         return;
14187     }
14188
14189     if (moveType == BlackDrop && selection < BlackPawn) {
14190       selection = (ChessSquare) ((int) selection
14191                                  + (int) BlackPawn - (int) WhitePawn);
14192     }
14193     if (boards[currentMove][y][x] != EmptySquare) {
14194         DisplayMoveError(_("That square is occupied"));
14195         return;
14196     }
14197
14198     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14199 }
14200
14201 void
14202 AcceptEvent ()
14203 {
14204     /* Accept a pending offer of any kind from opponent */
14205
14206     if (appData.icsActive) {
14207         SendToICS(ics_prefix);
14208         SendToICS("accept\n");
14209     } else if (cmailMsgLoaded) {
14210         if (currentMove == cmailOldMove &&
14211             commentList[cmailOldMove] != NULL &&
14212             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14213                    "Black offers a draw" : "White offers a draw")) {
14214             TruncateGame();
14215             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14216             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14217         } else {
14218             DisplayError(_("There is no pending offer on this move"), 0);
14219             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14220         }
14221     } else {
14222         /* Not used for offers from chess program */
14223     }
14224 }
14225
14226 void
14227 DeclineEvent ()
14228 {
14229     /* Decline a pending offer of any kind from opponent */
14230
14231     if (appData.icsActive) {
14232         SendToICS(ics_prefix);
14233         SendToICS("decline\n");
14234     } else if (cmailMsgLoaded) {
14235         if (currentMove == cmailOldMove &&
14236             commentList[cmailOldMove] != NULL &&
14237             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14238                    "Black offers a draw" : "White offers a draw")) {
14239 #ifdef NOTDEF
14240             AppendComment(cmailOldMove, "Draw declined", TRUE);
14241             DisplayComment(cmailOldMove - 1, "Draw declined");
14242 #endif /*NOTDEF*/
14243         } else {
14244             DisplayError(_("There is no pending offer on this move"), 0);
14245         }
14246     } else {
14247         /* Not used for offers from chess program */
14248     }
14249 }
14250
14251 void
14252 RematchEvent ()
14253 {
14254     /* Issue ICS rematch command */
14255     if (appData.icsActive) {
14256         SendToICS(ics_prefix);
14257         SendToICS("rematch\n");
14258     }
14259 }
14260
14261 void
14262 CallFlagEvent ()
14263 {
14264     /* Call your opponent's flag (claim a win on time) */
14265     if (appData.icsActive) {
14266         SendToICS(ics_prefix);
14267         SendToICS("flag\n");
14268     } else {
14269         switch (gameMode) {
14270           default:
14271             return;
14272           case MachinePlaysWhite:
14273             if (whiteFlag) {
14274                 if (blackFlag)
14275                   GameEnds(GameIsDrawn, "Both players ran out of time",
14276                            GE_PLAYER);
14277                 else
14278                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14279             } else {
14280                 DisplayError(_("Your opponent is not out of time"), 0);
14281             }
14282             break;
14283           case MachinePlaysBlack:
14284             if (blackFlag) {
14285                 if (whiteFlag)
14286                   GameEnds(GameIsDrawn, "Both players ran out of time",
14287                            GE_PLAYER);
14288                 else
14289                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14290             } else {
14291                 DisplayError(_("Your opponent is not out of time"), 0);
14292             }
14293             break;
14294         }
14295     }
14296 }
14297
14298 void
14299 ClockClick (int which)
14300 {       // [HGM] code moved to back-end from winboard.c
14301         if(which) { // black clock
14302           if (gameMode == EditPosition || gameMode == IcsExamining) {
14303             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14304             SetBlackToPlayEvent();
14305           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14306           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14307           } else if (shiftKey) {
14308             AdjustClock(which, -1);
14309           } else if (gameMode == IcsPlayingWhite ||
14310                      gameMode == MachinePlaysBlack) {
14311             CallFlagEvent();
14312           }
14313         } else { // white clock
14314           if (gameMode == EditPosition || gameMode == IcsExamining) {
14315             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14316             SetWhiteToPlayEvent();
14317           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14318           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14319           } else if (shiftKey) {
14320             AdjustClock(which, -1);
14321           } else if (gameMode == IcsPlayingBlack ||
14322                    gameMode == MachinePlaysWhite) {
14323             CallFlagEvent();
14324           }
14325         }
14326 }
14327
14328 void
14329 DrawEvent ()
14330 {
14331     /* Offer draw or accept pending draw offer from opponent */
14332
14333     if (appData.icsActive) {
14334         /* Note: tournament rules require draw offers to be
14335            made after you make your move but before you punch
14336            your clock.  Currently ICS doesn't let you do that;
14337            instead, you immediately punch your clock after making
14338            a move, but you can offer a draw at any time. */
14339
14340         SendToICS(ics_prefix);
14341         SendToICS("draw\n");
14342         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14343     } else if (cmailMsgLoaded) {
14344         if (currentMove == cmailOldMove &&
14345             commentList[cmailOldMove] != NULL &&
14346             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14347                    "Black offers a draw" : "White offers a draw")) {
14348             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14349             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14350         } else if (currentMove == cmailOldMove + 1) {
14351             char *offer = WhiteOnMove(cmailOldMove) ?
14352               "White offers a draw" : "Black offers a draw";
14353             AppendComment(currentMove, offer, TRUE);
14354             DisplayComment(currentMove - 1, offer);
14355             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14356         } else {
14357             DisplayError(_("You must make your move before offering a draw"), 0);
14358             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14359         }
14360     } else if (first.offeredDraw) {
14361         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14362     } else {
14363         if (first.sendDrawOffers) {
14364             SendToProgram("draw\n", &first);
14365             userOfferedDraw = TRUE;
14366         }
14367     }
14368 }
14369
14370 void
14371 AdjournEvent ()
14372 {
14373     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14374
14375     if (appData.icsActive) {
14376         SendToICS(ics_prefix);
14377         SendToICS("adjourn\n");
14378     } else {
14379         /* Currently GNU Chess doesn't offer or accept Adjourns */
14380     }
14381 }
14382
14383
14384 void
14385 AbortEvent ()
14386 {
14387     /* Offer Abort or accept pending Abort offer from opponent */
14388
14389     if (appData.icsActive) {
14390         SendToICS(ics_prefix);
14391         SendToICS("abort\n");
14392     } else {
14393         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14394     }
14395 }
14396
14397 void
14398 ResignEvent ()
14399 {
14400     /* Resign.  You can do this even if it's not your turn. */
14401
14402     if (appData.icsActive) {
14403         SendToICS(ics_prefix);
14404         SendToICS("resign\n");
14405     } else {
14406         switch (gameMode) {
14407           case MachinePlaysWhite:
14408             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14409             break;
14410           case MachinePlaysBlack:
14411             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14412             break;
14413           case EditGame:
14414             if (cmailMsgLoaded) {
14415                 TruncateGame();
14416                 if (WhiteOnMove(cmailOldMove)) {
14417                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14418                 } else {
14419                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14420                 }
14421                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14422             }
14423             break;
14424           default:
14425             break;
14426         }
14427     }
14428 }
14429
14430
14431 void
14432 StopObservingEvent ()
14433 {
14434     /* Stop observing current games */
14435     SendToICS(ics_prefix);
14436     SendToICS("unobserve\n");
14437 }
14438
14439 void
14440 StopExaminingEvent ()
14441 {
14442     /* Stop observing current game */
14443     SendToICS(ics_prefix);
14444     SendToICS("unexamine\n");
14445 }
14446
14447 void
14448 ForwardInner (int target)
14449 {
14450     int limit; int oldSeekGraphUp = seekGraphUp;
14451
14452     if (appData.debugMode)
14453         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14454                 target, currentMove, forwardMostMove);
14455
14456     if (gameMode == EditPosition)
14457       return;
14458
14459     seekGraphUp = FALSE;
14460     MarkTargetSquares(1);
14461
14462     if (gameMode == PlayFromGameFile && !pausing)
14463       PauseEvent();
14464
14465     if (gameMode == IcsExamining && pausing)
14466       limit = pauseExamForwardMostMove;
14467     else
14468       limit = forwardMostMove;
14469
14470     if (target > limit) target = limit;
14471
14472     if (target > 0 && moveList[target - 1][0]) {
14473         int fromX, fromY, toX, toY;
14474         toX = moveList[target - 1][2] - AAA;
14475         toY = moveList[target - 1][3] - ONE;
14476         if (moveList[target - 1][1] == '@') {
14477             if (appData.highlightLastMove) {
14478                 SetHighlights(-1, -1, toX, toY);
14479             }
14480         } else {
14481             fromX = moveList[target - 1][0] - AAA;
14482             fromY = moveList[target - 1][1] - ONE;
14483             if (target == currentMove + 1) {
14484                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14485             }
14486             if (appData.highlightLastMove) {
14487                 SetHighlights(fromX, fromY, toX, toY);
14488             }
14489         }
14490     }
14491     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14492         gameMode == Training || gameMode == PlayFromGameFile ||
14493         gameMode == AnalyzeFile) {
14494         while (currentMove < target) {
14495             SendMoveToProgram(currentMove++, &first);
14496         }
14497     } else {
14498         currentMove = target;
14499     }
14500
14501     if (gameMode == EditGame || gameMode == EndOfGame) {
14502         whiteTimeRemaining = timeRemaining[0][currentMove];
14503         blackTimeRemaining = timeRemaining[1][currentMove];
14504     }
14505     DisplayBothClocks();
14506     DisplayMove(currentMove - 1);
14507     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14508     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14509     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14510         DisplayComment(currentMove - 1, commentList[currentMove]);
14511     }
14512     ClearMap(); // [HGM] exclude: invalidate map
14513 }
14514
14515
14516 void
14517 ForwardEvent ()
14518 {
14519     if (gameMode == IcsExamining && !pausing) {
14520         SendToICS(ics_prefix);
14521         SendToICS("forward\n");
14522     } else {
14523         ForwardInner(currentMove + 1);
14524     }
14525 }
14526
14527 void
14528 ToEndEvent ()
14529 {
14530     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14531         /* to optimze, we temporarily turn off analysis mode while we feed
14532          * the remaining moves to the engine. Otherwise we get analysis output
14533          * after each move.
14534          */
14535         if (first.analysisSupport) {
14536           SendToProgram("exit\nforce\n", &first);
14537           first.analyzing = FALSE;
14538         }
14539     }
14540
14541     if (gameMode == IcsExamining && !pausing) {
14542         SendToICS(ics_prefix);
14543         SendToICS("forward 999999\n");
14544     } else {
14545         ForwardInner(forwardMostMove);
14546     }
14547
14548     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14549         /* we have fed all the moves, so reactivate analysis mode */
14550         SendToProgram("analyze\n", &first);
14551         first.analyzing = TRUE;
14552         /*first.maybeThinking = TRUE;*/
14553         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14554     }
14555 }
14556
14557 void
14558 BackwardInner (int target)
14559 {
14560     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14561
14562     if (appData.debugMode)
14563         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14564                 target, currentMove, forwardMostMove);
14565
14566     if (gameMode == EditPosition) return;
14567     seekGraphUp = FALSE;
14568     MarkTargetSquares(1);
14569     if (currentMove <= backwardMostMove) {
14570         ClearHighlights();
14571         DrawPosition(full_redraw, boards[currentMove]);
14572         return;
14573     }
14574     if (gameMode == PlayFromGameFile && !pausing)
14575       PauseEvent();
14576
14577     if (moveList[target][0]) {
14578         int fromX, fromY, toX, toY;
14579         toX = moveList[target][2] - AAA;
14580         toY = moveList[target][3] - ONE;
14581         if (moveList[target][1] == '@') {
14582             if (appData.highlightLastMove) {
14583                 SetHighlights(-1, -1, toX, toY);
14584             }
14585         } else {
14586             fromX = moveList[target][0] - AAA;
14587             fromY = moveList[target][1] - ONE;
14588             if (target == currentMove - 1) {
14589                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14590             }
14591             if (appData.highlightLastMove) {
14592                 SetHighlights(fromX, fromY, toX, toY);
14593             }
14594         }
14595     }
14596     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14597         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14598         while (currentMove > target) {
14599             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14600                 // null move cannot be undone. Reload program with move history before it.
14601                 int i;
14602                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14603                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14604                 }
14605                 SendBoard(&first, i); 
14606                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14607                 break;
14608             }
14609             SendToProgram("undo\n", &first);
14610             currentMove--;
14611         }
14612     } else {
14613         currentMove = target;
14614     }
14615
14616     if (gameMode == EditGame || gameMode == EndOfGame) {
14617         whiteTimeRemaining = timeRemaining[0][currentMove];
14618         blackTimeRemaining = timeRemaining[1][currentMove];
14619     }
14620     DisplayBothClocks();
14621     DisplayMove(currentMove - 1);
14622     DrawPosition(full_redraw, boards[currentMove]);
14623     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14624     // [HGM] PV info: routine tests if comment empty
14625     DisplayComment(currentMove - 1, commentList[currentMove]);
14626     ClearMap(); // [HGM] exclude: invalidate map
14627 }
14628
14629 void
14630 BackwardEvent ()
14631 {
14632     if (gameMode == IcsExamining && !pausing) {
14633         SendToICS(ics_prefix);
14634         SendToICS("backward\n");
14635     } else {
14636         BackwardInner(currentMove - 1);
14637     }
14638 }
14639
14640 void
14641 ToStartEvent ()
14642 {
14643     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14644         /* to optimize, we temporarily turn off analysis mode while we undo
14645          * all the moves. Otherwise we get analysis output after each undo.
14646          */
14647         if (first.analysisSupport) {
14648           SendToProgram("exit\nforce\n", &first);
14649           first.analyzing = FALSE;
14650         }
14651     }
14652
14653     if (gameMode == IcsExamining && !pausing) {
14654         SendToICS(ics_prefix);
14655         SendToICS("backward 999999\n");
14656     } else {
14657         BackwardInner(backwardMostMove);
14658     }
14659
14660     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14661         /* we have fed all the moves, so reactivate analysis mode */
14662         SendToProgram("analyze\n", &first);
14663         first.analyzing = TRUE;
14664         /*first.maybeThinking = TRUE;*/
14665         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14666     }
14667 }
14668
14669 void
14670 ToNrEvent (int to)
14671 {
14672   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14673   if (to >= forwardMostMove) to = forwardMostMove;
14674   if (to <= backwardMostMove) to = backwardMostMove;
14675   if (to < currentMove) {
14676     BackwardInner(to);
14677   } else {
14678     ForwardInner(to);
14679   }
14680 }
14681
14682 void
14683 RevertEvent (Boolean annotate)
14684 {
14685     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14686         return;
14687     }
14688     if (gameMode != IcsExamining) {
14689         DisplayError(_("You are not examining a game"), 0);
14690         return;
14691     }
14692     if (pausing) {
14693         DisplayError(_("You can't revert while pausing"), 0);
14694         return;
14695     }
14696     SendToICS(ics_prefix);
14697     SendToICS("revert\n");
14698 }
14699
14700 void
14701 RetractMoveEvent ()
14702 {
14703     switch (gameMode) {
14704       case MachinePlaysWhite:
14705       case MachinePlaysBlack:
14706         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14707             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14708             return;
14709         }
14710         if (forwardMostMove < 2) return;
14711         currentMove = forwardMostMove = forwardMostMove - 2;
14712         whiteTimeRemaining = timeRemaining[0][currentMove];
14713         blackTimeRemaining = timeRemaining[1][currentMove];
14714         DisplayBothClocks();
14715         DisplayMove(currentMove - 1);
14716         ClearHighlights();/*!! could figure this out*/
14717         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14718         SendToProgram("remove\n", &first);
14719         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14720         break;
14721
14722       case BeginningOfGame:
14723       default:
14724         break;
14725
14726       case IcsPlayingWhite:
14727       case IcsPlayingBlack:
14728         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14729             SendToICS(ics_prefix);
14730             SendToICS("takeback 2\n");
14731         } else {
14732             SendToICS(ics_prefix);
14733             SendToICS("takeback 1\n");
14734         }
14735         break;
14736     }
14737 }
14738
14739 void
14740 MoveNowEvent ()
14741 {
14742     ChessProgramState *cps;
14743
14744     switch (gameMode) {
14745       case MachinePlaysWhite:
14746         if (!WhiteOnMove(forwardMostMove)) {
14747             DisplayError(_("It is your turn"), 0);
14748             return;
14749         }
14750         cps = &first;
14751         break;
14752       case MachinePlaysBlack:
14753         if (WhiteOnMove(forwardMostMove)) {
14754             DisplayError(_("It is your turn"), 0);
14755             return;
14756         }
14757         cps = &first;
14758         break;
14759       case TwoMachinesPlay:
14760         if (WhiteOnMove(forwardMostMove) ==
14761             (first.twoMachinesColor[0] == 'w')) {
14762             cps = &first;
14763         } else {
14764             cps = &second;
14765         }
14766         break;
14767       case BeginningOfGame:
14768       default:
14769         return;
14770     }
14771     SendToProgram("?\n", cps);
14772 }
14773
14774 void
14775 TruncateGameEvent ()
14776 {
14777     EditGameEvent();
14778     if (gameMode != EditGame) return;
14779     TruncateGame();
14780 }
14781
14782 void
14783 TruncateGame ()
14784 {
14785     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14786     if (forwardMostMove > currentMove) {
14787         if (gameInfo.resultDetails != NULL) {
14788             free(gameInfo.resultDetails);
14789             gameInfo.resultDetails = NULL;
14790             gameInfo.result = GameUnfinished;
14791         }
14792         forwardMostMove = currentMove;
14793         HistorySet(parseList, backwardMostMove, forwardMostMove,
14794                    currentMove-1);
14795     }
14796 }
14797
14798 void
14799 HintEvent ()
14800 {
14801     if (appData.noChessProgram) return;
14802     switch (gameMode) {
14803       case MachinePlaysWhite:
14804         if (WhiteOnMove(forwardMostMove)) {
14805             DisplayError(_("Wait until your turn"), 0);
14806             return;
14807         }
14808         break;
14809       case BeginningOfGame:
14810       case MachinePlaysBlack:
14811         if (!WhiteOnMove(forwardMostMove)) {
14812             DisplayError(_("Wait until your turn"), 0);
14813             return;
14814         }
14815         break;
14816       default:
14817         DisplayError(_("No hint available"), 0);
14818         return;
14819     }
14820     SendToProgram("hint\n", &first);
14821     hintRequested = TRUE;
14822 }
14823
14824 void
14825 BookEvent ()
14826 {
14827     if (appData.noChessProgram) return;
14828     switch (gameMode) {
14829       case MachinePlaysWhite:
14830         if (WhiteOnMove(forwardMostMove)) {
14831             DisplayError(_("Wait until your turn"), 0);
14832             return;
14833         }
14834         break;
14835       case BeginningOfGame:
14836       case MachinePlaysBlack:
14837         if (!WhiteOnMove(forwardMostMove)) {
14838             DisplayError(_("Wait until your turn"), 0);
14839             return;
14840         }
14841         break;
14842       case EditPosition:
14843         EditPositionDone(TRUE);
14844         break;
14845       case TwoMachinesPlay:
14846         return;
14847       default:
14848         break;
14849     }
14850     SendToProgram("bk\n", &first);
14851     bookOutput[0] = NULLCHAR;
14852     bookRequested = TRUE;
14853 }
14854
14855 void
14856 AboutGameEvent ()
14857 {
14858     char *tags = PGNTags(&gameInfo);
14859     TagsPopUp(tags, CmailMsg());
14860     free(tags);
14861 }
14862
14863 /* end button procedures */
14864
14865 void
14866 PrintPosition (FILE *fp, int move)
14867 {
14868     int i, j;
14869
14870     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14871         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14872             char c = PieceToChar(boards[move][i][j]);
14873             fputc(c == 'x' ? '.' : c, fp);
14874             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14875         }
14876     }
14877     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14878       fprintf(fp, "white to play\n");
14879     else
14880       fprintf(fp, "black to play\n");
14881 }
14882
14883 void
14884 PrintOpponents (FILE *fp)
14885 {
14886     if (gameInfo.white != NULL) {
14887         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14888     } else {
14889         fprintf(fp, "\n");
14890     }
14891 }
14892
14893 /* Find last component of program's own name, using some heuristics */
14894 void
14895 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14896 {
14897     char *p, *q, c;
14898     int local = (strcmp(host, "localhost") == 0);
14899     while (!local && (p = strchr(prog, ';')) != NULL) {
14900         p++;
14901         while (*p == ' ') p++;
14902         prog = p;
14903     }
14904     if (*prog == '"' || *prog == '\'') {
14905         q = strchr(prog + 1, *prog);
14906     } else {
14907         q = strchr(prog, ' ');
14908     }
14909     if (q == NULL) q = prog + strlen(prog);
14910     p = q;
14911     while (p >= prog && *p != '/' && *p != '\\') p--;
14912     p++;
14913     if(p == prog && *p == '"') p++;
14914     c = *q; *q = 0;
14915     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14916     memcpy(buf, p, q - p);
14917     buf[q - p] = NULLCHAR;
14918     if (!local) {
14919         strcat(buf, "@");
14920         strcat(buf, host);
14921     }
14922 }
14923
14924 char *
14925 TimeControlTagValue ()
14926 {
14927     char buf[MSG_SIZ];
14928     if (!appData.clockMode) {
14929       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14930     } else if (movesPerSession > 0) {
14931       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14932     } else if (timeIncrement == 0) {
14933       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14934     } else {
14935       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14936     }
14937     return StrSave(buf);
14938 }
14939
14940 void
14941 SetGameInfo ()
14942 {
14943     /* This routine is used only for certain modes */
14944     VariantClass v = gameInfo.variant;
14945     ChessMove r = GameUnfinished;
14946     char *p = NULL;
14947
14948     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14949         r = gameInfo.result;
14950         p = gameInfo.resultDetails;
14951         gameInfo.resultDetails = NULL;
14952     }
14953     ClearGameInfo(&gameInfo);
14954     gameInfo.variant = v;
14955
14956     switch (gameMode) {
14957       case MachinePlaysWhite:
14958         gameInfo.event = StrSave( appData.pgnEventHeader );
14959         gameInfo.site = StrSave(HostName());
14960         gameInfo.date = PGNDate();
14961         gameInfo.round = StrSave("-");
14962         gameInfo.white = StrSave(first.tidy);
14963         gameInfo.black = StrSave(UserName());
14964         gameInfo.timeControl = TimeControlTagValue();
14965         break;
14966
14967       case MachinePlaysBlack:
14968         gameInfo.event = StrSave( appData.pgnEventHeader );
14969         gameInfo.site = StrSave(HostName());
14970         gameInfo.date = PGNDate();
14971         gameInfo.round = StrSave("-");
14972         gameInfo.white = StrSave(UserName());
14973         gameInfo.black = StrSave(first.tidy);
14974         gameInfo.timeControl = TimeControlTagValue();
14975         break;
14976
14977       case TwoMachinesPlay:
14978         gameInfo.event = StrSave( appData.pgnEventHeader );
14979         gameInfo.site = StrSave(HostName());
14980         gameInfo.date = PGNDate();
14981         if (roundNr > 0) {
14982             char buf[MSG_SIZ];
14983             snprintf(buf, MSG_SIZ, "%d", roundNr);
14984             gameInfo.round = StrSave(buf);
14985         } else {
14986             gameInfo.round = StrSave("-");
14987         }
14988         if (first.twoMachinesColor[0] == 'w') {
14989             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14990             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14991         } else {
14992             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14993             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14994         }
14995         gameInfo.timeControl = TimeControlTagValue();
14996         break;
14997
14998       case EditGame:
14999         gameInfo.event = StrSave("Edited game");
15000         gameInfo.site = StrSave(HostName());
15001         gameInfo.date = PGNDate();
15002         gameInfo.round = StrSave("-");
15003         gameInfo.white = StrSave("-");
15004         gameInfo.black = StrSave("-");
15005         gameInfo.result = r;
15006         gameInfo.resultDetails = p;
15007         break;
15008
15009       case EditPosition:
15010         gameInfo.event = StrSave("Edited position");
15011         gameInfo.site = StrSave(HostName());
15012         gameInfo.date = PGNDate();
15013         gameInfo.round = StrSave("-");
15014         gameInfo.white = StrSave("-");
15015         gameInfo.black = StrSave("-");
15016         break;
15017
15018       case IcsPlayingWhite:
15019       case IcsPlayingBlack:
15020       case IcsObserving:
15021       case IcsExamining:
15022         break;
15023
15024       case PlayFromGameFile:
15025         gameInfo.event = StrSave("Game from non-PGN file");
15026         gameInfo.site = StrSave(HostName());
15027         gameInfo.date = PGNDate();
15028         gameInfo.round = StrSave("-");
15029         gameInfo.white = StrSave("?");
15030         gameInfo.black = StrSave("?");
15031         break;
15032
15033       default:
15034         break;
15035     }
15036 }
15037
15038 void
15039 ReplaceComment (int index, char *text)
15040 {
15041     int len;
15042     char *p;
15043     float score;
15044
15045     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15046        pvInfoList[index-1].depth == len &&
15047        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15048        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15049     while (*text == '\n') text++;
15050     len = strlen(text);
15051     while (len > 0 && text[len - 1] == '\n') len--;
15052
15053     if (commentList[index] != NULL)
15054       free(commentList[index]);
15055
15056     if (len == 0) {
15057         commentList[index] = NULL;
15058         return;
15059     }
15060   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15061       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15062       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15063     commentList[index] = (char *) malloc(len + 2);
15064     strncpy(commentList[index], text, len);
15065     commentList[index][len] = '\n';
15066     commentList[index][len + 1] = NULLCHAR;
15067   } else {
15068     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15069     char *p;
15070     commentList[index] = (char *) malloc(len + 7);
15071     safeStrCpy(commentList[index], "{\n", 3);
15072     safeStrCpy(commentList[index]+2, text, len+1);
15073     commentList[index][len+2] = NULLCHAR;
15074     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15075     strcat(commentList[index], "\n}\n");
15076   }
15077 }
15078
15079 void
15080 CrushCRs (char *text)
15081 {
15082   char *p = text;
15083   char *q = text;
15084   char ch;
15085
15086   do {
15087     ch = *p++;
15088     if (ch == '\r') continue;
15089     *q++ = ch;
15090   } while (ch != '\0');
15091 }
15092
15093 void
15094 AppendComment (int index, char *text, Boolean addBraces)
15095 /* addBraces  tells if we should add {} */
15096 {
15097     int oldlen, len;
15098     char *old;
15099
15100 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15101     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15102
15103     CrushCRs(text);
15104     while (*text == '\n') text++;
15105     len = strlen(text);
15106     while (len > 0 && text[len - 1] == '\n') len--;
15107     text[len] = NULLCHAR;
15108
15109     if (len == 0) return;
15110
15111     if (commentList[index] != NULL) {
15112       Boolean addClosingBrace = addBraces;
15113         old = commentList[index];
15114         oldlen = strlen(old);
15115         while(commentList[index][oldlen-1] ==  '\n')
15116           commentList[index][--oldlen] = NULLCHAR;
15117         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15118         safeStrCpy(commentList[index], old, oldlen + len + 6);
15119         free(old);
15120         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15121         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15122           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15123           while (*text == '\n') { text++; len--; }
15124           commentList[index][--oldlen] = NULLCHAR;
15125       }
15126         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15127         else          strcat(commentList[index], "\n");
15128         strcat(commentList[index], text);
15129         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15130         else          strcat(commentList[index], "\n");
15131     } else {
15132         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15133         if(addBraces)
15134           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15135         else commentList[index][0] = NULLCHAR;
15136         strcat(commentList[index], text);
15137         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15138         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15139     }
15140 }
15141
15142 static char *
15143 FindStr (char * text, char * sub_text)
15144 {
15145     char * result = strstr( text, sub_text );
15146
15147     if( result != NULL ) {
15148         result += strlen( sub_text );
15149     }
15150
15151     return result;
15152 }
15153
15154 /* [AS] Try to extract PV info from PGN comment */
15155 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15156 char *
15157 GetInfoFromComment (int index, char * text)
15158 {
15159     char * sep = text, *p;
15160
15161     if( text != NULL && index > 0 ) {
15162         int score = 0;
15163         int depth = 0;
15164         int time = -1, sec = 0, deci;
15165         char * s_eval = FindStr( text, "[%eval " );
15166         char * s_emt = FindStr( text, "[%emt " );
15167
15168         if( s_eval != NULL || s_emt != NULL ) {
15169             /* New style */
15170             char delim;
15171
15172             if( s_eval != NULL ) {
15173                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15174                     return text;
15175                 }
15176
15177                 if( delim != ']' ) {
15178                     return text;
15179                 }
15180             }
15181
15182             if( s_emt != NULL ) {
15183             }
15184                 return text;
15185         }
15186         else {
15187             /* We expect something like: [+|-]nnn.nn/dd */
15188             int score_lo = 0;
15189
15190             if(*text != '{') return text; // [HGM] braces: must be normal comment
15191
15192             sep = strchr( text, '/' );
15193             if( sep == NULL || sep < (text+4) ) {
15194                 return text;
15195             }
15196
15197             p = text;
15198             if(p[1] == '(') { // comment starts with PV
15199                p = strchr(p, ')'); // locate end of PV
15200                if(p == NULL || sep < p+5) return text;
15201                // at this point we have something like "{(.*) +0.23/6 ..."
15202                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15203                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15204                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15205             }
15206             time = -1; sec = -1; deci = -1;
15207             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15208                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15209                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15210                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15211                 return text;
15212             }
15213
15214             if( score_lo < 0 || score_lo >= 100 ) {
15215                 return text;
15216             }
15217
15218             if(sec >= 0) time = 600*time + 10*sec; else
15219             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15220
15221             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15222
15223             /* [HGM] PV time: now locate end of PV info */
15224             while( *++sep >= '0' && *sep <= '9'); // strip depth
15225             if(time >= 0)
15226             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15227             if(sec >= 0)
15228             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15229             if(deci >= 0)
15230             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15231             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15232         }
15233
15234         if( depth <= 0 ) {
15235             return text;
15236         }
15237
15238         if( time < 0 ) {
15239             time = -1;
15240         }
15241
15242         pvInfoList[index-1].depth = depth;
15243         pvInfoList[index-1].score = score;
15244         pvInfoList[index-1].time  = 10*time; // centi-sec
15245         if(*sep == '}') *sep = 0; else *--sep = '{';
15246         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15247     }
15248     return sep;
15249 }
15250
15251 void
15252 SendToProgram (char *message, ChessProgramState *cps)
15253 {
15254     int count, outCount, error;
15255     char buf[MSG_SIZ];
15256
15257     if (cps->pr == NoProc) return;
15258     Attention(cps);
15259
15260     if (appData.debugMode) {
15261         TimeMark now;
15262         GetTimeMark(&now);
15263         fprintf(debugFP, "%ld >%-6s: %s",
15264                 SubtractTimeMarks(&now, &programStartTime),
15265                 cps->which, message);
15266         if(serverFP)
15267             fprintf(serverFP, "%ld >%-6s: %s",
15268                 SubtractTimeMarks(&now, &programStartTime),
15269                 cps->which, message), fflush(serverFP);
15270     }
15271
15272     count = strlen(message);
15273     outCount = OutputToProcess(cps->pr, message, count, &error);
15274     if (outCount < count && !exiting
15275                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15276       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15277       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15278         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15279             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15280                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15281                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15282                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15283             } else {
15284                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15285                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15286                 gameInfo.result = res;
15287             }
15288             gameInfo.resultDetails = StrSave(buf);
15289         }
15290         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15291         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15292     }
15293 }
15294
15295 void
15296 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15297 {
15298     char *end_str;
15299     char buf[MSG_SIZ];
15300     ChessProgramState *cps = (ChessProgramState *)closure;
15301
15302     if (isr != cps->isr) return; /* Killed intentionally */
15303     if (count <= 0) {
15304         if (count == 0) {
15305             RemoveInputSource(cps->isr);
15306             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15307                     _(cps->which), cps->program);
15308             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15309             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15310                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15311                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15312                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15313                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15314                 } else {
15315                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15316                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15317                     gameInfo.result = res;
15318                 }
15319                 gameInfo.resultDetails = StrSave(buf);
15320             }
15321             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15322             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15323         } else {
15324             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15325                     _(cps->which), cps->program);
15326             RemoveInputSource(cps->isr);
15327
15328             /* [AS] Program is misbehaving badly... kill it */
15329             if( count == -2 ) {
15330                 DestroyChildProcess( cps->pr, 9 );
15331                 cps->pr = NoProc;
15332             }
15333
15334             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15335         }
15336         return;
15337     }
15338
15339     if ((end_str = strchr(message, '\r')) != NULL)
15340       *end_str = NULLCHAR;
15341     if ((end_str = strchr(message, '\n')) != NULL)
15342       *end_str = NULLCHAR;
15343
15344     if (appData.debugMode) {
15345         TimeMark now; int print = 1;
15346         char *quote = ""; char c; int i;
15347
15348         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15349                 char start = message[0];
15350                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15351                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15352                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15353                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15354                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15355                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15356                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15357                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15358                    sscanf(message, "hint: %c", &c)!=1 && 
15359                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15360                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15361                     print = (appData.engineComments >= 2);
15362                 }
15363                 message[0] = start; // restore original message
15364         }
15365         if(print) {
15366                 GetTimeMark(&now);
15367                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15368                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15369                         quote,
15370                         message);
15371                 if(serverFP)
15372                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15373                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15374                         quote,
15375                         message), fflush(serverFP);
15376         }
15377     }
15378
15379     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15380     if (appData.icsEngineAnalyze) {
15381         if (strstr(message, "whisper") != NULL ||
15382              strstr(message, "kibitz") != NULL ||
15383             strstr(message, "tellics") != NULL) return;
15384     }
15385
15386     HandleMachineMove(message, cps);
15387 }
15388
15389
15390 void
15391 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15392 {
15393     char buf[MSG_SIZ];
15394     int seconds;
15395
15396     if( timeControl_2 > 0 ) {
15397         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15398             tc = timeControl_2;
15399         }
15400     }
15401     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15402     inc /= cps->timeOdds;
15403     st  /= cps->timeOdds;
15404
15405     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15406
15407     if (st > 0) {
15408       /* Set exact time per move, normally using st command */
15409       if (cps->stKludge) {
15410         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15411         seconds = st % 60;
15412         if (seconds == 0) {
15413           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15414         } else {
15415           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15416         }
15417       } else {
15418         snprintf(buf, MSG_SIZ, "st %d\n", st);
15419       }
15420     } else {
15421       /* Set conventional or incremental time control, using level command */
15422       if (seconds == 0) {
15423         /* Note old gnuchess bug -- minutes:seconds used to not work.
15424            Fixed in later versions, but still avoid :seconds
15425            when seconds is 0. */
15426         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15427       } else {
15428         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15429                  seconds, inc/1000.);
15430       }
15431     }
15432     SendToProgram(buf, cps);
15433
15434     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15435     /* Orthogonally, limit search to given depth */
15436     if (sd > 0) {
15437       if (cps->sdKludge) {
15438         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15439       } else {
15440         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15441       }
15442       SendToProgram(buf, cps);
15443     }
15444
15445     if(cps->nps >= 0) { /* [HGM] nps */
15446         if(cps->supportsNPS == FALSE)
15447           cps->nps = -1; // don't use if engine explicitly says not supported!
15448         else {
15449           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15450           SendToProgram(buf, cps);
15451         }
15452     }
15453 }
15454
15455 ChessProgramState *
15456 WhitePlayer ()
15457 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15458 {
15459     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15460        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15461         return &second;
15462     return &first;
15463 }
15464
15465 void
15466 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15467 {
15468     char message[MSG_SIZ];
15469     long time, otime;
15470
15471     /* Note: this routine must be called when the clocks are stopped
15472        or when they have *just* been set or switched; otherwise
15473        it will be off by the time since the current tick started.
15474     */
15475     if (machineWhite) {
15476         time = whiteTimeRemaining / 10;
15477         otime = blackTimeRemaining / 10;
15478     } else {
15479         time = blackTimeRemaining / 10;
15480         otime = whiteTimeRemaining / 10;
15481     }
15482     /* [HGM] translate opponent's time by time-odds factor */
15483     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15484
15485     if (time <= 0) time = 1;
15486     if (otime <= 0) otime = 1;
15487
15488     snprintf(message, MSG_SIZ, "time %ld\n", time);
15489     SendToProgram(message, cps);
15490
15491     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15492     SendToProgram(message, cps);
15493 }
15494
15495 int
15496 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15497 {
15498   char buf[MSG_SIZ];
15499   int len = strlen(name);
15500   int val;
15501
15502   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15503     (*p) += len + 1;
15504     sscanf(*p, "%d", &val);
15505     *loc = (val != 0);
15506     while (**p && **p != ' ')
15507       (*p)++;
15508     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15509     SendToProgram(buf, cps);
15510     return TRUE;
15511   }
15512   return FALSE;
15513 }
15514
15515 int
15516 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15517 {
15518   char buf[MSG_SIZ];
15519   int len = strlen(name);
15520   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15521     (*p) += len + 1;
15522     sscanf(*p, "%d", loc);
15523     while (**p && **p != ' ') (*p)++;
15524     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15525     SendToProgram(buf, cps);
15526     return TRUE;
15527   }
15528   return FALSE;
15529 }
15530
15531 int
15532 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15533 {
15534   char buf[MSG_SIZ];
15535   int len = strlen(name);
15536   if (strncmp((*p), name, len) == 0
15537       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15538     (*p) += len + 2;
15539     sscanf(*p, "%[^\"]", loc);
15540     while (**p && **p != '\"') (*p)++;
15541     if (**p == '\"') (*p)++;
15542     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15543     SendToProgram(buf, cps);
15544     return TRUE;
15545   }
15546   return FALSE;
15547 }
15548
15549 int
15550 ParseOption (Option *opt, ChessProgramState *cps)
15551 // [HGM] options: process the string that defines an engine option, and determine
15552 // name, type, default value, and allowed value range
15553 {
15554         char *p, *q, buf[MSG_SIZ];
15555         int n, min = (-1)<<31, max = 1<<31, def;
15556
15557         if(p = strstr(opt->name, " -spin ")) {
15558             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15559             if(max < min) max = min; // enforce consistency
15560             if(def < min) def = min;
15561             if(def > max) def = max;
15562             opt->value = def;
15563             opt->min = min;
15564             opt->max = max;
15565             opt->type = Spin;
15566         } else if((p = strstr(opt->name, " -slider "))) {
15567             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15568             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15569             if(max < min) max = min; // enforce consistency
15570             if(def < min) def = min;
15571             if(def > max) def = max;
15572             opt->value = def;
15573             opt->min = min;
15574             opt->max = max;
15575             opt->type = Spin; // Slider;
15576         } else if((p = strstr(opt->name, " -string "))) {
15577             opt->textValue = p+9;
15578             opt->type = TextBox;
15579         } else if((p = strstr(opt->name, " -file "))) {
15580             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15581             opt->textValue = p+7;
15582             opt->type = FileName; // FileName;
15583         } else if((p = strstr(opt->name, " -path "))) {
15584             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15585             opt->textValue = p+7;
15586             opt->type = PathName; // PathName;
15587         } else if(p = strstr(opt->name, " -check ")) {
15588             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15589             opt->value = (def != 0);
15590             opt->type = CheckBox;
15591         } else if(p = strstr(opt->name, " -combo ")) {
15592             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15593             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15594             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15595             opt->value = n = 0;
15596             while(q = StrStr(q, " /// ")) {
15597                 n++; *q = 0;    // count choices, and null-terminate each of them
15598                 q += 5;
15599                 if(*q == '*') { // remember default, which is marked with * prefix
15600                     q++;
15601                     opt->value = n;
15602                 }
15603                 cps->comboList[cps->comboCnt++] = q;
15604             }
15605             cps->comboList[cps->comboCnt++] = NULL;
15606             opt->max = n + 1;
15607             opt->type = ComboBox;
15608         } else if(p = strstr(opt->name, " -button")) {
15609             opt->type = Button;
15610         } else if(p = strstr(opt->name, " -save")) {
15611             opt->type = SaveButton;
15612         } else return FALSE;
15613         *p = 0; // terminate option name
15614         // now look if the command-line options define a setting for this engine option.
15615         if(cps->optionSettings && cps->optionSettings[0])
15616             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15617         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15618           snprintf(buf, MSG_SIZ, "option %s", p);
15619                 if(p = strstr(buf, ",")) *p = 0;
15620                 if(q = strchr(buf, '=')) switch(opt->type) {
15621                     case ComboBox:
15622                         for(n=0; n<opt->max; n++)
15623                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15624                         break;
15625                     case TextBox:
15626                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15627                         break;
15628                     case Spin:
15629                     case CheckBox:
15630                         opt->value = atoi(q+1);
15631                     default:
15632                         break;
15633                 }
15634                 strcat(buf, "\n");
15635                 SendToProgram(buf, cps);
15636         }
15637         return TRUE;
15638 }
15639
15640 void
15641 FeatureDone (ChessProgramState *cps, int val)
15642 {
15643   DelayedEventCallback cb = GetDelayedEvent();
15644   if ((cb == InitBackEnd3 && cps == &first) ||
15645       (cb == SettingsMenuIfReady && cps == &second) ||
15646       (cb == LoadEngine) ||
15647       (cb == TwoMachinesEventIfReady)) {
15648     CancelDelayedEvent();
15649     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15650   }
15651   cps->initDone = val;
15652 }
15653
15654 /* Parse feature command from engine */
15655 void
15656 ParseFeatures (char *args, ChessProgramState *cps)
15657 {
15658   char *p = args;
15659   char *q;
15660   int val;
15661   char buf[MSG_SIZ];
15662
15663   for (;;) {
15664     while (*p == ' ') p++;
15665     if (*p == NULLCHAR) return;
15666
15667     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15668     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15669     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15670     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15671     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15672     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15673     if (BoolFeature(&p, "reuse", &val, cps)) {
15674       /* Engine can disable reuse, but can't enable it if user said no */
15675       if (!val) cps->reuse = FALSE;
15676       continue;
15677     }
15678     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15679     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15680       if (gameMode == TwoMachinesPlay) {
15681         DisplayTwoMachinesTitle();
15682       } else {
15683         DisplayTitle("");
15684       }
15685       continue;
15686     }
15687     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15688     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15689     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15690     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15691     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15692     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15693     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15694     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15695     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15696     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15697     if (IntFeature(&p, "done", &val, cps)) {
15698       FeatureDone(cps, val);
15699       continue;
15700     }
15701     /* Added by Tord: */
15702     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15703     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15704     /* End of additions by Tord */
15705
15706     /* [HGM] added features: */
15707     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15708     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15709     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15710     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15711     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15712     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15713     if (StringFeature(&p, "option", buf, cps)) {
15714         FREE(cps->option[cps->nrOptions].name);
15715         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15716         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15717         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15718           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15719             SendToProgram(buf, cps);
15720             continue;
15721         }
15722         if(cps->nrOptions >= MAX_OPTIONS) {
15723             cps->nrOptions--;
15724             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15725             DisplayError(buf, 0);
15726         }
15727         continue;
15728     }
15729     /* End of additions by HGM */
15730
15731     /* unknown feature: complain and skip */
15732     q = p;
15733     while (*q && *q != '=') q++;
15734     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15735     SendToProgram(buf, cps);
15736     p = q;
15737     if (*p == '=') {
15738       p++;
15739       if (*p == '\"') {
15740         p++;
15741         while (*p && *p != '\"') p++;
15742         if (*p == '\"') p++;
15743       } else {
15744         while (*p && *p != ' ') p++;
15745       }
15746     }
15747   }
15748
15749 }
15750
15751 void
15752 PeriodicUpdatesEvent (int newState)
15753 {
15754     if (newState == appData.periodicUpdates)
15755       return;
15756
15757     appData.periodicUpdates=newState;
15758
15759     /* Display type changes, so update it now */
15760 //    DisplayAnalysis();
15761
15762     /* Get the ball rolling again... */
15763     if (newState) {
15764         AnalysisPeriodicEvent(1);
15765         StartAnalysisClock();
15766     }
15767 }
15768
15769 void
15770 PonderNextMoveEvent (int newState)
15771 {
15772     if (newState == appData.ponderNextMove) return;
15773     if (gameMode == EditPosition) EditPositionDone(TRUE);
15774     if (newState) {
15775         SendToProgram("hard\n", &first);
15776         if (gameMode == TwoMachinesPlay) {
15777             SendToProgram("hard\n", &second);
15778         }
15779     } else {
15780         SendToProgram("easy\n", &first);
15781         thinkOutput[0] = NULLCHAR;
15782         if (gameMode == TwoMachinesPlay) {
15783             SendToProgram("easy\n", &second);
15784         }
15785     }
15786     appData.ponderNextMove = newState;
15787 }
15788
15789 void
15790 NewSettingEvent (int option, int *feature, char *command, int value)
15791 {
15792     char buf[MSG_SIZ];
15793
15794     if (gameMode == EditPosition) EditPositionDone(TRUE);
15795     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15796     if(feature == NULL || *feature) SendToProgram(buf, &first);
15797     if (gameMode == TwoMachinesPlay) {
15798         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15799     }
15800 }
15801
15802 void
15803 ShowThinkingEvent ()
15804 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15805 {
15806     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15807     int newState = appData.showThinking
15808         // [HGM] thinking: other features now need thinking output as well
15809         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15810
15811     if (oldState == newState) return;
15812     oldState = newState;
15813     if (gameMode == EditPosition) EditPositionDone(TRUE);
15814     if (oldState) {
15815         SendToProgram("post\n", &first);
15816         if (gameMode == TwoMachinesPlay) {
15817             SendToProgram("post\n", &second);
15818         }
15819     } else {
15820         SendToProgram("nopost\n", &first);
15821         thinkOutput[0] = NULLCHAR;
15822         if (gameMode == TwoMachinesPlay) {
15823             SendToProgram("nopost\n", &second);
15824         }
15825     }
15826 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15827 }
15828
15829 void
15830 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15831 {
15832   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15833   if (pr == NoProc) return;
15834   AskQuestion(title, question, replyPrefix, pr);
15835 }
15836
15837 void
15838 TypeInEvent (char firstChar)
15839 {
15840     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15841         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15842         gameMode == AnalyzeMode || gameMode == EditGame || 
15843         gameMode == EditPosition || gameMode == IcsExamining ||
15844         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15845         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15846                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15847                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15848         gameMode == Training) PopUpMoveDialog(firstChar);
15849 }
15850
15851 void
15852 TypeInDoneEvent (char *move)
15853 {
15854         Board board;
15855         int n, fromX, fromY, toX, toY;
15856         char promoChar;
15857         ChessMove moveType;
15858
15859         // [HGM] FENedit
15860         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15861                 EditPositionPasteFEN(move);
15862                 return;
15863         }
15864         // [HGM] movenum: allow move number to be typed in any mode
15865         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15866           ToNrEvent(2*n-1);
15867           return;
15868         }
15869         // undocumented kludge: allow command-line option to be typed in!
15870         // (potentially fatal, and does not implement the effect of the option.)
15871         // should only be used for options that are values on which future decisions will be made,
15872         // and definitely not on options that would be used during initialization.
15873         if(strstr(move, "!!! -") == move) {
15874             ParseArgsFromString(move+4);
15875             return;
15876         }
15877
15878       if (gameMode != EditGame && currentMove != forwardMostMove && 
15879         gameMode != Training) {
15880         DisplayMoveError(_("Displayed move is not current"));
15881       } else {
15882         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15883           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15884         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15885         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15886           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15887           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15888         } else {
15889           DisplayMoveError(_("Could not parse move"));
15890         }
15891       }
15892 }
15893
15894 void
15895 DisplayMove (int moveNumber)
15896 {
15897     char message[MSG_SIZ];
15898     char res[MSG_SIZ];
15899     char cpThinkOutput[MSG_SIZ];
15900
15901     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15902
15903     if (moveNumber == forwardMostMove - 1 ||
15904         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15905
15906         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15907
15908         if (strchr(cpThinkOutput, '\n')) {
15909             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15910         }
15911     } else {
15912         *cpThinkOutput = NULLCHAR;
15913     }
15914
15915     /* [AS] Hide thinking from human user */
15916     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15917         *cpThinkOutput = NULLCHAR;
15918         if( thinkOutput[0] != NULLCHAR ) {
15919             int i;
15920
15921             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15922                 cpThinkOutput[i] = '.';
15923             }
15924             cpThinkOutput[i] = NULLCHAR;
15925             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15926         }
15927     }
15928
15929     if (moveNumber == forwardMostMove - 1 &&
15930         gameInfo.resultDetails != NULL) {
15931         if (gameInfo.resultDetails[0] == NULLCHAR) {
15932           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15933         } else {
15934           snprintf(res, MSG_SIZ, " {%s} %s",
15935                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15936         }
15937     } else {
15938         res[0] = NULLCHAR;
15939     }
15940
15941     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15942         DisplayMessage(res, cpThinkOutput);
15943     } else {
15944       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15945                 WhiteOnMove(moveNumber) ? " " : ".. ",
15946                 parseList[moveNumber], res);
15947         DisplayMessage(message, cpThinkOutput);
15948     }
15949 }
15950
15951 void
15952 DisplayComment (int moveNumber, char *text)
15953 {
15954     char title[MSG_SIZ];
15955
15956     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15957       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15958     } else {
15959       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15960               WhiteOnMove(moveNumber) ? " " : ".. ",
15961               parseList[moveNumber]);
15962     }
15963     if (text != NULL && (appData.autoDisplayComment || commentUp))
15964         CommentPopUp(title, text);
15965 }
15966
15967 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15968  * might be busy thinking or pondering.  It can be omitted if your
15969  * gnuchess is configured to stop thinking immediately on any user
15970  * input.  However, that gnuchess feature depends on the FIONREAD
15971  * ioctl, which does not work properly on some flavors of Unix.
15972  */
15973 void
15974 Attention (ChessProgramState *cps)
15975 {
15976 #if ATTENTION
15977     if (!cps->useSigint) return;
15978     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15979     switch (gameMode) {
15980       case MachinePlaysWhite:
15981       case MachinePlaysBlack:
15982       case TwoMachinesPlay:
15983       case IcsPlayingWhite:
15984       case IcsPlayingBlack:
15985       case AnalyzeMode:
15986       case AnalyzeFile:
15987         /* Skip if we know it isn't thinking */
15988         if (!cps->maybeThinking) return;
15989         if (appData.debugMode)
15990           fprintf(debugFP, "Interrupting %s\n", cps->which);
15991         InterruptChildProcess(cps->pr);
15992         cps->maybeThinking = FALSE;
15993         break;
15994       default:
15995         break;
15996     }
15997 #endif /*ATTENTION*/
15998 }
15999
16000 int
16001 CheckFlags ()
16002 {
16003     if (whiteTimeRemaining <= 0) {
16004         if (!whiteFlag) {
16005             whiteFlag = TRUE;
16006             if (appData.icsActive) {
16007                 if (appData.autoCallFlag &&
16008                     gameMode == IcsPlayingBlack && !blackFlag) {
16009                   SendToICS(ics_prefix);
16010                   SendToICS("flag\n");
16011                 }
16012             } else {
16013                 if (blackFlag) {
16014                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16015                 } else {
16016                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16017                     if (appData.autoCallFlag) {
16018                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16019                         return TRUE;
16020                     }
16021                 }
16022             }
16023         }
16024     }
16025     if (blackTimeRemaining <= 0) {
16026         if (!blackFlag) {
16027             blackFlag = TRUE;
16028             if (appData.icsActive) {
16029                 if (appData.autoCallFlag &&
16030                     gameMode == IcsPlayingWhite && !whiteFlag) {
16031                   SendToICS(ics_prefix);
16032                   SendToICS("flag\n");
16033                 }
16034             } else {
16035                 if (whiteFlag) {
16036                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16037                 } else {
16038                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16039                     if (appData.autoCallFlag) {
16040                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16041                         return TRUE;
16042                     }
16043                 }
16044             }
16045         }
16046     }
16047     return FALSE;
16048 }
16049
16050 void
16051 CheckTimeControl ()
16052 {
16053     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16054         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16055
16056     /*
16057      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16058      */
16059     if ( !WhiteOnMove(forwardMostMove) ) {
16060         /* White made time control */
16061         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16062         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16063         /* [HGM] time odds: correct new time quota for time odds! */
16064                                             / WhitePlayer()->timeOdds;
16065         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16066     } else {
16067         lastBlack -= blackTimeRemaining;
16068         /* Black made time control */
16069         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16070                                             / WhitePlayer()->other->timeOdds;
16071         lastWhite = whiteTimeRemaining;
16072     }
16073 }
16074
16075 void
16076 DisplayBothClocks ()
16077 {
16078     int wom = gameMode == EditPosition ?
16079       !blackPlaysFirst : WhiteOnMove(currentMove);
16080     DisplayWhiteClock(whiteTimeRemaining, wom);
16081     DisplayBlackClock(blackTimeRemaining, !wom);
16082 }
16083
16084
16085 /* Timekeeping seems to be a portability nightmare.  I think everyone
16086    has ftime(), but I'm really not sure, so I'm including some ifdefs
16087    to use other calls if you don't.  Clocks will be less accurate if
16088    you have neither ftime nor gettimeofday.
16089 */
16090
16091 /* VS 2008 requires the #include outside of the function */
16092 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16093 #include <sys/timeb.h>
16094 #endif
16095
16096 /* Get the current time as a TimeMark */
16097 void
16098 GetTimeMark (TimeMark *tm)
16099 {
16100 #if HAVE_GETTIMEOFDAY
16101
16102     struct timeval timeVal;
16103     struct timezone timeZone;
16104
16105     gettimeofday(&timeVal, &timeZone);
16106     tm->sec = (long) timeVal.tv_sec;
16107     tm->ms = (int) (timeVal.tv_usec / 1000L);
16108
16109 #else /*!HAVE_GETTIMEOFDAY*/
16110 #if HAVE_FTIME
16111
16112 // include <sys/timeb.h> / moved to just above start of function
16113     struct timeb timeB;
16114
16115     ftime(&timeB);
16116     tm->sec = (long) timeB.time;
16117     tm->ms = (int) timeB.millitm;
16118
16119 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16120     tm->sec = (long) time(NULL);
16121     tm->ms = 0;
16122 #endif
16123 #endif
16124 }
16125
16126 /* Return the difference in milliseconds between two
16127    time marks.  We assume the difference will fit in a long!
16128 */
16129 long
16130 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16131 {
16132     return 1000L*(tm2->sec - tm1->sec) +
16133            (long) (tm2->ms - tm1->ms);
16134 }
16135
16136
16137 /*
16138  * Code to manage the game clocks.
16139  *
16140  * In tournament play, black starts the clock and then white makes a move.
16141  * We give the human user a slight advantage if he is playing white---the
16142  * clocks don't run until he makes his first move, so it takes zero time.
16143  * Also, we don't account for network lag, so we could get out of sync
16144  * with GNU Chess's clock -- but then, referees are always right.
16145  */
16146
16147 static TimeMark tickStartTM;
16148 static long intendedTickLength;
16149
16150 long
16151 NextTickLength (long timeRemaining)
16152 {
16153     long nominalTickLength, nextTickLength;
16154
16155     if (timeRemaining > 0L && timeRemaining <= 10000L)
16156       nominalTickLength = 100L;
16157     else
16158       nominalTickLength = 1000L;
16159     nextTickLength = timeRemaining % nominalTickLength;
16160     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16161
16162     return nextTickLength;
16163 }
16164
16165 /* Adjust clock one minute up or down */
16166 void
16167 AdjustClock (Boolean which, int dir)
16168 {
16169     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16170     if(which) blackTimeRemaining += 60000*dir;
16171     else      whiteTimeRemaining += 60000*dir;
16172     DisplayBothClocks();
16173     adjustedClock = TRUE;
16174 }
16175
16176 /* Stop clocks and reset to a fresh time control */
16177 void
16178 ResetClocks ()
16179 {
16180     (void) StopClockTimer();
16181     if (appData.icsActive) {
16182         whiteTimeRemaining = blackTimeRemaining = 0;
16183     } else if (searchTime) {
16184         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16185         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16186     } else { /* [HGM] correct new time quote for time odds */
16187         whiteTC = blackTC = fullTimeControlString;
16188         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16189         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16190     }
16191     if (whiteFlag || blackFlag) {
16192         DisplayTitle("");
16193         whiteFlag = blackFlag = FALSE;
16194     }
16195     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16196     DisplayBothClocks();
16197     adjustedClock = FALSE;
16198 }
16199
16200 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16201
16202 /* Decrement running clock by amount of time that has passed */
16203 void
16204 DecrementClocks ()
16205 {
16206     long timeRemaining;
16207     long lastTickLength, fudge;
16208     TimeMark now;
16209
16210     if (!appData.clockMode) return;
16211     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16212
16213     GetTimeMark(&now);
16214
16215     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16216
16217     /* Fudge if we woke up a little too soon */
16218     fudge = intendedTickLength - lastTickLength;
16219     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16220
16221     if (WhiteOnMove(forwardMostMove)) {
16222         if(whiteNPS >= 0) lastTickLength = 0;
16223         timeRemaining = whiteTimeRemaining -= lastTickLength;
16224         if(timeRemaining < 0 && !appData.icsActive) {
16225             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16226             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16227                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16228                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16229             }
16230         }
16231         DisplayWhiteClock(whiteTimeRemaining - fudge,
16232                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16233     } else {
16234         if(blackNPS >= 0) lastTickLength = 0;
16235         timeRemaining = blackTimeRemaining -= lastTickLength;
16236         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16237             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16238             if(suddenDeath) {
16239                 blackStartMove = forwardMostMove;
16240                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16241             }
16242         }
16243         DisplayBlackClock(blackTimeRemaining - fudge,
16244                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16245     }
16246     if (CheckFlags()) return;
16247
16248     if(twoBoards) { // count down secondary board's clocks as well
16249         activePartnerTime -= lastTickLength;
16250         partnerUp = 1;
16251         if(activePartner == 'W')
16252             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16253         else
16254             DisplayBlackClock(activePartnerTime, TRUE);
16255         partnerUp = 0;
16256     }
16257
16258     tickStartTM = now;
16259     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16260     StartClockTimer(intendedTickLength);
16261
16262     /* if the time remaining has fallen below the alarm threshold, sound the
16263      * alarm. if the alarm has sounded and (due to a takeback or time control
16264      * with increment) the time remaining has increased to a level above the
16265      * threshold, reset the alarm so it can sound again.
16266      */
16267
16268     if (appData.icsActive && appData.icsAlarm) {
16269
16270         /* make sure we are dealing with the user's clock */
16271         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16272                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16273            )) return;
16274
16275         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16276             alarmSounded = FALSE;
16277         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16278             PlayAlarmSound();
16279             alarmSounded = TRUE;
16280         }
16281     }
16282 }
16283
16284
16285 /* A player has just moved, so stop the previously running
16286    clock and (if in clock mode) start the other one.
16287    We redisplay both clocks in case we're in ICS mode, because
16288    ICS gives us an update to both clocks after every move.
16289    Note that this routine is called *after* forwardMostMove
16290    is updated, so the last fractional tick must be subtracted
16291    from the color that is *not* on move now.
16292 */
16293 void
16294 SwitchClocks (int newMoveNr)
16295 {
16296     long lastTickLength;
16297     TimeMark now;
16298     int flagged = FALSE;
16299
16300     GetTimeMark(&now);
16301
16302     if (StopClockTimer() && appData.clockMode) {
16303         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16304         if (!WhiteOnMove(forwardMostMove)) {
16305             if(blackNPS >= 0) lastTickLength = 0;
16306             blackTimeRemaining -= lastTickLength;
16307            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16308 //         if(pvInfoList[forwardMostMove].time == -1)
16309                  pvInfoList[forwardMostMove].time =               // use GUI time
16310                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16311         } else {
16312            if(whiteNPS >= 0) lastTickLength = 0;
16313            whiteTimeRemaining -= lastTickLength;
16314            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16315 //         if(pvInfoList[forwardMostMove].time == -1)
16316                  pvInfoList[forwardMostMove].time =
16317                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16318         }
16319         flagged = CheckFlags();
16320     }
16321     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16322     CheckTimeControl();
16323
16324     if (flagged || !appData.clockMode) return;
16325
16326     switch (gameMode) {
16327       case MachinePlaysBlack:
16328       case MachinePlaysWhite:
16329       case BeginningOfGame:
16330         if (pausing) return;
16331         break;
16332
16333       case EditGame:
16334       case PlayFromGameFile:
16335       case IcsExamining:
16336         return;
16337
16338       default:
16339         break;
16340     }
16341
16342     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16343         if(WhiteOnMove(forwardMostMove))
16344              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16345         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16346     }
16347
16348     tickStartTM = now;
16349     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16350       whiteTimeRemaining : blackTimeRemaining);
16351     StartClockTimer(intendedTickLength);
16352 }
16353
16354
16355 /* Stop both clocks */
16356 void
16357 StopClocks ()
16358 {
16359     long lastTickLength;
16360     TimeMark now;
16361
16362     if (!StopClockTimer()) return;
16363     if (!appData.clockMode) return;
16364
16365     GetTimeMark(&now);
16366
16367     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16368     if (WhiteOnMove(forwardMostMove)) {
16369         if(whiteNPS >= 0) lastTickLength = 0;
16370         whiteTimeRemaining -= lastTickLength;
16371         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16372     } else {
16373         if(blackNPS >= 0) lastTickLength = 0;
16374         blackTimeRemaining -= lastTickLength;
16375         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16376     }
16377     CheckFlags();
16378 }
16379
16380 /* Start clock of player on move.  Time may have been reset, so
16381    if clock is already running, stop and restart it. */
16382 void
16383 StartClocks ()
16384 {
16385     (void) StopClockTimer(); /* in case it was running already */
16386     DisplayBothClocks();
16387     if (CheckFlags()) return;
16388
16389     if (!appData.clockMode) return;
16390     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16391
16392     GetTimeMark(&tickStartTM);
16393     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16394       whiteTimeRemaining : blackTimeRemaining);
16395
16396    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16397     whiteNPS = blackNPS = -1;
16398     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16399        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16400         whiteNPS = first.nps;
16401     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16402        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16403         blackNPS = first.nps;
16404     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16405         whiteNPS = second.nps;
16406     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16407         blackNPS = second.nps;
16408     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16409
16410     StartClockTimer(intendedTickLength);
16411 }
16412
16413 char *
16414 TimeString (long ms)
16415 {
16416     long second, minute, hour, day;
16417     char *sign = "";
16418     static char buf[32];
16419
16420     if (ms > 0 && ms <= 9900) {
16421       /* convert milliseconds to tenths, rounding up */
16422       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16423
16424       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16425       return buf;
16426     }
16427
16428     /* convert milliseconds to seconds, rounding up */
16429     /* use floating point to avoid strangeness of integer division
16430        with negative dividends on many machines */
16431     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16432
16433     if (second < 0) {
16434         sign = "-";
16435         second = -second;
16436     }
16437
16438     day = second / (60 * 60 * 24);
16439     second = second % (60 * 60 * 24);
16440     hour = second / (60 * 60);
16441     second = second % (60 * 60);
16442     minute = second / 60;
16443     second = second % 60;
16444
16445     if (day > 0)
16446       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16447               sign, day, hour, minute, second);
16448     else if (hour > 0)
16449       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16450     else
16451       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16452
16453     return buf;
16454 }
16455
16456
16457 /*
16458  * This is necessary because some C libraries aren't ANSI C compliant yet.
16459  */
16460 char *
16461 StrStr (char *string, char *match)
16462 {
16463     int i, length;
16464
16465     length = strlen(match);
16466
16467     for (i = strlen(string) - length; i >= 0; i--, string++)
16468       if (!strncmp(match, string, length))
16469         return string;
16470
16471     return NULL;
16472 }
16473
16474 char *
16475 StrCaseStr (char *string, char *match)
16476 {
16477     int i, j, length;
16478
16479     length = strlen(match);
16480
16481     for (i = strlen(string) - length; i >= 0; i--, string++) {
16482         for (j = 0; j < length; j++) {
16483             if (ToLower(match[j]) != ToLower(string[j]))
16484               break;
16485         }
16486         if (j == length) return string;
16487     }
16488
16489     return NULL;
16490 }
16491
16492 #ifndef _amigados
16493 int
16494 StrCaseCmp (char *s1, char *s2)
16495 {
16496     char c1, c2;
16497
16498     for (;;) {
16499         c1 = ToLower(*s1++);
16500         c2 = ToLower(*s2++);
16501         if (c1 > c2) return 1;
16502         if (c1 < c2) return -1;
16503         if (c1 == NULLCHAR) return 0;
16504     }
16505 }
16506
16507
16508 int
16509 ToLower (int c)
16510 {
16511     return isupper(c) ? tolower(c) : c;
16512 }
16513
16514
16515 int
16516 ToUpper (int c)
16517 {
16518     return islower(c) ? toupper(c) : c;
16519 }
16520 #endif /* !_amigados    */
16521
16522 char *
16523 StrSave (char *s)
16524 {
16525   char *ret;
16526
16527   if ((ret = (char *) malloc(strlen(s) + 1)))
16528     {
16529       safeStrCpy(ret, s, strlen(s)+1);
16530     }
16531   return ret;
16532 }
16533
16534 char *
16535 StrSavePtr (char *s, char **savePtr)
16536 {
16537     if (*savePtr) {
16538         free(*savePtr);
16539     }
16540     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16541       safeStrCpy(*savePtr, s, strlen(s)+1);
16542     }
16543     return(*savePtr);
16544 }
16545
16546 char *
16547 PGNDate ()
16548 {
16549     time_t clock;
16550     struct tm *tm;
16551     char buf[MSG_SIZ];
16552
16553     clock = time((time_t *)NULL);
16554     tm = localtime(&clock);
16555     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16556             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16557     return StrSave(buf);
16558 }
16559
16560
16561 char *
16562 PositionToFEN (int move, char *overrideCastling)
16563 {
16564     int i, j, fromX, fromY, toX, toY;
16565     int whiteToPlay;
16566     char buf[MSG_SIZ];
16567     char *p, *q;
16568     int emptycount;
16569     ChessSquare piece;
16570
16571     whiteToPlay = (gameMode == EditPosition) ?
16572       !blackPlaysFirst : (move % 2 == 0);
16573     p = buf;
16574
16575     /* Piece placement data */
16576     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16577         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16578         emptycount = 0;
16579         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16580             if (boards[move][i][j] == EmptySquare) {
16581                 emptycount++;
16582             } else { ChessSquare piece = boards[move][i][j];
16583                 if (emptycount > 0) {
16584                     if(emptycount<10) /* [HGM] can be >= 10 */
16585                         *p++ = '0' + emptycount;
16586                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16587                     emptycount = 0;
16588                 }
16589                 if(PieceToChar(piece) == '+') {
16590                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16591                     *p++ = '+';
16592                     piece = (ChessSquare)(DEMOTED piece);
16593                 }
16594                 *p++ = PieceToChar(piece);
16595                 if(p[-1] == '~') {
16596                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16597                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16598                     *p++ = '~';
16599                 }
16600             }
16601         }
16602         if (emptycount > 0) {
16603             if(emptycount<10) /* [HGM] can be >= 10 */
16604                 *p++ = '0' + emptycount;
16605             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16606             emptycount = 0;
16607         }
16608         *p++ = '/';
16609     }
16610     *(p - 1) = ' ';
16611
16612     /* [HGM] print Crazyhouse or Shogi holdings */
16613     if( gameInfo.holdingsWidth ) {
16614         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16615         q = p;
16616         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16617             piece = boards[move][i][BOARD_WIDTH-1];
16618             if( piece != EmptySquare )
16619               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16620                   *p++ = PieceToChar(piece);
16621         }
16622         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16623             piece = boards[move][BOARD_HEIGHT-i-1][0];
16624             if( piece != EmptySquare )
16625               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16626                   *p++ = PieceToChar(piece);
16627         }
16628
16629         if( q == p ) *p++ = '-';
16630         *p++ = ']';
16631         *p++ = ' ';
16632     }
16633
16634     /* Active color */
16635     *p++ = whiteToPlay ? 'w' : 'b';
16636     *p++ = ' ';
16637
16638   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16639     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16640   } else {
16641   if(nrCastlingRights) {
16642      q = p;
16643      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16644        /* [HGM] write directly from rights */
16645            if(boards[move][CASTLING][2] != NoRights &&
16646               boards[move][CASTLING][0] != NoRights   )
16647                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16648            if(boards[move][CASTLING][2] != NoRights &&
16649               boards[move][CASTLING][1] != NoRights   )
16650                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16651            if(boards[move][CASTLING][5] != NoRights &&
16652               boards[move][CASTLING][3] != NoRights   )
16653                 *p++ = boards[move][CASTLING][3] + AAA;
16654            if(boards[move][CASTLING][5] != NoRights &&
16655               boards[move][CASTLING][4] != NoRights   )
16656                 *p++ = boards[move][CASTLING][4] + AAA;
16657      } else {
16658
16659         /* [HGM] write true castling rights */
16660         if( nrCastlingRights == 6 ) {
16661             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16662                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16663             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16664                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16665             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16666                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16667             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16668                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16669         }
16670      }
16671      if (q == p) *p++ = '-'; /* No castling rights */
16672      *p++ = ' ';
16673   }
16674
16675   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16676      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16677     /* En passant target square */
16678     if (move > backwardMostMove) {
16679         fromX = moveList[move - 1][0] - AAA;
16680         fromY = moveList[move - 1][1] - ONE;
16681         toX = moveList[move - 1][2] - AAA;
16682         toY = moveList[move - 1][3] - ONE;
16683         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16684             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16685             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16686             fromX == toX) {
16687             /* 2-square pawn move just happened */
16688             *p++ = toX + AAA;
16689             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16690         } else {
16691             *p++ = '-';
16692         }
16693     } else if(move == backwardMostMove) {
16694         // [HGM] perhaps we should always do it like this, and forget the above?
16695         if((signed char)boards[move][EP_STATUS] >= 0) {
16696             *p++ = boards[move][EP_STATUS] + AAA;
16697             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16698         } else {
16699             *p++ = '-';
16700         }
16701     } else {
16702         *p++ = '-';
16703     }
16704     *p++ = ' ';
16705   }
16706   }
16707
16708     /* [HGM] find reversible plies */
16709     {   int i = 0, j=move;
16710
16711         if (appData.debugMode) { int k;
16712             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16713             for(k=backwardMostMove; k<=forwardMostMove; k++)
16714                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16715
16716         }
16717
16718         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16719         if( j == backwardMostMove ) i += initialRulePlies;
16720         sprintf(p, "%d ", i);
16721         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16722     }
16723     /* Fullmove number */
16724     sprintf(p, "%d", (move / 2) + 1);
16725
16726     return StrSave(buf);
16727 }
16728
16729 Boolean
16730 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16731 {
16732     int i, j;
16733     char *p, c;
16734     int emptycount;
16735     ChessSquare piece;
16736
16737     p = fen;
16738
16739     /* [HGM] by default clear Crazyhouse holdings, if present */
16740     if(gameInfo.holdingsWidth) {
16741        for(i=0; i<BOARD_HEIGHT; i++) {
16742            board[i][0]             = EmptySquare; /* black holdings */
16743            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16744            board[i][1]             = (ChessSquare) 0; /* black counts */
16745            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16746        }
16747     }
16748
16749     /* Piece placement data */
16750     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16751         j = 0;
16752         for (;;) {
16753             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16754                 if (*p == '/') p++;
16755                 emptycount = gameInfo.boardWidth - j;
16756                 while (emptycount--)
16757                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16758                 break;
16759 #if(BOARD_FILES >= 10)
16760             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16761                 p++; emptycount=10;
16762                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16763                 while (emptycount--)
16764                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16765 #endif
16766             } else if (isdigit(*p)) {
16767                 emptycount = *p++ - '0';
16768                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16769                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16770                 while (emptycount--)
16771                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16772             } else if (*p == '+' || isalpha(*p)) {
16773                 if (j >= gameInfo.boardWidth) return FALSE;
16774                 if(*p=='+') {
16775                     piece = CharToPiece(*++p);
16776                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16777                     piece = (ChessSquare) (PROMOTED piece ); p++;
16778                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16779                 } else piece = CharToPiece(*p++);
16780
16781                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16782                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16783                     piece = (ChessSquare) (PROMOTED piece);
16784                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16785                     p++;
16786                 }
16787                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16788             } else {
16789                 return FALSE;
16790             }
16791         }
16792     }
16793     while (*p == '/' || *p == ' ') p++;
16794
16795     /* [HGM] look for Crazyhouse holdings here */
16796     while(*p==' ') p++;
16797     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16798         if(*p == '[') p++;
16799         if(*p == '-' ) p++; /* empty holdings */ else {
16800             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16801             /* if we would allow FEN reading to set board size, we would   */
16802             /* have to add holdings and shift the board read so far here   */
16803             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16804                 p++;
16805                 if((int) piece >= (int) BlackPawn ) {
16806                     i = (int)piece - (int)BlackPawn;
16807                     i = PieceToNumber((ChessSquare)i);
16808                     if( i >= gameInfo.holdingsSize ) return FALSE;
16809                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16810                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16811                 } else {
16812                     i = (int)piece - (int)WhitePawn;
16813                     i = PieceToNumber((ChessSquare)i);
16814                     if( i >= gameInfo.holdingsSize ) return FALSE;
16815                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16816                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16817                 }
16818             }
16819         }
16820         if(*p == ']') p++;
16821     }
16822
16823     while(*p == ' ') p++;
16824
16825     /* Active color */
16826     c = *p++;
16827     if(appData.colorNickNames) {
16828       if( c == appData.colorNickNames[0] ) c = 'w'; else
16829       if( c == appData.colorNickNames[1] ) c = 'b';
16830     }
16831     switch (c) {
16832       case 'w':
16833         *blackPlaysFirst = FALSE;
16834         break;
16835       case 'b':
16836         *blackPlaysFirst = TRUE;
16837         break;
16838       default:
16839         return FALSE;
16840     }
16841
16842     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16843     /* return the extra info in global variiables             */
16844
16845     /* set defaults in case FEN is incomplete */
16846     board[EP_STATUS] = EP_UNKNOWN;
16847     for(i=0; i<nrCastlingRights; i++ ) {
16848         board[CASTLING][i] =
16849             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16850     }   /* assume possible unless obviously impossible */
16851     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16852     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16853     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16854                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16855     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16856     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16857     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16858                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16859     FENrulePlies = 0;
16860
16861     while(*p==' ') p++;
16862     if(nrCastlingRights) {
16863       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16864           /* castling indicator present, so default becomes no castlings */
16865           for(i=0; i<nrCastlingRights; i++ ) {
16866                  board[CASTLING][i] = NoRights;
16867           }
16868       }
16869       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16870              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16871              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16872              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16873         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16874
16875         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16876             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16877             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16878         }
16879         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16880             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16881         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16882                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16883         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16884                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16885         switch(c) {
16886           case'K':
16887               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16888               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16889               board[CASTLING][2] = whiteKingFile;
16890               break;
16891           case'Q':
16892               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16893               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16894               board[CASTLING][2] = whiteKingFile;
16895               break;
16896           case'k':
16897               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16898               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16899               board[CASTLING][5] = blackKingFile;
16900               break;
16901           case'q':
16902               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16903               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16904               board[CASTLING][5] = blackKingFile;
16905           case '-':
16906               break;
16907           default: /* FRC castlings */
16908               if(c >= 'a') { /* black rights */
16909                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16910                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16911                   if(i == BOARD_RGHT) break;
16912                   board[CASTLING][5] = i;
16913                   c -= AAA;
16914                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16915                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16916                   if(c > i)
16917                       board[CASTLING][3] = c;
16918                   else
16919                       board[CASTLING][4] = c;
16920               } else { /* white rights */
16921                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16922                     if(board[0][i] == WhiteKing) break;
16923                   if(i == BOARD_RGHT) break;
16924                   board[CASTLING][2] = i;
16925                   c -= AAA - 'a' + 'A';
16926                   if(board[0][c] >= WhiteKing) break;
16927                   if(c > i)
16928                       board[CASTLING][0] = c;
16929                   else
16930                       board[CASTLING][1] = c;
16931               }
16932         }
16933       }
16934       for(i=0; i<nrCastlingRights; i++)
16935         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16936     if (appData.debugMode) {
16937         fprintf(debugFP, "FEN castling rights:");
16938         for(i=0; i<nrCastlingRights; i++)
16939         fprintf(debugFP, " %d", board[CASTLING][i]);
16940         fprintf(debugFP, "\n");
16941     }
16942
16943       while(*p==' ') p++;
16944     }
16945
16946     /* read e.p. field in games that know e.p. capture */
16947     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16948        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16949       if(*p=='-') {
16950         p++; board[EP_STATUS] = EP_NONE;
16951       } else {
16952          char c = *p++ - AAA;
16953
16954          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16955          if(*p >= '0' && *p <='9') p++;
16956          board[EP_STATUS] = c;
16957       }
16958     }
16959
16960
16961     if(sscanf(p, "%d", &i) == 1) {
16962         FENrulePlies = i; /* 50-move ply counter */
16963         /* (The move number is still ignored)    */
16964     }
16965
16966     return TRUE;
16967 }
16968
16969 void
16970 EditPositionPasteFEN (char *fen)
16971 {
16972   if (fen != NULL) {
16973     Board initial_position;
16974
16975     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16976       DisplayError(_("Bad FEN position in clipboard"), 0);
16977       return ;
16978     } else {
16979       int savedBlackPlaysFirst = blackPlaysFirst;
16980       EditPositionEvent();
16981       blackPlaysFirst = savedBlackPlaysFirst;
16982       CopyBoard(boards[0], initial_position);
16983       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16984       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16985       DisplayBothClocks();
16986       DrawPosition(FALSE, boards[currentMove]);
16987     }
16988   }
16989 }
16990
16991 static char cseq[12] = "\\   ";
16992
16993 Boolean
16994 set_cont_sequence (char *new_seq)
16995 {
16996     int len;
16997     Boolean ret;
16998
16999     // handle bad attempts to set the sequence
17000         if (!new_seq)
17001                 return 0; // acceptable error - no debug
17002
17003     len = strlen(new_seq);
17004     ret = (len > 0) && (len < sizeof(cseq));
17005     if (ret)
17006       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17007     else if (appData.debugMode)
17008       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17009     return ret;
17010 }
17011
17012 /*
17013     reformat a source message so words don't cross the width boundary.  internal
17014     newlines are not removed.  returns the wrapped size (no null character unless
17015     included in source message).  If dest is NULL, only calculate the size required
17016     for the dest buffer.  lp argument indicats line position upon entry, and it's
17017     passed back upon exit.
17018 */
17019 int
17020 wrap (char *dest, char *src, int count, int width, int *lp)
17021 {
17022     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17023
17024     cseq_len = strlen(cseq);
17025     old_line = line = *lp;
17026     ansi = len = clen = 0;
17027
17028     for (i=0; i < count; i++)
17029     {
17030         if (src[i] == '\033')
17031             ansi = 1;
17032
17033         // if we hit the width, back up
17034         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17035         {
17036             // store i & len in case the word is too long
17037             old_i = i, old_len = len;
17038
17039             // find the end of the last word
17040             while (i && src[i] != ' ' && src[i] != '\n')
17041             {
17042                 i--;
17043                 len--;
17044             }
17045
17046             // word too long?  restore i & len before splitting it
17047             if ((old_i-i+clen) >= width)
17048             {
17049                 i = old_i;
17050                 len = old_len;
17051             }
17052
17053             // extra space?
17054             if (i && src[i-1] == ' ')
17055                 len--;
17056
17057             if (src[i] != ' ' && src[i] != '\n')
17058             {
17059                 i--;
17060                 if (len)
17061                     len--;
17062             }
17063
17064             // now append the newline and continuation sequence
17065             if (dest)
17066                 dest[len] = '\n';
17067             len++;
17068             if (dest)
17069                 strncpy(dest+len, cseq, cseq_len);
17070             len += cseq_len;
17071             line = cseq_len;
17072             clen = cseq_len;
17073             continue;
17074         }
17075
17076         if (dest)
17077             dest[len] = src[i];
17078         len++;
17079         if (!ansi)
17080             line++;
17081         if (src[i] == '\n')
17082             line = 0;
17083         if (src[i] == 'm')
17084             ansi = 0;
17085     }
17086     if (dest && appData.debugMode)
17087     {
17088         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17089             count, width, line, len, *lp);
17090         show_bytes(debugFP, src, count);
17091         fprintf(debugFP, "\ndest: ");
17092         show_bytes(debugFP, dest, len);
17093         fprintf(debugFP, "\n");
17094     }
17095     *lp = dest ? line : old_line;
17096
17097     return len;
17098 }
17099
17100 // [HGM] vari: routines for shelving variations
17101 Boolean modeRestore = FALSE;
17102
17103 void
17104 PushInner (int firstMove, int lastMove)
17105 {
17106         int i, j, nrMoves = lastMove - firstMove;
17107
17108         // push current tail of game on stack
17109         savedResult[storedGames] = gameInfo.result;
17110         savedDetails[storedGames] = gameInfo.resultDetails;
17111         gameInfo.resultDetails = NULL;
17112         savedFirst[storedGames] = firstMove;
17113         savedLast [storedGames] = lastMove;
17114         savedFramePtr[storedGames] = framePtr;
17115         framePtr -= nrMoves; // reserve space for the boards
17116         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17117             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17118             for(j=0; j<MOVE_LEN; j++)
17119                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17120             for(j=0; j<2*MOVE_LEN; j++)
17121                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17122             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17123             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17124             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17125             pvInfoList[firstMove+i-1].depth = 0;
17126             commentList[framePtr+i] = commentList[firstMove+i];
17127             commentList[firstMove+i] = NULL;
17128         }
17129
17130         storedGames++;
17131         forwardMostMove = firstMove; // truncate game so we can start variation
17132 }
17133
17134 void
17135 PushTail (int firstMove, int lastMove)
17136 {
17137         if(appData.icsActive) { // only in local mode
17138                 forwardMostMove = currentMove; // mimic old ICS behavior
17139                 return;
17140         }
17141         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17142
17143         PushInner(firstMove, lastMove);
17144         if(storedGames == 1) GreyRevert(FALSE);
17145         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17146 }
17147
17148 void
17149 PopInner (Boolean annotate)
17150 {
17151         int i, j, nrMoves;
17152         char buf[8000], moveBuf[20];
17153
17154         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17155         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17156         nrMoves = savedLast[storedGames] - currentMove;
17157         if(annotate) {
17158                 int cnt = 10;
17159                 if(!WhiteOnMove(currentMove))
17160                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17161                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17162                 for(i=currentMove; i<forwardMostMove; i++) {
17163                         if(WhiteOnMove(i))
17164                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17165                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17166                         strcat(buf, moveBuf);
17167                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17168                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17169                 }
17170                 strcat(buf, ")");
17171         }
17172         for(i=1; i<=nrMoves; i++) { // copy last variation back
17173             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17174             for(j=0; j<MOVE_LEN; j++)
17175                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17176             for(j=0; j<2*MOVE_LEN; j++)
17177                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17178             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17179             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17180             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17181             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17182             commentList[currentMove+i] = commentList[framePtr+i];
17183             commentList[framePtr+i] = NULL;
17184         }
17185         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17186         framePtr = savedFramePtr[storedGames];
17187         gameInfo.result = savedResult[storedGames];
17188         if(gameInfo.resultDetails != NULL) {
17189             free(gameInfo.resultDetails);
17190       }
17191         gameInfo.resultDetails = savedDetails[storedGames];
17192         forwardMostMove = currentMove + nrMoves;
17193 }
17194
17195 Boolean
17196 PopTail (Boolean annotate)
17197 {
17198         if(appData.icsActive) return FALSE; // only in local mode
17199         if(!storedGames) return FALSE; // sanity
17200         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17201
17202         PopInner(annotate);
17203         if(currentMove < forwardMostMove) ForwardEvent(); else
17204         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17205
17206         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17207         return TRUE;
17208 }
17209
17210 void
17211 CleanupTail ()
17212 {       // remove all shelved variations
17213         int i;
17214         for(i=0; i<storedGames; i++) {
17215             if(savedDetails[i])
17216                 free(savedDetails[i]);
17217             savedDetails[i] = NULL;
17218         }
17219         for(i=framePtr; i<MAX_MOVES; i++) {
17220                 if(commentList[i]) free(commentList[i]);
17221                 commentList[i] = NULL;
17222         }
17223         framePtr = MAX_MOVES-1;
17224         storedGames = 0;
17225 }
17226
17227 void
17228 LoadVariation (int index, char *text)
17229 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17230         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17231         int level = 0, move;
17232
17233         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17234         // first find outermost bracketing variation
17235         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17236             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17237                 if(*p == '{') wait = '}'; else
17238                 if(*p == '[') wait = ']'; else
17239                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17240                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17241             }
17242             if(*p == wait) wait = NULLCHAR; // closing ]} found
17243             p++;
17244         }
17245         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17246         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17247         end[1] = NULLCHAR; // clip off comment beyond variation
17248         ToNrEvent(currentMove-1);
17249         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17250         // kludge: use ParsePV() to append variation to game
17251         move = currentMove;
17252         ParsePV(start, TRUE, TRUE);
17253         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17254         ClearPremoveHighlights();
17255         CommentPopDown();
17256         ToNrEvent(currentMove+1);
17257 }
17258