Add options -fe, -se, -is to load installed engines/ics from list
[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 enum ICS_TYPE ics_type = ICS_GENERIC;
425
426 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
427 int pauseExamForwardMostMove = 0;
428 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
429 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
430 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
431 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
432 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
433 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
434 int whiteFlag = FALSE, blackFlag = FALSE;
435 int userOfferedDraw = FALSE;
436 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
437 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
438 int cmailMoveType[CMAIL_MAX_GAMES];
439 long ics_clock_paused = 0;
440 ProcRef icsPR = NoProc, cmailPR = NoProc;
441 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
442 GameMode gameMode = BeginningOfGame;
443 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
444 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
445 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
446 int hiddenThinkOutputState = 0; /* [AS] */
447 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
448 int adjudicateLossPlies = 6;
449 char white_holding[64], black_holding[64];
450 TimeMark lastNodeCountTime;
451 long lastNodeCount=0;
452 int shiftKey, controlKey; // [HGM] set by mouse handler
453
454 int have_sent_ICS_logon = 0;
455 int movesPerSession;
456 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
457 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
458 Boolean adjustedClock;
459 long timeControl_2; /* [AS] Allow separate time controls */
460 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
461 long timeRemaining[2][MAX_MOVES];
462 int matchGame = 0, nextGame = 0, roundNr = 0;
463 Boolean waitingForGame = FALSE;
464 TimeMark programStartTime, pauseStart;
465 char ics_handle[MSG_SIZ];
466 int have_set_title = 0;
467
468 /* animateTraining preserves the state of appData.animate
469  * when Training mode is activated. This allows the
470  * response to be animated when appData.animate == TRUE and
471  * appData.animateDragging == TRUE.
472  */
473 Boolean animateTraining;
474
475 GameInfo gameInfo;
476
477 AppData appData;
478
479 Board boards[MAX_MOVES];
480 /* [HGM] Following 7 needed for accurate legality tests: */
481 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
482 signed char  initialRights[BOARD_FILES];
483 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
484 int   initialRulePlies, FENrulePlies;
485 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
486 int loadFlag = 0;
487 Boolean shuffleOpenings;
488 int mute; // mute all sounds
489
490 // [HGM] vari: next 12 to save and restore variations
491 #define MAX_VARIATIONS 10
492 int framePtr = MAX_MOVES-1; // points to free stack entry
493 int storedGames = 0;
494 int savedFirst[MAX_VARIATIONS];
495 int savedLast[MAX_VARIATIONS];
496 int savedFramePtr[MAX_VARIATIONS];
497 char *savedDetails[MAX_VARIATIONS];
498 ChessMove savedResult[MAX_VARIATIONS];
499
500 void PushTail P((int firstMove, int lastMove));
501 Boolean PopTail P((Boolean annotate));
502 void PushInner P((int firstMove, int lastMove));
503 void PopInner P((Boolean annotate));
504 void CleanupTail P((void));
505
506 ChessSquare  FIDEArray[2][BOARD_FILES] = {
507     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
508         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
509     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
510         BlackKing, BlackBishop, BlackKnight, BlackRook }
511 };
512
513 ChessSquare twoKingsArray[2][BOARD_FILES] = {
514     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
516     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517         BlackKing, BlackKing, BlackKnight, BlackRook }
518 };
519
520 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
521     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
522         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
523     { BlackRook, BlackMan, BlackBishop, BlackQueen,
524         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
525 };
526
527 ChessSquare SpartanArray[2][BOARD_FILES] = {
528     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
529         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
530     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
531         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
532 };
533
534 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
535     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
538         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
539 };
540
541 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
542     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
543         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
544     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
545         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
546 };
547
548 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
550         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
551     { BlackRook, BlackKnight, BlackMan, BlackFerz,
552         BlackKing, BlackMan, BlackKnight, BlackRook }
553 };
554
555
556 #if (BOARD_FILES>=10)
557 ChessSquare ShogiArray[2][BOARD_FILES] = {
558     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
559         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
560     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
561         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
562 };
563
564 ChessSquare XiangqiArray[2][BOARD_FILES] = {
565     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
566         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
567     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
568         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
569 };
570
571 ChessSquare CapablancaArray[2][BOARD_FILES] = {
572     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
573         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
574     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
575         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
576 };
577
578 ChessSquare GreatArray[2][BOARD_FILES] = {
579     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
580         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
581     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
582         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
583 };
584
585 ChessSquare JanusArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
587         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
588     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
589         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
590 };
591
592 ChessSquare GrandArray[2][BOARD_FILES] = {
593     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
594         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
595     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
596         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
597 };
598
599 #ifdef GOTHIC
600 ChessSquare GothicArray[2][BOARD_FILES] = {
601     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
602         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
603     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
604         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
605 };
606 #else // !GOTHIC
607 #define GothicArray CapablancaArray
608 #endif // !GOTHIC
609
610 #ifdef FALCON
611 ChessSquare FalconArray[2][BOARD_FILES] = {
612     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
613         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
614     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
615         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
616 };
617 #else // !FALCON
618 #define FalconArray CapablancaArray
619 #endif // !FALCON
620
621 #else // !(BOARD_FILES>=10)
622 #define XiangqiPosition FIDEArray
623 #define CapablancaArray FIDEArray
624 #define GothicArray FIDEArray
625 #define GreatArray FIDEArray
626 #endif // !(BOARD_FILES>=10)
627
628 #if (BOARD_FILES>=12)
629 ChessSquare CourierArray[2][BOARD_FILES] = {
630     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
631         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
632     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
633         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
634 };
635 #else // !(BOARD_FILES>=12)
636 #define CourierArray CapablancaArray
637 #endif // !(BOARD_FILES>=12)
638
639
640 Board initialPosition;
641
642
643 /* Convert str to a rating. Checks for special cases of "----",
644
645    "++++", etc. Also strips ()'s */
646 int
647 string_to_rating (char *str)
648 {
649   while(*str && !isdigit(*str)) ++str;
650   if (!*str)
651     return 0;   /* One of the special "no rating" cases */
652   else
653     return atoi(str);
654 }
655
656 void
657 ClearProgramStats ()
658 {
659     /* Init programStats */
660     programStats.movelist[0] = 0;
661     programStats.depth = 0;
662     programStats.nr_moves = 0;
663     programStats.moves_left = 0;
664     programStats.nodes = 0;
665     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
666     programStats.score = 0;
667     programStats.got_only_move = 0;
668     programStats.got_fail = 0;
669     programStats.line_is_book = 0;
670 }
671
672 void
673 CommonEngineInit ()
674 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
675     if (appData.firstPlaysBlack) {
676         first.twoMachinesColor = "black\n";
677         second.twoMachinesColor = "white\n";
678     } else {
679         first.twoMachinesColor = "white\n";
680         second.twoMachinesColor = "black\n";
681     }
682
683     first.other = &second;
684     second.other = &first;
685
686     { float norm = 1;
687         if(appData.timeOddsMode) {
688             norm = appData.timeOdds[0];
689             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
690         }
691         first.timeOdds  = appData.timeOdds[0]/norm;
692         second.timeOdds = appData.timeOdds[1]/norm;
693     }
694
695     if(programVersion) free(programVersion);
696     if (appData.noChessProgram) {
697         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
698         sprintf(programVersion, "%s", PACKAGE_STRING);
699     } else {
700       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
701       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
702       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
703     }
704 }
705
706 void
707 UnloadEngine (ChessProgramState *cps)
708 {
709         /* Kill off first chess program */
710         if (cps->isr != NULL)
711           RemoveInputSource(cps->isr);
712         cps->isr = NULL;
713
714         if (cps->pr != NoProc) {
715             ExitAnalyzeMode();
716             DoSleep( appData.delayBeforeQuit );
717             SendToProgram("quit\n", cps);
718             DoSleep( appData.delayAfterQuit );
719             DestroyChildProcess(cps->pr, cps->useSigterm);
720         }
721         cps->pr = NoProc;
722         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
723 }
724
725 void
726 ClearOptions (ChessProgramState *cps)
727 {
728     int i;
729     cps->nrOptions = cps->comboCnt = 0;
730     for(i=0; i<MAX_OPTIONS; i++) {
731         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
732         cps->option[i].textValue = 0;
733     }
734 }
735
736 char *engineNames[] = {
737   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
738      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
739 N_("first"),
740   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
741      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
742 N_("second")
743 };
744
745 void
746 InitEngine (ChessProgramState *cps, int n)
747 {   // [HGM] all engine initialiation put in a function that does one engine
748
749     ClearOptions(cps);
750
751     cps->which = engineNames[n];
752     cps->maybeThinking = FALSE;
753     cps->pr = NoProc;
754     cps->isr = NULL;
755     cps->sendTime = 2;
756     cps->sendDrawOffers = 1;
757
758     cps->program = appData.chessProgram[n];
759     cps->host = appData.host[n];
760     cps->dir = appData.directory[n];
761     cps->initString = appData.engInitString[n];
762     cps->computerString = appData.computerString[n];
763     cps->useSigint  = TRUE;
764     cps->useSigterm = TRUE;
765     cps->reuse = appData.reuse[n];
766     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
767     cps->useSetboard = FALSE;
768     cps->useSAN = FALSE;
769     cps->usePing = FALSE;
770     cps->lastPing = 0;
771     cps->lastPong = 0;
772     cps->usePlayother = FALSE;
773     cps->useColors = TRUE;
774     cps->useUsermove = FALSE;
775     cps->sendICS = FALSE;
776     cps->sendName = appData.icsActive;
777     cps->sdKludge = FALSE;
778     cps->stKludge = FALSE;
779     TidyProgramName(cps->program, cps->host, cps->tidy);
780     cps->matchWins = 0;
781     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
782     cps->analysisSupport = 2; /* detect */
783     cps->analyzing = FALSE;
784     cps->initDone = FALSE;
785
786     /* New features added by Tord: */
787     cps->useFEN960 = FALSE;
788     cps->useOOCastle = TRUE;
789     /* End of new features added by Tord. */
790     cps->fenOverride  = appData.fenOverride[n];
791
792     /* [HGM] time odds: set factor for each machine */
793     cps->timeOdds  = appData.timeOdds[n];
794
795     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
796     cps->accumulateTC = appData.accumulateTC[n];
797     cps->maxNrOfSessions = 1;
798
799     /* [HGM] debug */
800     cps->debug = FALSE;
801
802     cps->supportsNPS = UNKNOWN;
803     cps->memSize = FALSE;
804     cps->maxCores = FALSE;
805     cps->egtFormats[0] = NULLCHAR;
806
807     /* [HGM] options */
808     cps->optionSettings  = appData.engOptions[n];
809
810     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
811     cps->isUCI = appData.isUCI[n]; /* [AS] */
812     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
813
814     if (appData.protocolVersion[n] > PROTOVER
815         || appData.protocolVersion[n] < 1)
816       {
817         char buf[MSG_SIZ];
818         int len;
819
820         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
821                        appData.protocolVersion[n]);
822         if( (len >= MSG_SIZ) && appData.debugMode )
823           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
824
825         DisplayFatalError(buf, 0, 2);
826       }
827     else
828       {
829         cps->protocolVersion = appData.protocolVersion[n];
830       }
831
832     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
833     ParseFeatures(appData.featureDefaults, cps);
834 }
835
836 ChessProgramState *savCps;
837
838 void
839 LoadEngine ()
840 {
841     int i;
842     if(WaitForEngine(savCps, LoadEngine)) return;
843     CommonEngineInit(); // recalculate time odds
844     if(gameInfo.variant != StringToVariant(appData.variant)) {
845         // we changed variant when loading the engine; this forces us to reset
846         Reset(TRUE, savCps != &first);
847         EditGameEvent(); // for consistency with other path, as Reset changes mode
848     }
849     InitChessProgram(savCps, FALSE);
850     SendToProgram("force\n", savCps);
851     DisplayMessage("", "");
852     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
853     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
854     ThawUI();
855     SetGNUMode();
856 }
857
858 void
859 ReplaceEngine (ChessProgramState *cps, int n)
860 {
861     EditGameEvent();
862     UnloadEngine(cps);
863     appData.noChessProgram = FALSE;
864     appData.clockMode = TRUE;
865     InitEngine(cps, n);
866     UpdateLogos(TRUE);
867     if(n) return; // only startup first engine immediately; second can wait
868     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
869     LoadEngine();
870 }
871
872 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
873 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
874
875 static char resetOptions[] = 
876         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
877         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
878         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
879         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
880
881 void
882 FloatToFront(char **list, char *engineLine)
883 {
884     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
885     int i=0;
886     if(appData.recentEngines <= 0) return;
887     TidyProgramName(engineLine, "localhost", tidy+1);
888     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
889     strncpy(buf+1, *list, MSG_SIZ-50);
890     if(p = strstr(buf, tidy)) { // tidy name appears in list
891         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
892         while(*p++ = *++q); // squeeze out
893     }
894     strcat(tidy, buf+1); // put list behind tidy name
895     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
896     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
897     ASSIGN(*list, tidy+1);
898 }
899
900 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
901
902 void
903 Load (ChessProgramState *cps, int i)
904 {
905     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
906     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
907         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
908         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
909         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
910         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
911         appData.firstProtocolVersion = PROTOVER;
912         ParseArgsFromString(buf);
913         SwapEngines(i);
914         ReplaceEngine(cps, i);
915         FloatToFront(&appData.recentEngineList, engineLine);
916         return;
917     }
918     p = engineName;
919     while(q = strchr(p, SLASH)) p = q+1;
920     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
921     if(engineDir[0] != NULLCHAR) {
922         ASSIGN(appData.directory[i], engineDir); p = engineName;
923     } else if(p != engineName) { // derive directory from engine path, when not given
924         p[-1] = 0;
925         ASSIGN(appData.directory[i], engineName);
926         p[-1] = SLASH;
927         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
928     } else { ASSIGN(appData.directory[i], "."); }
929     if(params[0]) {
930         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
931         snprintf(command, MSG_SIZ, "%s %s", p, params);
932         p = command;
933     }
934     ASSIGN(appData.chessProgram[i], p);
935     appData.isUCI[i] = isUCI;
936     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
937     appData.hasOwnBookUCI[i] = hasBook;
938     if(!nickName[0]) useNick = FALSE;
939     if(useNick) ASSIGN(appData.pgnName[i], nickName);
940     if(addToList) {
941         int len;
942         char quote;
943         q = firstChessProgramNames;
944         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
945         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
946         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
947                         quote, p, quote, appData.directory[i], 
948                         useNick ? " -fn \"" : "",
949                         useNick ? nickName : "",
950                         useNick ? "\"" : "",
951                         v1 ? " -firstProtocolVersion 1" : "",
952                         hasBook ? "" : " -fNoOwnBookUCI",
953                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
954                         storeVariant ? " -variant " : "",
955                         storeVariant ? VariantName(gameInfo.variant) : "");
956         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
957         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
958         if(insert != q) insert[-1] = NULLCHAR;
959         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
960         if(q)   free(q);
961         FloatToFront(&appData.recentEngineList, buf);
962     }
963     ReplaceEngine(cps, i);
964 }
965
966 void
967 InitTimeControls ()
968 {
969     int matched, min, sec;
970     /*
971      * Parse timeControl resource
972      */
973     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
974                           appData.movesPerSession)) {
975         char buf[MSG_SIZ];
976         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
977         DisplayFatalError(buf, 0, 2);
978     }
979
980     /*
981      * Parse searchTime resource
982      */
983     if (*appData.searchTime != NULLCHAR) {
984         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
985         if (matched == 1) {
986             searchTime = min * 60;
987         } else if (matched == 2) {
988             searchTime = min * 60 + sec;
989         } else {
990             char buf[MSG_SIZ];
991             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
992             DisplayFatalError(buf, 0, 2);
993         }
994     }
995 }
996
997 void
998 InitBackEnd1 ()
999 {
1000
1001     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1002     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1003
1004     GetTimeMark(&programStartTime);
1005     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1006     appData.seedBase = random() + (random()<<15);
1007     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1008
1009     ClearProgramStats();
1010     programStats.ok_to_send = 1;
1011     programStats.seen_stat = 0;
1012
1013     /*
1014      * Initialize game list
1015      */
1016     ListNew(&gameList);
1017
1018
1019     /*
1020      * Internet chess server status
1021      */
1022     if (appData.icsActive) {
1023         appData.matchMode = FALSE;
1024         appData.matchGames = 0;
1025 #if ZIPPY
1026         appData.noChessProgram = !appData.zippyPlay;
1027 #else
1028         appData.zippyPlay = FALSE;
1029         appData.zippyTalk = FALSE;
1030         appData.noChessProgram = TRUE;
1031 #endif
1032         if (*appData.icsHelper != NULLCHAR) {
1033             appData.useTelnet = TRUE;
1034             appData.telnetProgram = appData.icsHelper;
1035         }
1036     } else {
1037         appData.zippyTalk = appData.zippyPlay = FALSE;
1038     }
1039
1040     /* [AS] Initialize pv info list [HGM] and game state */
1041     {
1042         int i, j;
1043
1044         for( i=0; i<=framePtr; i++ ) {
1045             pvInfoList[i].depth = -1;
1046             boards[i][EP_STATUS] = EP_NONE;
1047             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1048         }
1049     }
1050
1051     InitTimeControls();
1052
1053     /* [AS] Adjudication threshold */
1054     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1055
1056     InitEngine(&first, 0);
1057     InitEngine(&second, 1);
1058     CommonEngineInit();
1059
1060     pairing.which = "pairing"; // pairing engine
1061     pairing.pr = NoProc;
1062     pairing.isr = NULL;
1063     pairing.program = appData.pairingEngine;
1064     pairing.host = "localhost";
1065     pairing.dir = ".";
1066
1067     if (appData.icsActive) {
1068         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1069     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1070         appData.clockMode = FALSE;
1071         first.sendTime = second.sendTime = 0;
1072     }
1073
1074 #if ZIPPY
1075     /* Override some settings from environment variables, for backward
1076        compatibility.  Unfortunately it's not feasible to have the env
1077        vars just set defaults, at least in xboard.  Ugh.
1078     */
1079     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1080       ZippyInit();
1081     }
1082 #endif
1083
1084     if (!appData.icsActive) {
1085       char buf[MSG_SIZ];
1086       int len;
1087
1088       /* Check for variants that are supported only in ICS mode,
1089          or not at all.  Some that are accepted here nevertheless
1090          have bugs; see comments below.
1091       */
1092       VariantClass variant = StringToVariant(appData.variant);
1093       switch (variant) {
1094       case VariantBughouse:     /* need four players and two boards */
1095       case VariantKriegspiel:   /* need to hide pieces and move details */
1096         /* case VariantFischeRandom: (Fabien: moved below) */
1097         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1098         if( (len >= MSG_SIZ) && appData.debugMode )
1099           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1100
1101         DisplayFatalError(buf, 0, 2);
1102         return;
1103
1104       case VariantUnknown:
1105       case VariantLoadable:
1106       case Variant29:
1107       case Variant30:
1108       case Variant31:
1109       case Variant32:
1110       case Variant33:
1111       case Variant34:
1112       case Variant35:
1113       case Variant36:
1114       default:
1115         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1116         if( (len >= MSG_SIZ) && appData.debugMode )
1117           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1118
1119         DisplayFatalError(buf, 0, 2);
1120         return;
1121
1122       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1123       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1124       case VariantGothic:     /* [HGM] should work */
1125       case VariantCapablanca: /* [HGM] should work */
1126       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1127       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1128       case VariantKnightmate: /* [HGM] should work */
1129       case VariantCylinder:   /* [HGM] untested */
1130       case VariantFalcon:     /* [HGM] untested */
1131       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1132                                  offboard interposition not understood */
1133       case VariantNormal:     /* definitely works! */
1134       case VariantWildCastle: /* pieces not automatically shuffled */
1135       case VariantNoCastle:   /* pieces not automatically shuffled */
1136       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1137       case VariantLosers:     /* should work except for win condition,
1138                                  and doesn't know captures are mandatory */
1139       case VariantSuicide:    /* should work except for win condition,
1140                                  and doesn't know captures are mandatory */
1141       case VariantGiveaway:   /* should work except for win condition,
1142                                  and doesn't know captures are mandatory */
1143       case VariantTwoKings:   /* should work */
1144       case VariantAtomic:     /* should work except for win condition */
1145       case Variant3Check:     /* should work except for win condition */
1146       case VariantShatranj:   /* should work except for all win conditions */
1147       case VariantMakruk:     /* should work except for draw countdown */
1148       case VariantBerolina:   /* might work if TestLegality is off */
1149       case VariantCapaRandom: /* should work */
1150       case VariantJanus:      /* should work */
1151       case VariantSuper:      /* experimental */
1152       case VariantGreat:      /* experimental, requires legality testing to be off */
1153       case VariantSChess:     /* S-Chess, should work */
1154       case VariantGrand:      /* should work */
1155       case VariantSpartan:    /* should work */
1156         break;
1157       }
1158     }
1159
1160 }
1161
1162 int
1163 NextIntegerFromString (char ** str, long * value)
1164 {
1165     int result = -1;
1166     char * s = *str;
1167
1168     while( *s == ' ' || *s == '\t' ) {
1169         s++;
1170     }
1171
1172     *value = 0;
1173
1174     if( *s >= '0' && *s <= '9' ) {
1175         while( *s >= '0' && *s <= '9' ) {
1176             *value = *value * 10 + (*s - '0');
1177             s++;
1178         }
1179
1180         result = 0;
1181     }
1182
1183     *str = s;
1184
1185     return result;
1186 }
1187
1188 int
1189 NextTimeControlFromString (char ** str, long * value)
1190 {
1191     long temp;
1192     int result = NextIntegerFromString( str, &temp );
1193
1194     if( result == 0 ) {
1195         *value = temp * 60; /* Minutes */
1196         if( **str == ':' ) {
1197             (*str)++;
1198             result = NextIntegerFromString( str, &temp );
1199             *value += temp; /* Seconds */
1200         }
1201     }
1202
1203     return result;
1204 }
1205
1206 int
1207 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1208 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1209     int result = -1, type = 0; long temp, temp2;
1210
1211     if(**str != ':') return -1; // old params remain in force!
1212     (*str)++;
1213     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1214     if( NextIntegerFromString( str, &temp ) ) return -1;
1215     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1216
1217     if(**str != '/') {
1218         /* time only: incremental or sudden-death time control */
1219         if(**str == '+') { /* increment follows; read it */
1220             (*str)++;
1221             if(**str == '!') type = *(*str)++; // Bronstein TC
1222             if(result = NextIntegerFromString( str, &temp2)) return -1;
1223             *inc = temp2 * 1000;
1224             if(**str == '.') { // read fraction of increment
1225                 char *start = ++(*str);
1226                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1227                 temp2 *= 1000;
1228                 while(start++ < *str) temp2 /= 10;
1229                 *inc += temp2;
1230             }
1231         } else *inc = 0;
1232         *moves = 0; *tc = temp * 1000; *incType = type;
1233         return 0;
1234     }
1235
1236     (*str)++; /* classical time control */
1237     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1238
1239     if(result == 0) {
1240         *moves = temp;
1241         *tc    = temp2 * 1000;
1242         *inc   = 0;
1243         *incType = type;
1244     }
1245     return result;
1246 }
1247
1248 int
1249 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1250 {   /* [HGM] get time to add from the multi-session time-control string */
1251     int incType, moves=1; /* kludge to force reading of first session */
1252     long time, increment;
1253     char *s = tcString;
1254
1255     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1256     do {
1257         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1258         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1259         if(movenr == -1) return time;    /* last move before new session     */
1260         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1261         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1262         if(!moves) return increment;     /* current session is incremental   */
1263         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1264     } while(movenr >= -1);               /* try again for next session       */
1265
1266     return 0; // no new time quota on this move
1267 }
1268
1269 int
1270 ParseTimeControl (char *tc, float ti, int mps)
1271 {
1272   long tc1;
1273   long tc2;
1274   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1275   int min, sec=0;
1276
1277   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1278   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1279       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1280   if(ti > 0) {
1281
1282     if(mps)
1283       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1284     else 
1285       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1286   } else {
1287     if(mps)
1288       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1289     else 
1290       snprintf(buf, MSG_SIZ, ":%s", mytc);
1291   }
1292   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1293   
1294   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1295     return FALSE;
1296   }
1297
1298   if( *tc == '/' ) {
1299     /* Parse second time control */
1300     tc++;
1301
1302     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1303       return FALSE;
1304     }
1305
1306     if( tc2 == 0 ) {
1307       return FALSE;
1308     }
1309
1310     timeControl_2 = tc2 * 1000;
1311   }
1312   else {
1313     timeControl_2 = 0;
1314   }
1315
1316   if( tc1 == 0 ) {
1317     return FALSE;
1318   }
1319
1320   timeControl = tc1 * 1000;
1321
1322   if (ti >= 0) {
1323     timeIncrement = ti * 1000;  /* convert to ms */
1324     movesPerSession = 0;
1325   } else {
1326     timeIncrement = 0;
1327     movesPerSession = mps;
1328   }
1329   return TRUE;
1330 }
1331
1332 void
1333 InitBackEnd2 ()
1334 {
1335     if (appData.debugMode) {
1336         fprintf(debugFP, "%s\n", programVersion);
1337     }
1338     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1339
1340     set_cont_sequence(appData.wrapContSeq);
1341     if (appData.matchGames > 0) {
1342         appData.matchMode = TRUE;
1343     } else if (appData.matchMode) {
1344         appData.matchGames = 1;
1345     }
1346     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1347         appData.matchGames = appData.sameColorGames;
1348     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1349         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1350         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1351     }
1352     Reset(TRUE, FALSE);
1353     if (appData.noChessProgram || first.protocolVersion == 1) {
1354       InitBackEnd3();
1355     } else {
1356       /* kludge: allow timeout for initial "feature" commands */
1357       FreezeUI();
1358       DisplayMessage("", _("Starting chess program"));
1359       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1360     }
1361 }
1362
1363 int
1364 CalculateIndex (int index, int gameNr)
1365 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1366     int res;
1367     if(index > 0) return index; // fixed nmber
1368     if(index == 0) return 1;
1369     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1370     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1371     return res;
1372 }
1373
1374 int
1375 LoadGameOrPosition (int gameNr)
1376 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1377     if (*appData.loadGameFile != NULLCHAR) {
1378         if (!LoadGameFromFile(appData.loadGameFile,
1379                 CalculateIndex(appData.loadGameIndex, gameNr),
1380                               appData.loadGameFile, FALSE)) {
1381             DisplayFatalError(_("Bad game file"), 0, 1);
1382             return 0;
1383         }
1384     } else if (*appData.loadPositionFile != NULLCHAR) {
1385         if (!LoadPositionFromFile(appData.loadPositionFile,
1386                 CalculateIndex(appData.loadPositionIndex, gameNr),
1387                                   appData.loadPositionFile)) {
1388             DisplayFatalError(_("Bad position file"), 0, 1);
1389             return 0;
1390         }
1391     }
1392     return 1;
1393 }
1394
1395 void
1396 ReserveGame (int gameNr, char resChar)
1397 {
1398     FILE *tf = fopen(appData.tourneyFile, "r+");
1399     char *p, *q, c, buf[MSG_SIZ];
1400     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1401     safeStrCpy(buf, lastMsg, MSG_SIZ);
1402     DisplayMessage(_("Pick new game"), "");
1403     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1404     ParseArgsFromFile(tf);
1405     p = q = appData.results;
1406     if(appData.debugMode) {
1407       char *r = appData.participants;
1408       fprintf(debugFP, "results = '%s'\n", p);
1409       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1410       fprintf(debugFP, "\n");
1411     }
1412     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1413     nextGame = q - p;
1414     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1415     safeStrCpy(q, p, strlen(p) + 2);
1416     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1417     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1418     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1419         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1420         q[nextGame] = '*';
1421     }
1422     fseek(tf, -(strlen(p)+4), SEEK_END);
1423     c = fgetc(tf);
1424     if(c != '"') // depending on DOS or Unix line endings we can be one off
1425          fseek(tf, -(strlen(p)+2), SEEK_END);
1426     else fseek(tf, -(strlen(p)+3), SEEK_END);
1427     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1428     DisplayMessage(buf, "");
1429     free(p); appData.results = q;
1430     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1431        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1432       int round = appData.defaultMatchGames * appData.tourneyType;
1433       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1434          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1435         UnloadEngine(&first);  // next game belongs to other pairing;
1436         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1437     }
1438     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1439 }
1440
1441 void
1442 MatchEvent (int mode)
1443 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1444         int dummy;
1445         if(matchMode) { // already in match mode: switch it off
1446             abortMatch = TRUE;
1447             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1448             return;
1449         }
1450 //      if(gameMode != BeginningOfGame) {
1451 //          DisplayError(_("You can only start a match from the initial position."), 0);
1452 //          return;
1453 //      }
1454         abortMatch = FALSE;
1455         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1456         /* Set up machine vs. machine match */
1457         nextGame = 0;
1458         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1459         if(appData.tourneyFile[0]) {
1460             ReserveGame(-1, 0);
1461             if(nextGame > appData.matchGames) {
1462                 char buf[MSG_SIZ];
1463                 if(strchr(appData.results, '*') == NULL) {
1464                     FILE *f;
1465                     appData.tourneyCycles++;
1466                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1467                         fclose(f);
1468                         NextTourneyGame(-1, &dummy);
1469                         ReserveGame(-1, 0);
1470                         if(nextGame <= appData.matchGames) {
1471                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1472                             matchMode = mode;
1473                             ScheduleDelayedEvent(NextMatchGame, 10000);
1474                             return;
1475                         }
1476                     }
1477                 }
1478                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1479                 DisplayError(buf, 0);
1480                 appData.tourneyFile[0] = 0;
1481                 return;
1482             }
1483         } else
1484         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1485             DisplayFatalError(_("Can't have a match with no chess programs"),
1486                               0, 2);
1487             return;
1488         }
1489         matchMode = mode;
1490         matchGame = roundNr = 1;
1491         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1492         NextMatchGame();
1493 }
1494
1495 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1496
1497 void
1498 InitBackEnd3 P((void))
1499 {
1500     GameMode initialMode;
1501     char buf[MSG_SIZ];
1502     int err, len;
1503
1504     InitChessProgram(&first, startedFromSetupPosition);
1505
1506     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1507         free(programVersion);
1508         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1509         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1510         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1511     }
1512
1513     if (appData.icsActive) {
1514 #ifdef WIN32
1515         /* [DM] Make a console window if needed [HGM] merged ifs */
1516         ConsoleCreate();
1517 #endif
1518         err = establish();
1519         if (err != 0)
1520           {
1521             if (*appData.icsCommPort != NULLCHAR)
1522               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1523                              appData.icsCommPort);
1524             else
1525               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1526                         appData.icsHost, appData.icsPort);
1527
1528             if( (len >= MSG_SIZ) && appData.debugMode )
1529               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1530
1531             DisplayFatalError(buf, err, 1);
1532             return;
1533         }
1534         SetICSMode();
1535         telnetISR =
1536           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1537         fromUserISR =
1538           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1539         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1540             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1541     } else if (appData.noChessProgram) {
1542         SetNCPMode();
1543     } else {
1544         SetGNUMode();
1545     }
1546
1547     if (*appData.cmailGameName != NULLCHAR) {
1548         SetCmailMode();
1549         OpenLoopback(&cmailPR);
1550         cmailISR =
1551           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1552     }
1553
1554     ThawUI();
1555     DisplayMessage("", "");
1556     if (StrCaseCmp(appData.initialMode, "") == 0) {
1557       initialMode = BeginningOfGame;
1558       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1559         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1560         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1561         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1562         ModeHighlight();
1563       }
1564     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1565       initialMode = TwoMachinesPlay;
1566     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1567       initialMode = AnalyzeFile;
1568     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1569       initialMode = AnalyzeMode;
1570     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1571       initialMode = MachinePlaysWhite;
1572     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1573       initialMode = MachinePlaysBlack;
1574     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1575       initialMode = EditGame;
1576     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1577       initialMode = EditPosition;
1578     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1579       initialMode = Training;
1580     } else {
1581       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1582       if( (len >= MSG_SIZ) && appData.debugMode )
1583         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1584
1585       DisplayFatalError(buf, 0, 2);
1586       return;
1587     }
1588
1589     if (appData.matchMode) {
1590         if(appData.tourneyFile[0]) { // start tourney from command line
1591             FILE *f;
1592             if(f = fopen(appData.tourneyFile, "r")) {
1593                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1594                 fclose(f);
1595                 appData.clockMode = TRUE;
1596                 SetGNUMode();
1597             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1598         }
1599         MatchEvent(TRUE);
1600     } else if (*appData.cmailGameName != NULLCHAR) {
1601         /* Set up cmail mode */
1602         ReloadCmailMsgEvent(TRUE);
1603     } else {
1604         /* Set up other modes */
1605         if (initialMode == AnalyzeFile) {
1606           if (*appData.loadGameFile == NULLCHAR) {
1607             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1608             return;
1609           }
1610         }
1611         if (*appData.loadGameFile != NULLCHAR) {
1612             (void) LoadGameFromFile(appData.loadGameFile,
1613                                     appData.loadGameIndex,
1614                                     appData.loadGameFile, TRUE);
1615         } else if (*appData.loadPositionFile != NULLCHAR) {
1616             (void) LoadPositionFromFile(appData.loadPositionFile,
1617                                         appData.loadPositionIndex,
1618                                         appData.loadPositionFile);
1619             /* [HGM] try to make self-starting even after FEN load */
1620             /* to allow automatic setup of fairy variants with wtm */
1621             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1622                 gameMode = BeginningOfGame;
1623                 setboardSpoiledMachineBlack = 1;
1624             }
1625             /* [HGM] loadPos: make that every new game uses the setup */
1626             /* from file as long as we do not switch variant          */
1627             if(!blackPlaysFirst) {
1628                 startedFromPositionFile = TRUE;
1629                 CopyBoard(filePosition, boards[0]);
1630             }
1631         }
1632         if (initialMode == AnalyzeMode) {
1633           if (appData.noChessProgram) {
1634             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1635             return;
1636           }
1637           if (appData.icsActive) {
1638             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1639             return;
1640           }
1641           AnalyzeModeEvent();
1642         } else if (initialMode == AnalyzeFile) {
1643           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1644           ShowThinkingEvent();
1645           AnalyzeFileEvent();
1646           AnalysisPeriodicEvent(1);
1647         } else if (initialMode == MachinePlaysWhite) {
1648           if (appData.noChessProgram) {
1649             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1650                               0, 2);
1651             return;
1652           }
1653           if (appData.icsActive) {
1654             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1655                               0, 2);
1656             return;
1657           }
1658           MachineWhiteEvent();
1659         } else if (initialMode == MachinePlaysBlack) {
1660           if (appData.noChessProgram) {
1661             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1662                               0, 2);
1663             return;
1664           }
1665           if (appData.icsActive) {
1666             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1667                               0, 2);
1668             return;
1669           }
1670           MachineBlackEvent();
1671         } else if (initialMode == TwoMachinesPlay) {
1672           if (appData.noChessProgram) {
1673             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1674                               0, 2);
1675             return;
1676           }
1677           if (appData.icsActive) {
1678             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1679                               0, 2);
1680             return;
1681           }
1682           TwoMachinesEvent();
1683         } else if (initialMode == EditGame) {
1684           EditGameEvent();
1685         } else if (initialMode == EditPosition) {
1686           EditPositionEvent();
1687         } else if (initialMode == Training) {
1688           if (*appData.loadGameFile == NULLCHAR) {
1689             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1690             return;
1691           }
1692           TrainingEvent();
1693         }
1694     }
1695 }
1696
1697 void
1698 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1699 {
1700     DisplayBook(current+1);
1701
1702     MoveHistorySet( movelist, first, last, current, pvInfoList );
1703
1704     EvalGraphSet( first, last, current, pvInfoList );
1705
1706     MakeEngineOutputTitle();
1707 }
1708
1709 /*
1710  * Establish will establish a contact to a remote host.port.
1711  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1712  *  used to talk to the host.
1713  * Returns 0 if okay, error code if not.
1714  */
1715 int
1716 establish ()
1717 {
1718     char buf[MSG_SIZ];
1719
1720     if (*appData.icsCommPort != NULLCHAR) {
1721         /* Talk to the host through a serial comm port */
1722         return OpenCommPort(appData.icsCommPort, &icsPR);
1723
1724     } else if (*appData.gateway != NULLCHAR) {
1725         if (*appData.remoteShell == NULLCHAR) {
1726             /* Use the rcmd protocol to run telnet program on a gateway host */
1727             snprintf(buf, sizeof(buf), "%s %s %s",
1728                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1729             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1730
1731         } else {
1732             /* Use the rsh program to run telnet program on a gateway host */
1733             if (*appData.remoteUser == NULLCHAR) {
1734                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1735                         appData.gateway, appData.telnetProgram,
1736                         appData.icsHost, appData.icsPort);
1737             } else {
1738                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1739                         appData.remoteShell, appData.gateway,
1740                         appData.remoteUser, appData.telnetProgram,
1741                         appData.icsHost, appData.icsPort);
1742             }
1743             return StartChildProcess(buf, "", &icsPR);
1744
1745         }
1746     } else if (appData.useTelnet) {
1747         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1748
1749     } else {
1750         /* TCP socket interface differs somewhat between
1751            Unix and NT; handle details in the front end.
1752            */
1753         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1754     }
1755 }
1756
1757 void
1758 EscapeExpand (char *p, char *q)
1759 {       // [HGM] initstring: routine to shape up string arguments
1760         while(*p++ = *q++) if(p[-1] == '\\')
1761             switch(*q++) {
1762                 case 'n': p[-1] = '\n'; break;
1763                 case 'r': p[-1] = '\r'; break;
1764                 case 't': p[-1] = '\t'; break;
1765                 case '\\': p[-1] = '\\'; break;
1766                 case 0: *p = 0; return;
1767                 default: p[-1] = q[-1]; break;
1768             }
1769 }
1770
1771 void
1772 show_bytes (FILE *fp, char *buf, int count)
1773 {
1774     while (count--) {
1775         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1776             fprintf(fp, "\\%03o", *buf & 0xff);
1777         } else {
1778             putc(*buf, fp);
1779         }
1780         buf++;
1781     }
1782     fflush(fp);
1783 }
1784
1785 /* Returns an errno value */
1786 int
1787 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1788 {
1789     char buf[8192], *p, *q, *buflim;
1790     int left, newcount, outcount;
1791
1792     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1793         *appData.gateway != NULLCHAR) {
1794         if (appData.debugMode) {
1795             fprintf(debugFP, ">ICS: ");
1796             show_bytes(debugFP, message, count);
1797             fprintf(debugFP, "\n");
1798         }
1799         return OutputToProcess(pr, message, count, outError);
1800     }
1801
1802     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1803     p = message;
1804     q = buf;
1805     left = count;
1806     newcount = 0;
1807     while (left) {
1808         if (q >= buflim) {
1809             if (appData.debugMode) {
1810                 fprintf(debugFP, ">ICS: ");
1811                 show_bytes(debugFP, buf, newcount);
1812                 fprintf(debugFP, "\n");
1813             }
1814             outcount = OutputToProcess(pr, buf, newcount, outError);
1815             if (outcount < newcount) return -1; /* to be sure */
1816             q = buf;
1817             newcount = 0;
1818         }
1819         if (*p == '\n') {
1820             *q++ = '\r';
1821             newcount++;
1822         } else if (((unsigned char) *p) == TN_IAC) {
1823             *q++ = (char) TN_IAC;
1824             newcount ++;
1825         }
1826         *q++ = *p++;
1827         newcount++;
1828         left--;
1829     }
1830     if (appData.debugMode) {
1831         fprintf(debugFP, ">ICS: ");
1832         show_bytes(debugFP, buf, newcount);
1833         fprintf(debugFP, "\n");
1834     }
1835     outcount = OutputToProcess(pr, buf, newcount, outError);
1836     if (outcount < newcount) return -1; /* to be sure */
1837     return count;
1838 }
1839
1840 void
1841 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1842 {
1843     int outError, outCount;
1844     static int gotEof = 0;
1845
1846     /* Pass data read from player on to ICS */
1847     if (count > 0) {
1848         gotEof = 0;
1849         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1850         if (outCount < count) {
1851             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1852         }
1853     } else if (count < 0) {
1854         RemoveInputSource(isr);
1855         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1856     } else if (gotEof++ > 0) {
1857         RemoveInputSource(isr);
1858         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1859     }
1860 }
1861
1862 void
1863 KeepAlive ()
1864 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1865     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1866     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1867     SendToICS("date\n");
1868     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1869 }
1870
1871 /* added routine for printf style output to ics */
1872 void
1873 ics_printf (char *format, ...)
1874 {
1875     char buffer[MSG_SIZ];
1876     va_list args;
1877
1878     va_start(args, format);
1879     vsnprintf(buffer, sizeof(buffer), format, args);
1880     buffer[sizeof(buffer)-1] = '\0';
1881     SendToICS(buffer);
1882     va_end(args);
1883 }
1884
1885 void
1886 SendToICS (char *s)
1887 {
1888     int count, outCount, outError;
1889
1890     if (icsPR == NoProc) return;
1891
1892     count = strlen(s);
1893     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1894     if (outCount < count) {
1895         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1896     }
1897 }
1898
1899 /* This is used for sending logon scripts to the ICS. Sending
1900    without a delay causes problems when using timestamp on ICC
1901    (at least on my machine). */
1902 void
1903 SendToICSDelayed (char *s, long msdelay)
1904 {
1905     int count, outCount, outError;
1906
1907     if (icsPR == NoProc) return;
1908
1909     count = strlen(s);
1910     if (appData.debugMode) {
1911         fprintf(debugFP, ">ICS: ");
1912         show_bytes(debugFP, s, count);
1913         fprintf(debugFP, "\n");
1914     }
1915     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1916                                       msdelay);
1917     if (outCount < count) {
1918         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1919     }
1920 }
1921
1922
1923 /* Remove all highlighting escape sequences in s
1924    Also deletes any suffix starting with '('
1925    */
1926 char *
1927 StripHighlightAndTitle (char *s)
1928 {
1929     static char retbuf[MSG_SIZ];
1930     char *p = retbuf;
1931
1932     while (*s != NULLCHAR) {
1933         while (*s == '\033') {
1934             while (*s != NULLCHAR && !isalpha(*s)) s++;
1935             if (*s != NULLCHAR) s++;
1936         }
1937         while (*s != NULLCHAR && *s != '\033') {
1938             if (*s == '(' || *s == '[') {
1939                 *p = NULLCHAR;
1940                 return retbuf;
1941             }
1942             *p++ = *s++;
1943         }
1944     }
1945     *p = NULLCHAR;
1946     return retbuf;
1947 }
1948
1949 /* Remove all highlighting escape sequences in s */
1950 char *
1951 StripHighlight (char *s)
1952 {
1953     static char retbuf[MSG_SIZ];
1954     char *p = retbuf;
1955
1956     while (*s != NULLCHAR) {
1957         while (*s == '\033') {
1958             while (*s != NULLCHAR && !isalpha(*s)) s++;
1959             if (*s != NULLCHAR) s++;
1960         }
1961         while (*s != NULLCHAR && *s != '\033') {
1962             *p++ = *s++;
1963         }
1964     }
1965     *p = NULLCHAR;
1966     return retbuf;
1967 }
1968
1969 char *variantNames[] = VARIANT_NAMES;
1970 char *
1971 VariantName (VariantClass v)
1972 {
1973     return variantNames[v];
1974 }
1975
1976
1977 /* Identify a variant from the strings the chess servers use or the
1978    PGN Variant tag names we use. */
1979 VariantClass
1980 StringToVariant (char *e)
1981 {
1982     char *p;
1983     int wnum = -1;
1984     VariantClass v = VariantNormal;
1985     int i, found = FALSE;
1986     char buf[MSG_SIZ];
1987     int len;
1988
1989     if (!e) return v;
1990
1991     /* [HGM] skip over optional board-size prefixes */
1992     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1993         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1994         while( *e++ != '_');
1995     }
1996
1997     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1998         v = VariantNormal;
1999         found = TRUE;
2000     } else
2001     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2002       if (StrCaseStr(e, variantNames[i])) {
2003         v = (VariantClass) i;
2004         found = TRUE;
2005         break;
2006       }
2007     }
2008
2009     if (!found) {
2010       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2011           || StrCaseStr(e, "wild/fr")
2012           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2013         v = VariantFischeRandom;
2014       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2015                  (i = 1, p = StrCaseStr(e, "w"))) {
2016         p += i;
2017         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2018         if (isdigit(*p)) {
2019           wnum = atoi(p);
2020         } else {
2021           wnum = -1;
2022         }
2023         switch (wnum) {
2024         case 0: /* FICS only, actually */
2025         case 1:
2026           /* Castling legal even if K starts on d-file */
2027           v = VariantWildCastle;
2028           break;
2029         case 2:
2030         case 3:
2031         case 4:
2032           /* Castling illegal even if K & R happen to start in
2033              normal positions. */
2034           v = VariantNoCastle;
2035           break;
2036         case 5:
2037         case 7:
2038         case 8:
2039         case 10:
2040         case 11:
2041         case 12:
2042         case 13:
2043         case 14:
2044         case 15:
2045         case 18:
2046         case 19:
2047           /* Castling legal iff K & R start in normal positions */
2048           v = VariantNormal;
2049           break;
2050         case 6:
2051         case 20:
2052         case 21:
2053           /* Special wilds for position setup; unclear what to do here */
2054           v = VariantLoadable;
2055           break;
2056         case 9:
2057           /* Bizarre ICC game */
2058           v = VariantTwoKings;
2059           break;
2060         case 16:
2061           v = VariantKriegspiel;
2062           break;
2063         case 17:
2064           v = VariantLosers;
2065           break;
2066         case 22:
2067           v = VariantFischeRandom;
2068           break;
2069         case 23:
2070           v = VariantCrazyhouse;
2071           break;
2072         case 24:
2073           v = VariantBughouse;
2074           break;
2075         case 25:
2076           v = Variant3Check;
2077           break;
2078         case 26:
2079           /* Not quite the same as FICS suicide! */
2080           v = VariantGiveaway;
2081           break;
2082         case 27:
2083           v = VariantAtomic;
2084           break;
2085         case 28:
2086           v = VariantShatranj;
2087           break;
2088
2089         /* Temporary names for future ICC types.  The name *will* change in
2090            the next xboard/WinBoard release after ICC defines it. */
2091         case 29:
2092           v = Variant29;
2093           break;
2094         case 30:
2095           v = Variant30;
2096           break;
2097         case 31:
2098           v = Variant31;
2099           break;
2100         case 32:
2101           v = Variant32;
2102           break;
2103         case 33:
2104           v = Variant33;
2105           break;
2106         case 34:
2107           v = Variant34;
2108           break;
2109         case 35:
2110           v = Variant35;
2111           break;
2112         case 36:
2113           v = Variant36;
2114           break;
2115         case 37:
2116           v = VariantShogi;
2117           break;
2118         case 38:
2119           v = VariantXiangqi;
2120           break;
2121         case 39:
2122           v = VariantCourier;
2123           break;
2124         case 40:
2125           v = VariantGothic;
2126           break;
2127         case 41:
2128           v = VariantCapablanca;
2129           break;
2130         case 42:
2131           v = VariantKnightmate;
2132           break;
2133         case 43:
2134           v = VariantFairy;
2135           break;
2136         case 44:
2137           v = VariantCylinder;
2138           break;
2139         case 45:
2140           v = VariantFalcon;
2141           break;
2142         case 46:
2143           v = VariantCapaRandom;
2144           break;
2145         case 47:
2146           v = VariantBerolina;
2147           break;
2148         case 48:
2149           v = VariantJanus;
2150           break;
2151         case 49:
2152           v = VariantSuper;
2153           break;
2154         case 50:
2155           v = VariantGreat;
2156           break;
2157         case -1:
2158           /* Found "wild" or "w" in the string but no number;
2159              must assume it's normal chess. */
2160           v = VariantNormal;
2161           break;
2162         default:
2163           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2164           if( (len >= MSG_SIZ) && appData.debugMode )
2165             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2166
2167           DisplayError(buf, 0);
2168           v = VariantUnknown;
2169           break;
2170         }
2171       }
2172     }
2173     if (appData.debugMode) {
2174       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2175               e, wnum, VariantName(v));
2176     }
2177     return v;
2178 }
2179
2180 static int leftover_start = 0, leftover_len = 0;
2181 char star_match[STAR_MATCH_N][MSG_SIZ];
2182
2183 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2184    advance *index beyond it, and set leftover_start to the new value of
2185    *index; else return FALSE.  If pattern contains the character '*', it
2186    matches any sequence of characters not containing '\r', '\n', or the
2187    character following the '*' (if any), and the matched sequence(s) are
2188    copied into star_match.
2189    */
2190 int
2191 looking_at ( char *buf, int *index, char *pattern)
2192 {
2193     char *bufp = &buf[*index], *patternp = pattern;
2194     int star_count = 0;
2195     char *matchp = star_match[0];
2196
2197     for (;;) {
2198         if (*patternp == NULLCHAR) {
2199             *index = leftover_start = bufp - buf;
2200             *matchp = NULLCHAR;
2201             return TRUE;
2202         }
2203         if (*bufp == NULLCHAR) return FALSE;
2204         if (*patternp == '*') {
2205             if (*bufp == *(patternp + 1)) {
2206                 *matchp = NULLCHAR;
2207                 matchp = star_match[++star_count];
2208                 patternp += 2;
2209                 bufp++;
2210                 continue;
2211             } else if (*bufp == '\n' || *bufp == '\r') {
2212                 patternp++;
2213                 if (*patternp == NULLCHAR)
2214                   continue;
2215                 else
2216                   return FALSE;
2217             } else {
2218                 *matchp++ = *bufp++;
2219                 continue;
2220             }
2221         }
2222         if (*patternp != *bufp) return FALSE;
2223         patternp++;
2224         bufp++;
2225     }
2226 }
2227
2228 void
2229 SendToPlayer (char *data, int length)
2230 {
2231     int error, outCount;
2232     outCount = OutputToProcess(NoProc, data, length, &error);
2233     if (outCount < length) {
2234         DisplayFatalError(_("Error writing to display"), error, 1);
2235     }
2236 }
2237
2238 void
2239 PackHolding (char packed[], char *holding)
2240 {
2241     char *p = holding;
2242     char *q = packed;
2243     int runlength = 0;
2244     int curr = 9999;
2245     do {
2246         if (*p == curr) {
2247             runlength++;
2248         } else {
2249             switch (runlength) {
2250               case 0:
2251                 break;
2252               case 1:
2253                 *q++ = curr;
2254                 break;
2255               case 2:
2256                 *q++ = curr;
2257                 *q++ = curr;
2258                 break;
2259               default:
2260                 sprintf(q, "%d", runlength);
2261                 while (*q) q++;
2262                 *q++ = curr;
2263                 break;
2264             }
2265             runlength = 1;
2266             curr = *p;
2267         }
2268     } while (*p++);
2269     *q = NULLCHAR;
2270 }
2271
2272 /* Telnet protocol requests from the front end */
2273 void
2274 TelnetRequest (unsigned char ddww, unsigned char option)
2275 {
2276     unsigned char msg[3];
2277     int outCount, outError;
2278
2279     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2280
2281     if (appData.debugMode) {
2282         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2283         switch (ddww) {
2284           case TN_DO:
2285             ddwwStr = "DO";
2286             break;
2287           case TN_DONT:
2288             ddwwStr = "DONT";
2289             break;
2290           case TN_WILL:
2291             ddwwStr = "WILL";
2292             break;
2293           case TN_WONT:
2294             ddwwStr = "WONT";
2295             break;
2296           default:
2297             ddwwStr = buf1;
2298             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2299             break;
2300         }
2301         switch (option) {
2302           case TN_ECHO:
2303             optionStr = "ECHO";
2304             break;
2305           default:
2306             optionStr = buf2;
2307             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2308             break;
2309         }
2310         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2311     }
2312     msg[0] = TN_IAC;
2313     msg[1] = ddww;
2314     msg[2] = option;
2315     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2316     if (outCount < 3) {
2317         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2318     }
2319 }
2320
2321 void
2322 DoEcho ()
2323 {
2324     if (!appData.icsActive) return;
2325     TelnetRequest(TN_DO, TN_ECHO);
2326 }
2327
2328 void
2329 DontEcho ()
2330 {
2331     if (!appData.icsActive) return;
2332     TelnetRequest(TN_DONT, TN_ECHO);
2333 }
2334
2335 void
2336 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2337 {
2338     /* put the holdings sent to us by the server on the board holdings area */
2339     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2340     char p;
2341     ChessSquare piece;
2342
2343     if(gameInfo.holdingsWidth < 2)  return;
2344     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2345         return; // prevent overwriting by pre-board holdings
2346
2347     if( (int)lowestPiece >= BlackPawn ) {
2348         holdingsColumn = 0;
2349         countsColumn = 1;
2350         holdingsStartRow = BOARD_HEIGHT-1;
2351         direction = -1;
2352     } else {
2353         holdingsColumn = BOARD_WIDTH-1;
2354         countsColumn = BOARD_WIDTH-2;
2355         holdingsStartRow = 0;
2356         direction = 1;
2357     }
2358
2359     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2360         board[i][holdingsColumn] = EmptySquare;
2361         board[i][countsColumn]   = (ChessSquare) 0;
2362     }
2363     while( (p=*holdings++) != NULLCHAR ) {
2364         piece = CharToPiece( ToUpper(p) );
2365         if(piece == EmptySquare) continue;
2366         /*j = (int) piece - (int) WhitePawn;*/
2367         j = PieceToNumber(piece);
2368         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2369         if(j < 0) continue;               /* should not happen */
2370         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2371         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2372         board[holdingsStartRow+j*direction][countsColumn]++;
2373     }
2374 }
2375
2376
2377 void
2378 VariantSwitch (Board board, VariantClass newVariant)
2379 {
2380    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2381    static Board oldBoard;
2382
2383    startedFromPositionFile = FALSE;
2384    if(gameInfo.variant == newVariant) return;
2385
2386    /* [HGM] This routine is called each time an assignment is made to
2387     * gameInfo.variant during a game, to make sure the board sizes
2388     * are set to match the new variant. If that means adding or deleting
2389     * holdings, we shift the playing board accordingly
2390     * This kludge is needed because in ICS observe mode, we get boards
2391     * of an ongoing game without knowing the variant, and learn about the
2392     * latter only later. This can be because of the move list we requested,
2393     * in which case the game history is refilled from the beginning anyway,
2394     * but also when receiving holdings of a crazyhouse game. In the latter
2395     * case we want to add those holdings to the already received position.
2396     */
2397
2398
2399    if (appData.debugMode) {
2400      fprintf(debugFP, "Switch board from %s to %s\n",
2401              VariantName(gameInfo.variant), VariantName(newVariant));
2402      setbuf(debugFP, NULL);
2403    }
2404    shuffleOpenings = 0;       /* [HGM] shuffle */
2405    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2406    switch(newVariant)
2407      {
2408      case VariantShogi:
2409        newWidth = 9;  newHeight = 9;
2410        gameInfo.holdingsSize = 7;
2411      case VariantBughouse:
2412      case VariantCrazyhouse:
2413        newHoldingsWidth = 2; break;
2414      case VariantGreat:
2415        newWidth = 10;
2416      case VariantSuper:
2417        newHoldingsWidth = 2;
2418        gameInfo.holdingsSize = 8;
2419        break;
2420      case VariantGothic:
2421      case VariantCapablanca:
2422      case VariantCapaRandom:
2423        newWidth = 10;
2424      default:
2425        newHoldingsWidth = gameInfo.holdingsSize = 0;
2426      };
2427
2428    if(newWidth  != gameInfo.boardWidth  ||
2429       newHeight != gameInfo.boardHeight ||
2430       newHoldingsWidth != gameInfo.holdingsWidth ) {
2431
2432      /* shift position to new playing area, if needed */
2433      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2434        for(i=0; i<BOARD_HEIGHT; i++)
2435          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2436            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2437              board[i][j];
2438        for(i=0; i<newHeight; i++) {
2439          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2440          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2441        }
2442      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2443        for(i=0; i<BOARD_HEIGHT; i++)
2444          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2445            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2446              board[i][j];
2447      }
2448      board[HOLDINGS_SET] = 0;
2449      gameInfo.boardWidth  = newWidth;
2450      gameInfo.boardHeight = newHeight;
2451      gameInfo.holdingsWidth = newHoldingsWidth;
2452      gameInfo.variant = newVariant;
2453      InitDrawingSizes(-2, 0);
2454    } else gameInfo.variant = newVariant;
2455    CopyBoard(oldBoard, board);   // remember correctly formatted board
2456      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2457    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2458 }
2459
2460 static int loggedOn = FALSE;
2461
2462 /*-- Game start info cache: --*/
2463 int gs_gamenum;
2464 char gs_kind[MSG_SIZ];
2465 static char player1Name[128] = "";
2466 static char player2Name[128] = "";
2467 static char cont_seq[] = "\n\\   ";
2468 static int player1Rating = -1;
2469 static int player2Rating = -1;
2470 /*----------------------------*/
2471
2472 ColorClass curColor = ColorNormal;
2473 int suppressKibitz = 0;
2474
2475 // [HGM] seekgraph
2476 Boolean soughtPending = FALSE;
2477 Boolean seekGraphUp;
2478 #define MAX_SEEK_ADS 200
2479 #define SQUARE 0x80
2480 char *seekAdList[MAX_SEEK_ADS];
2481 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2482 float tcList[MAX_SEEK_ADS];
2483 char colorList[MAX_SEEK_ADS];
2484 int nrOfSeekAds = 0;
2485 int minRating = 1010, maxRating = 2800;
2486 int hMargin = 10, vMargin = 20, h, w;
2487 extern int squareSize, lineGap;
2488
2489 void
2490 PlotSeekAd (int i)
2491 {
2492         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2493         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2494         if(r < minRating+100 && r >=0 ) r = minRating+100;
2495         if(r > maxRating) r = maxRating;
2496         if(tc < 1.f) tc = 1.f;
2497         if(tc > 95.f) tc = 95.f;
2498         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2499         y = ((double)r - minRating)/(maxRating - minRating)
2500             * (h-vMargin-squareSize/8-1) + vMargin;
2501         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2502         if(strstr(seekAdList[i], " u ")) color = 1;
2503         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2504            !strstr(seekAdList[i], "bullet") &&
2505            !strstr(seekAdList[i], "blitz") &&
2506            !strstr(seekAdList[i], "standard") ) color = 2;
2507         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2508         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2509 }
2510
2511 void
2512 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2513 {
2514         char buf[MSG_SIZ], *ext = "";
2515         VariantClass v = StringToVariant(type);
2516         if(strstr(type, "wild")) {
2517             ext = type + 4; // append wild number
2518             if(v == VariantFischeRandom) type = "chess960"; else
2519             if(v == VariantLoadable) type = "setup"; else
2520             type = VariantName(v);
2521         }
2522         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2523         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2524             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2525             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2526             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2527             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2528             seekNrList[nrOfSeekAds] = nr;
2529             zList[nrOfSeekAds] = 0;
2530             seekAdList[nrOfSeekAds++] = StrSave(buf);
2531             if(plot) PlotSeekAd(nrOfSeekAds-1);
2532         }
2533 }
2534
2535 void
2536 EraseSeekDot (int i)
2537 {
2538     int x = xList[i], y = yList[i], d=squareSize/4, k;
2539     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2540     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2541     // now replot every dot that overlapped
2542     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2543         int xx = xList[k], yy = yList[k];
2544         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2545             DrawSeekDot(xx, yy, colorList[k]);
2546     }
2547 }
2548
2549 void
2550 RemoveSeekAd (int nr)
2551 {
2552         int i;
2553         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2554             EraseSeekDot(i);
2555             if(seekAdList[i]) free(seekAdList[i]);
2556             seekAdList[i] = seekAdList[--nrOfSeekAds];
2557             seekNrList[i] = seekNrList[nrOfSeekAds];
2558             ratingList[i] = ratingList[nrOfSeekAds];
2559             colorList[i]  = colorList[nrOfSeekAds];
2560             tcList[i] = tcList[nrOfSeekAds];
2561             xList[i]  = xList[nrOfSeekAds];
2562             yList[i]  = yList[nrOfSeekAds];
2563             zList[i]  = zList[nrOfSeekAds];
2564             seekAdList[nrOfSeekAds] = NULL;
2565             break;
2566         }
2567 }
2568
2569 Boolean
2570 MatchSoughtLine (char *line)
2571 {
2572     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2573     int nr, base, inc, u=0; char dummy;
2574
2575     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2576        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2577        (u=1) &&
2578        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2579         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2580         // match: compact and save the line
2581         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2582         return TRUE;
2583     }
2584     return FALSE;
2585 }
2586
2587 int
2588 DrawSeekGraph ()
2589 {
2590     int i;
2591     if(!seekGraphUp) return FALSE;
2592     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2593     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2594
2595     DrawSeekBackground(0, 0, w, h);
2596     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2597     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2598     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2599         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2600         yy = h-1-yy;
2601         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2602         if(i%500 == 0) {
2603             char buf[MSG_SIZ];
2604             snprintf(buf, MSG_SIZ, "%d", i);
2605             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2606         }
2607     }
2608     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2609     for(i=1; i<100; i+=(i<10?1:5)) {
2610         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2611         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2612         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2613             char buf[MSG_SIZ];
2614             snprintf(buf, MSG_SIZ, "%d", i);
2615             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2616         }
2617     }
2618     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2619     return TRUE;
2620 }
2621
2622 int
2623 SeekGraphClick (ClickType click, int x, int y, int moving)
2624 {
2625     static int lastDown = 0, displayed = 0, lastSecond;
2626     if(y < 0) return FALSE;
2627     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2628         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2629         if(!seekGraphUp) return FALSE;
2630         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2631         DrawPosition(TRUE, NULL);
2632         return TRUE;
2633     }
2634     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2635         if(click == Release || moving) return FALSE;
2636         nrOfSeekAds = 0;
2637         soughtPending = TRUE;
2638         SendToICS(ics_prefix);
2639         SendToICS("sought\n"); // should this be "sought all"?
2640     } else { // issue challenge based on clicked ad
2641         int dist = 10000; int i, closest = 0, second = 0;
2642         for(i=0; i<nrOfSeekAds; i++) {
2643             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2644             if(d < dist) { dist = d; closest = i; }
2645             second += (d - zList[i] < 120); // count in-range ads
2646             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2647         }
2648         if(dist < 120) {
2649             char buf[MSG_SIZ];
2650             second = (second > 1);
2651             if(displayed != closest || second != lastSecond) {
2652                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2653                 lastSecond = second; displayed = closest;
2654             }
2655             if(click == Press) {
2656                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2657                 lastDown = closest;
2658                 return TRUE;
2659             } // on press 'hit', only show info
2660             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2661             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2662             SendToICS(ics_prefix);
2663             SendToICS(buf);
2664             return TRUE; // let incoming board of started game pop down the graph
2665         } else if(click == Release) { // release 'miss' is ignored
2666             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2667             if(moving == 2) { // right up-click
2668                 nrOfSeekAds = 0; // refresh graph
2669                 soughtPending = TRUE;
2670                 SendToICS(ics_prefix);
2671                 SendToICS("sought\n"); // should this be "sought all"?
2672             }
2673             return TRUE;
2674         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2675         // press miss or release hit 'pop down' seek graph
2676         seekGraphUp = FALSE;
2677         DrawPosition(TRUE, NULL);
2678     }
2679     return TRUE;
2680 }
2681
2682 void
2683 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2684 {
2685 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2686 #define STARTED_NONE 0
2687 #define STARTED_MOVES 1
2688 #define STARTED_BOARD 2
2689 #define STARTED_OBSERVE 3
2690 #define STARTED_HOLDINGS 4
2691 #define STARTED_CHATTER 5
2692 #define STARTED_COMMENT 6
2693 #define STARTED_MOVES_NOHIDE 7
2694
2695     static int started = STARTED_NONE;
2696     static char parse[20000];
2697     static int parse_pos = 0;
2698     static char buf[BUF_SIZE + 1];
2699     static int firstTime = TRUE, intfSet = FALSE;
2700     static ColorClass prevColor = ColorNormal;
2701     static int savingComment = FALSE;
2702     static int cmatch = 0; // continuation sequence match
2703     char *bp;
2704     char str[MSG_SIZ];
2705     int i, oldi;
2706     int buf_len;
2707     int next_out;
2708     int tkind;
2709     int backup;    /* [DM] For zippy color lines */
2710     char *p;
2711     char talker[MSG_SIZ]; // [HGM] chat
2712     int channel;
2713
2714     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2715
2716     if (appData.debugMode) {
2717       if (!error) {
2718         fprintf(debugFP, "<ICS: ");
2719         show_bytes(debugFP, data, count);
2720         fprintf(debugFP, "\n");
2721       }
2722     }
2723
2724     if (appData.debugMode) { int f = forwardMostMove;
2725         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2726                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2727                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2728     }
2729     if (count > 0) {
2730         /* If last read ended with a partial line that we couldn't parse,
2731            prepend it to the new read and try again. */
2732         if (leftover_len > 0) {
2733             for (i=0; i<leftover_len; i++)
2734               buf[i] = buf[leftover_start + i];
2735         }
2736
2737     /* copy new characters into the buffer */
2738     bp = buf + leftover_len;
2739     buf_len=leftover_len;
2740     for (i=0; i<count; i++)
2741     {
2742         // ignore these
2743         if (data[i] == '\r')
2744             continue;
2745
2746         // join lines split by ICS?
2747         if (!appData.noJoin)
2748         {
2749             /*
2750                 Joining just consists of finding matches against the
2751                 continuation sequence, and discarding that sequence
2752                 if found instead of copying it.  So, until a match
2753                 fails, there's nothing to do since it might be the
2754                 complete sequence, and thus, something we don't want
2755                 copied.
2756             */
2757             if (data[i] == cont_seq[cmatch])
2758             {
2759                 cmatch++;
2760                 if (cmatch == strlen(cont_seq))
2761                 {
2762                     cmatch = 0; // complete match.  just reset the counter
2763
2764                     /*
2765                         it's possible for the ICS to not include the space
2766                         at the end of the last word, making our [correct]
2767                         join operation fuse two separate words.  the server
2768                         does this when the space occurs at the width setting.
2769                     */
2770                     if (!buf_len || buf[buf_len-1] != ' ')
2771                     {
2772                         *bp++ = ' ';
2773                         buf_len++;
2774                     }
2775                 }
2776                 continue;
2777             }
2778             else if (cmatch)
2779             {
2780                 /*
2781                     match failed, so we have to copy what matched before
2782                     falling through and copying this character.  In reality,
2783                     this will only ever be just the newline character, but
2784                     it doesn't hurt to be precise.
2785                 */
2786                 strncpy(bp, cont_seq, cmatch);
2787                 bp += cmatch;
2788                 buf_len += cmatch;
2789                 cmatch = 0;
2790             }
2791         }
2792
2793         // copy this char
2794         *bp++ = data[i];
2795         buf_len++;
2796     }
2797
2798         buf[buf_len] = NULLCHAR;
2799 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2800         next_out = 0;
2801         leftover_start = 0;
2802
2803         i = 0;
2804         while (i < buf_len) {
2805             /* Deal with part of the TELNET option negotiation
2806                protocol.  We refuse to do anything beyond the
2807                defaults, except that we allow the WILL ECHO option,
2808                which ICS uses to turn off password echoing when we are
2809                directly connected to it.  We reject this option
2810                if localLineEditing mode is on (always on in xboard)
2811                and we are talking to port 23, which might be a real
2812                telnet server that will try to keep WILL ECHO on permanently.
2813              */
2814             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2815                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2816                 unsigned char option;
2817                 oldi = i;
2818                 switch ((unsigned char) buf[++i]) {
2819                   case TN_WILL:
2820                     if (appData.debugMode)
2821                       fprintf(debugFP, "\n<WILL ");
2822                     switch (option = (unsigned char) buf[++i]) {
2823                       case TN_ECHO:
2824                         if (appData.debugMode)
2825                           fprintf(debugFP, "ECHO ");
2826                         /* Reply only if this is a change, according
2827                            to the protocol rules. */
2828                         if (remoteEchoOption) break;
2829                         if (appData.localLineEditing &&
2830                             atoi(appData.icsPort) == TN_PORT) {
2831                             TelnetRequest(TN_DONT, TN_ECHO);
2832                         } else {
2833                             EchoOff();
2834                             TelnetRequest(TN_DO, TN_ECHO);
2835                             remoteEchoOption = TRUE;
2836                         }
2837                         break;
2838                       default:
2839                         if (appData.debugMode)
2840                           fprintf(debugFP, "%d ", option);
2841                         /* Whatever this is, we don't want it. */
2842                         TelnetRequest(TN_DONT, option);
2843                         break;
2844                     }
2845                     break;
2846                   case TN_WONT:
2847                     if (appData.debugMode)
2848                       fprintf(debugFP, "\n<WONT ");
2849                     switch (option = (unsigned char) buf[++i]) {
2850                       case TN_ECHO:
2851                         if (appData.debugMode)
2852                           fprintf(debugFP, "ECHO ");
2853                         /* Reply only if this is a change, according
2854                            to the protocol rules. */
2855                         if (!remoteEchoOption) break;
2856                         EchoOn();
2857                         TelnetRequest(TN_DONT, TN_ECHO);
2858                         remoteEchoOption = FALSE;
2859                         break;
2860                       default:
2861                         if (appData.debugMode)
2862                           fprintf(debugFP, "%d ", (unsigned char) option);
2863                         /* Whatever this is, it must already be turned
2864                            off, because we never agree to turn on
2865                            anything non-default, so according to the
2866                            protocol rules, we don't reply. */
2867                         break;
2868                     }
2869                     break;
2870                   case TN_DO:
2871                     if (appData.debugMode)
2872                       fprintf(debugFP, "\n<DO ");
2873                     switch (option = (unsigned char) buf[++i]) {
2874                       default:
2875                         /* Whatever this is, we refuse to do it. */
2876                         if (appData.debugMode)
2877                           fprintf(debugFP, "%d ", option);
2878                         TelnetRequest(TN_WONT, option);
2879                         break;
2880                     }
2881                     break;
2882                   case TN_DONT:
2883                     if (appData.debugMode)
2884                       fprintf(debugFP, "\n<DONT ");
2885                     switch (option = (unsigned char) buf[++i]) {
2886                       default:
2887                         if (appData.debugMode)
2888                           fprintf(debugFP, "%d ", option);
2889                         /* Whatever this is, we are already not doing
2890                            it, because we never agree to do anything
2891                            non-default, so according to the protocol
2892                            rules, we don't reply. */
2893                         break;
2894                     }
2895                     break;
2896                   case TN_IAC:
2897                     if (appData.debugMode)
2898                       fprintf(debugFP, "\n<IAC ");
2899                     /* Doubled IAC; pass it through */
2900                     i--;
2901                     break;
2902                   default:
2903                     if (appData.debugMode)
2904                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2905                     /* Drop all other telnet commands on the floor */
2906                     break;
2907                 }
2908                 if (oldi > next_out)
2909                   SendToPlayer(&buf[next_out], oldi - next_out);
2910                 if (++i > next_out)
2911                   next_out = i;
2912                 continue;
2913             }
2914
2915             /* OK, this at least will *usually* work */
2916             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2917                 loggedOn = TRUE;
2918             }
2919
2920             if (loggedOn && !intfSet) {
2921                 if (ics_type == ICS_ICC) {
2922                   snprintf(str, MSG_SIZ,
2923                           "/set-quietly interface %s\n/set-quietly style 12\n",
2924                           programVersion);
2925                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2926                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2927                 } else if (ics_type == ICS_CHESSNET) {
2928                   snprintf(str, MSG_SIZ, "/style 12\n");
2929                 } else {
2930                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2931                   strcat(str, programVersion);
2932                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2933                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2934                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2935 #ifdef WIN32
2936                   strcat(str, "$iset nohighlight 1\n");
2937 #endif
2938                   strcat(str, "$iset lock 1\n$style 12\n");
2939                 }
2940                 SendToICS(str);
2941                 NotifyFrontendLogin();
2942                 intfSet = TRUE;
2943             }
2944
2945             if (started == STARTED_COMMENT) {
2946                 /* Accumulate characters in comment */
2947                 parse[parse_pos++] = buf[i];
2948                 if (buf[i] == '\n') {
2949                     parse[parse_pos] = NULLCHAR;
2950                     if(chattingPartner>=0) {
2951                         char mess[MSG_SIZ];
2952                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2953                         OutputChatMessage(chattingPartner, mess);
2954                         chattingPartner = -1;
2955                         next_out = i+1; // [HGM] suppress printing in ICS window
2956                     } else
2957                     if(!suppressKibitz) // [HGM] kibitz
2958                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2959                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2960                         int nrDigit = 0, nrAlph = 0, j;
2961                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2962                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2963                         parse[parse_pos] = NULLCHAR;
2964                         // try to be smart: if it does not look like search info, it should go to
2965                         // ICS interaction window after all, not to engine-output window.
2966                         for(j=0; j<parse_pos; j++) { // count letters and digits
2967                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2968                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2969                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2970                         }
2971                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2972                             int depth=0; float score;
2973                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2974                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2975                                 pvInfoList[forwardMostMove-1].depth = depth;
2976                                 pvInfoList[forwardMostMove-1].score = 100*score;
2977                             }
2978                             OutputKibitz(suppressKibitz, parse);
2979                         } else {
2980                             char tmp[MSG_SIZ];
2981                             if(gameMode == IcsObserving) // restore original ICS messages
2982                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2983                             else
2984                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2985                             SendToPlayer(tmp, strlen(tmp));
2986                         }
2987                         next_out = i+1; // [HGM] suppress printing in ICS window
2988                     }
2989                     started = STARTED_NONE;
2990                 } else {
2991                     /* Don't match patterns against characters in comment */
2992                     i++;
2993                     continue;
2994                 }
2995             }
2996             if (started == STARTED_CHATTER) {
2997                 if (buf[i] != '\n') {
2998                     /* Don't match patterns against characters in chatter */
2999                     i++;
3000                     continue;
3001                 }
3002                 started = STARTED_NONE;
3003                 if(suppressKibitz) next_out = i+1;
3004             }
3005
3006             /* Kludge to deal with rcmd protocol */
3007             if (firstTime && looking_at(buf, &i, "\001*")) {
3008                 DisplayFatalError(&buf[1], 0, 1);
3009                 continue;
3010             } else {
3011                 firstTime = FALSE;
3012             }
3013
3014             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3015                 ics_type = ICS_ICC;
3016                 ics_prefix = "/";
3017                 if (appData.debugMode)
3018                   fprintf(debugFP, "ics_type %d\n", ics_type);
3019                 continue;
3020             }
3021             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3022                 ics_type = ICS_FICS;
3023                 ics_prefix = "$";
3024                 if (appData.debugMode)
3025                   fprintf(debugFP, "ics_type %d\n", ics_type);
3026                 continue;
3027             }
3028             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3029                 ics_type = ICS_CHESSNET;
3030                 ics_prefix = "/";
3031                 if (appData.debugMode)
3032                   fprintf(debugFP, "ics_type %d\n", ics_type);
3033                 continue;
3034             }
3035
3036             if (!loggedOn &&
3037                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3038                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3039                  looking_at(buf, &i, "will be \"*\""))) {
3040               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3041               continue;
3042             }
3043
3044             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3045               char buf[MSG_SIZ];
3046               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3047               DisplayIcsInteractionTitle(buf);
3048               have_set_title = TRUE;
3049             }
3050
3051             /* skip finger notes */
3052             if (started == STARTED_NONE &&
3053                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3054                  (buf[i] == '1' && buf[i+1] == '0')) &&
3055                 buf[i+2] == ':' && buf[i+3] == ' ') {
3056               started = STARTED_CHATTER;
3057               i += 3;
3058               continue;
3059             }
3060
3061             oldi = i;
3062             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3063             if(appData.seekGraph) {
3064                 if(soughtPending && MatchSoughtLine(buf+i)) {
3065                     i = strstr(buf+i, "rated") - buf;
3066                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3067                     next_out = leftover_start = i;
3068                     started = STARTED_CHATTER;
3069                     suppressKibitz = TRUE;
3070                     continue;
3071                 }
3072                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3073                         && looking_at(buf, &i, "* ads displayed")) {
3074                     soughtPending = FALSE;
3075                     seekGraphUp = TRUE;
3076                     DrawSeekGraph();
3077                     continue;
3078                 }
3079                 if(appData.autoRefresh) {
3080                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3081                         int s = (ics_type == ICS_ICC); // ICC format differs
3082                         if(seekGraphUp)
3083                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3084                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3085                         looking_at(buf, &i, "*% "); // eat prompt
3086                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3087                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3088                         next_out = i; // suppress
3089                         continue;
3090                     }
3091                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3092                         char *p = star_match[0];
3093                         while(*p) {
3094                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3095                             while(*p && *p++ != ' '); // next
3096                         }
3097                         looking_at(buf, &i, "*% "); // eat prompt
3098                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3099                         next_out = i;
3100                         continue;
3101                     }
3102                 }
3103             }
3104
3105             /* skip formula vars */
3106             if (started == STARTED_NONE &&
3107                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3108               started = STARTED_CHATTER;
3109               i += 3;
3110               continue;
3111             }
3112
3113             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3114             if (appData.autoKibitz && started == STARTED_NONE &&
3115                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3116                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3117                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3118                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3119                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3120                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3121                         suppressKibitz = TRUE;
3122                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3123                         next_out = i;
3124                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3125                                 && (gameMode == IcsPlayingWhite)) ||
3126                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3127                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3128                             started = STARTED_CHATTER; // own kibitz we simply discard
3129                         else {
3130                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3131                             parse_pos = 0; parse[0] = NULLCHAR;
3132                             savingComment = TRUE;
3133                             suppressKibitz = gameMode != IcsObserving ? 2 :
3134                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3135                         }
3136                         continue;
3137                 } else
3138                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3139                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3140                          && atoi(star_match[0])) {
3141                     // suppress the acknowledgements of our own autoKibitz
3142                     char *p;
3143                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3144                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3145                     SendToPlayer(star_match[0], strlen(star_match[0]));
3146                     if(looking_at(buf, &i, "*% ")) // eat prompt
3147                         suppressKibitz = FALSE;
3148                     next_out = i;
3149                     continue;
3150                 }
3151             } // [HGM] kibitz: end of patch
3152
3153             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3154
3155             // [HGM] chat: intercept tells by users for which we have an open chat window
3156             channel = -1;
3157             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3158                                            looking_at(buf, &i, "* whispers:") ||
3159                                            looking_at(buf, &i, "* kibitzes:") ||
3160                                            looking_at(buf, &i, "* shouts:") ||
3161                                            looking_at(buf, &i, "* c-shouts:") ||
3162                                            looking_at(buf, &i, "--> * ") ||
3163                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3164                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3165                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3166                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3167                 int p;
3168                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3169                 chattingPartner = -1;
3170
3171                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3172                 for(p=0; p<MAX_CHAT; p++) {
3173                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3174                     talker[0] = '['; strcat(talker, "] ");
3175                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3176                     chattingPartner = p; break;
3177                     }
3178                 } else
3179                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3180                 for(p=0; p<MAX_CHAT; p++) {
3181                     if(!strcmp("kibitzes", chatPartner[p])) {
3182                         talker[0] = '['; strcat(talker, "] ");
3183                         chattingPartner = p; break;
3184                     }
3185                 } else
3186                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3187                 for(p=0; p<MAX_CHAT; p++) {
3188                     if(!strcmp("whispers", chatPartner[p])) {
3189                         talker[0] = '['; strcat(talker, "] ");
3190                         chattingPartner = p; break;
3191                     }
3192                 } else
3193                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3194                   if(buf[i-8] == '-' && buf[i-3] == 't')
3195                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3196                     if(!strcmp("c-shouts", chatPartner[p])) {
3197                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3198                         chattingPartner = p; break;
3199                     }
3200                   }
3201                   if(chattingPartner < 0)
3202                   for(p=0; p<MAX_CHAT; p++) {
3203                     if(!strcmp("shouts", chatPartner[p])) {
3204                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3205                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3206                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3207                         chattingPartner = p; break;
3208                     }
3209                   }
3210                 }
3211                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3212                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3213                     talker[0] = 0; Colorize(ColorTell, FALSE);
3214                     chattingPartner = p; break;
3215                 }
3216                 if(chattingPartner<0) i = oldi; else {
3217                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3218                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3219                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3220                     started = STARTED_COMMENT;
3221                     parse_pos = 0; parse[0] = NULLCHAR;
3222                     savingComment = 3 + chattingPartner; // counts as TRUE
3223                     suppressKibitz = TRUE;
3224                     continue;
3225                 }
3226             } // [HGM] chat: end of patch
3227
3228           backup = i;
3229             if (appData.zippyTalk || appData.zippyPlay) {
3230                 /* [DM] Backup address for color zippy lines */
3231 #if ZIPPY
3232                if (loggedOn == TRUE)
3233                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3234                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3235 #endif
3236             } // [DM] 'else { ' deleted
3237                 if (
3238                     /* Regular tells and says */
3239                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3240                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3241                     looking_at(buf, &i, "* says: ") ||
3242                     /* Don't color "message" or "messages" output */
3243                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3244                     looking_at(buf, &i, "*. * at *:*: ") ||
3245                     looking_at(buf, &i, "--* (*:*): ") ||
3246                     /* Message notifications (same color as tells) */
3247                     looking_at(buf, &i, "* has left a message ") ||
3248                     looking_at(buf, &i, "* just sent you a message:\n") ||
3249                     /* Whispers and kibitzes */
3250                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3251                     looking_at(buf, &i, "* kibitzes: ") ||
3252                     /* Channel tells */
3253                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3254
3255                   if (tkind == 1 && strchr(star_match[0], ':')) {
3256                       /* Avoid "tells you:" spoofs in channels */
3257                      tkind = 3;
3258                   }
3259                   if (star_match[0][0] == NULLCHAR ||
3260                       strchr(star_match[0], ' ') ||
3261                       (tkind == 3 && strchr(star_match[1], ' '))) {
3262                     /* Reject bogus matches */
3263                     i = oldi;
3264                   } else {
3265                     if (appData.colorize) {
3266                       if (oldi > next_out) {
3267                         SendToPlayer(&buf[next_out], oldi - next_out);
3268                         next_out = oldi;
3269                       }
3270                       switch (tkind) {
3271                       case 1:
3272                         Colorize(ColorTell, FALSE);
3273                         curColor = ColorTell;
3274                         break;
3275                       case 2:
3276                         Colorize(ColorKibitz, FALSE);
3277                         curColor = ColorKibitz;
3278                         break;
3279                       case 3:
3280                         p = strrchr(star_match[1], '(');
3281                         if (p == NULL) {
3282                           p = star_match[1];
3283                         } else {
3284                           p++;
3285                         }
3286                         if (atoi(p) == 1) {
3287                           Colorize(ColorChannel1, FALSE);
3288                           curColor = ColorChannel1;
3289                         } else {
3290                           Colorize(ColorChannel, FALSE);
3291                           curColor = ColorChannel;
3292                         }
3293                         break;
3294                       case 5:
3295                         curColor = ColorNormal;
3296                         break;
3297                       }
3298                     }
3299                     if (started == STARTED_NONE && appData.autoComment &&
3300                         (gameMode == IcsObserving ||
3301                          gameMode == IcsPlayingWhite ||
3302                          gameMode == IcsPlayingBlack)) {
3303                       parse_pos = i - oldi;
3304                       memcpy(parse, &buf[oldi], parse_pos);
3305                       parse[parse_pos] = NULLCHAR;
3306                       started = STARTED_COMMENT;
3307                       savingComment = TRUE;
3308                     } else {
3309                       started = STARTED_CHATTER;
3310                       savingComment = FALSE;
3311                     }
3312                     loggedOn = TRUE;
3313                     continue;
3314                   }
3315                 }
3316
3317                 if (looking_at(buf, &i, "* s-shouts: ") ||
3318                     looking_at(buf, &i, "* c-shouts: ")) {
3319                     if (appData.colorize) {
3320                         if (oldi > next_out) {
3321                             SendToPlayer(&buf[next_out], oldi - next_out);
3322                             next_out = oldi;
3323                         }
3324                         Colorize(ColorSShout, FALSE);
3325                         curColor = ColorSShout;
3326                     }
3327                     loggedOn = TRUE;
3328                     started = STARTED_CHATTER;
3329                     continue;
3330                 }
3331
3332                 if (looking_at(buf, &i, "--->")) {
3333                     loggedOn = TRUE;
3334                     continue;
3335                 }
3336
3337                 if (looking_at(buf, &i, "* shouts: ") ||
3338                     looking_at(buf, &i, "--> ")) {
3339                     if (appData.colorize) {
3340                         if (oldi > next_out) {
3341                             SendToPlayer(&buf[next_out], oldi - next_out);
3342                             next_out = oldi;
3343                         }
3344                         Colorize(ColorShout, FALSE);
3345                         curColor = ColorShout;
3346                     }
3347                     loggedOn = TRUE;
3348                     started = STARTED_CHATTER;
3349                     continue;
3350                 }
3351
3352                 if (looking_at( buf, &i, "Challenge:")) {
3353                     if (appData.colorize) {
3354                         if (oldi > next_out) {
3355                             SendToPlayer(&buf[next_out], oldi - next_out);
3356                             next_out = oldi;
3357                         }
3358                         Colorize(ColorChallenge, FALSE);
3359                         curColor = ColorChallenge;
3360                     }
3361                     loggedOn = TRUE;
3362                     continue;
3363                 }
3364
3365                 if (looking_at(buf, &i, "* offers you") ||
3366                     looking_at(buf, &i, "* offers to be") ||
3367                     looking_at(buf, &i, "* would like to") ||
3368                     looking_at(buf, &i, "* requests to") ||
3369                     looking_at(buf, &i, "Your opponent offers") ||
3370                     looking_at(buf, &i, "Your opponent requests")) {
3371
3372                     if (appData.colorize) {
3373                         if (oldi > next_out) {
3374                             SendToPlayer(&buf[next_out], oldi - next_out);
3375                             next_out = oldi;
3376                         }
3377                         Colorize(ColorRequest, FALSE);
3378                         curColor = ColorRequest;
3379                     }
3380                     continue;
3381                 }
3382
3383                 if (looking_at(buf, &i, "* (*) seeking")) {
3384                     if (appData.colorize) {
3385                         if (oldi > next_out) {
3386                             SendToPlayer(&buf[next_out], oldi - next_out);
3387                             next_out = oldi;
3388                         }
3389                         Colorize(ColorSeek, FALSE);
3390                         curColor = ColorSeek;
3391                     }
3392                     continue;
3393             }
3394
3395           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3396
3397             if (looking_at(buf, &i, "\\   ")) {
3398                 if (prevColor != ColorNormal) {
3399                     if (oldi > next_out) {
3400                         SendToPlayer(&buf[next_out], oldi - next_out);
3401                         next_out = oldi;
3402                     }
3403                     Colorize(prevColor, TRUE);
3404                     curColor = prevColor;
3405                 }
3406                 if (savingComment) {
3407                     parse_pos = i - oldi;
3408                     memcpy(parse, &buf[oldi], parse_pos);
3409                     parse[parse_pos] = NULLCHAR;
3410                     started = STARTED_COMMENT;
3411                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3412                         chattingPartner = savingComment - 3; // kludge to remember the box
3413                 } else {
3414                     started = STARTED_CHATTER;
3415                 }
3416                 continue;
3417             }
3418
3419             if (looking_at(buf, &i, "Black Strength :") ||
3420                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3421                 looking_at(buf, &i, "<10>") ||
3422                 looking_at(buf, &i, "#@#")) {
3423                 /* Wrong board style */
3424                 loggedOn = TRUE;
3425                 SendToICS(ics_prefix);
3426                 SendToICS("set style 12\n");
3427                 SendToICS(ics_prefix);
3428                 SendToICS("refresh\n");
3429                 continue;
3430             }
3431
3432             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3433                 ICSInitScript();
3434                 have_sent_ICS_logon = 1;
3435                 continue;
3436             }
3437
3438             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3439                 (looking_at(buf, &i, "\n<12> ") ||
3440                  looking_at(buf, &i, "<12> "))) {
3441                 loggedOn = TRUE;
3442                 if (oldi > next_out) {
3443                     SendToPlayer(&buf[next_out], oldi - next_out);
3444                 }
3445                 next_out = i;
3446                 started = STARTED_BOARD;
3447                 parse_pos = 0;
3448                 continue;
3449             }
3450
3451             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3452                 looking_at(buf, &i, "<b1> ")) {
3453                 if (oldi > next_out) {
3454                     SendToPlayer(&buf[next_out], oldi - next_out);
3455                 }
3456                 next_out = i;
3457                 started = STARTED_HOLDINGS;
3458                 parse_pos = 0;
3459                 continue;
3460             }
3461
3462             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3463                 loggedOn = TRUE;
3464                 /* Header for a move list -- first line */
3465
3466                 switch (ics_getting_history) {
3467                   case H_FALSE:
3468                     switch (gameMode) {
3469                       case IcsIdle:
3470                       case BeginningOfGame:
3471                         /* User typed "moves" or "oldmoves" while we
3472                            were idle.  Pretend we asked for these
3473                            moves and soak them up so user can step
3474                            through them and/or save them.
3475                            */
3476                         Reset(FALSE, TRUE);
3477                         gameMode = IcsObserving;
3478                         ModeHighlight();
3479                         ics_gamenum = -1;
3480                         ics_getting_history = H_GOT_UNREQ_HEADER;
3481                         break;
3482                       case EditGame: /*?*/
3483                       case EditPosition: /*?*/
3484                         /* Should above feature work in these modes too? */
3485                         /* For now it doesn't */
3486                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3487                         break;
3488                       default:
3489                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3490                         break;
3491                     }
3492                     break;
3493                   case H_REQUESTED:
3494                     /* Is this the right one? */
3495                     if (gameInfo.white && gameInfo.black &&
3496                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3497                         strcmp(gameInfo.black, star_match[2]) == 0) {
3498                         /* All is well */
3499                         ics_getting_history = H_GOT_REQ_HEADER;
3500                     }
3501                     break;
3502                   case H_GOT_REQ_HEADER:
3503                   case H_GOT_UNREQ_HEADER:
3504                   case H_GOT_UNWANTED_HEADER:
3505                   case H_GETTING_MOVES:
3506                     /* Should not happen */
3507                     DisplayError(_("Error gathering move list: two headers"), 0);
3508                     ics_getting_history = H_FALSE;
3509                     break;
3510                 }
3511
3512                 /* Save player ratings into gameInfo if needed */
3513                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3514                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3515                     (gameInfo.whiteRating == -1 ||
3516                      gameInfo.blackRating == -1)) {
3517
3518                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3519                     gameInfo.blackRating = string_to_rating(star_match[3]);
3520                     if (appData.debugMode)
3521                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3522                               gameInfo.whiteRating, gameInfo.blackRating);
3523                 }
3524                 continue;
3525             }
3526
3527             if (looking_at(buf, &i,
3528               "* * match, initial time: * minute*, increment: * second")) {
3529                 /* Header for a move list -- second line */
3530                 /* Initial board will follow if this is a wild game */
3531                 if (gameInfo.event != NULL) free(gameInfo.event);
3532                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3533                 gameInfo.event = StrSave(str);
3534                 /* [HGM] we switched variant. Translate boards if needed. */
3535                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3536                 continue;
3537             }
3538
3539             if (looking_at(buf, &i, "Move  ")) {
3540                 /* Beginning of a move list */
3541                 switch (ics_getting_history) {
3542                   case H_FALSE:
3543                     /* Normally should not happen */
3544                     /* Maybe user hit reset while we were parsing */
3545                     break;
3546                   case H_REQUESTED:
3547                     /* Happens if we are ignoring a move list that is not
3548                      * the one we just requested.  Common if the user
3549                      * tries to observe two games without turning off
3550                      * getMoveList */
3551                     break;
3552                   case H_GETTING_MOVES:
3553                     /* Should not happen */
3554                     DisplayError(_("Error gathering move list: nested"), 0);
3555                     ics_getting_history = H_FALSE;
3556                     break;
3557                   case H_GOT_REQ_HEADER:
3558                     ics_getting_history = H_GETTING_MOVES;
3559                     started = STARTED_MOVES;
3560                     parse_pos = 0;
3561                     if (oldi > next_out) {
3562                         SendToPlayer(&buf[next_out], oldi - next_out);
3563                     }
3564                     break;
3565                   case H_GOT_UNREQ_HEADER:
3566                     ics_getting_history = H_GETTING_MOVES;
3567                     started = STARTED_MOVES_NOHIDE;
3568                     parse_pos = 0;
3569                     break;
3570                   case H_GOT_UNWANTED_HEADER:
3571                     ics_getting_history = H_FALSE;
3572                     break;
3573                 }
3574                 continue;
3575             }
3576
3577             if (looking_at(buf, &i, "% ") ||
3578                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3579                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3580                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3581                     soughtPending = FALSE;
3582                     seekGraphUp = TRUE;
3583                     DrawSeekGraph();
3584                 }
3585                 if(suppressKibitz) next_out = i;
3586                 savingComment = FALSE;
3587                 suppressKibitz = 0;
3588                 switch (started) {
3589                   case STARTED_MOVES:
3590                   case STARTED_MOVES_NOHIDE:
3591                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3592                     parse[parse_pos + i - oldi] = NULLCHAR;
3593                     ParseGameHistory(parse);
3594 #if ZIPPY
3595                     if (appData.zippyPlay && first.initDone) {
3596                         FeedMovesToProgram(&first, forwardMostMove);
3597                         if (gameMode == IcsPlayingWhite) {
3598                             if (WhiteOnMove(forwardMostMove)) {
3599                                 if (first.sendTime) {
3600                                   if (first.useColors) {
3601                                     SendToProgram("black\n", &first);
3602                                   }
3603                                   SendTimeRemaining(&first, TRUE);
3604                                 }
3605                                 if (first.useColors) {
3606                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3607                                 }
3608                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3609                                 first.maybeThinking = TRUE;
3610                             } else {
3611                                 if (first.usePlayother) {
3612                                   if (first.sendTime) {
3613                                     SendTimeRemaining(&first, TRUE);
3614                                   }
3615                                   SendToProgram("playother\n", &first);
3616                                   firstMove = FALSE;
3617                                 } else {
3618                                   firstMove = TRUE;
3619                                 }
3620                             }
3621                         } else if (gameMode == IcsPlayingBlack) {
3622                             if (!WhiteOnMove(forwardMostMove)) {
3623                                 if (first.sendTime) {
3624                                   if (first.useColors) {
3625                                     SendToProgram("white\n", &first);
3626                                   }
3627                                   SendTimeRemaining(&first, FALSE);
3628                                 }
3629                                 if (first.useColors) {
3630                                   SendToProgram("black\n", &first);
3631                                 }
3632                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3633                                 first.maybeThinking = TRUE;
3634                             } else {
3635                                 if (first.usePlayother) {
3636                                   if (first.sendTime) {
3637                                     SendTimeRemaining(&first, FALSE);
3638                                   }
3639                                   SendToProgram("playother\n", &first);
3640                                   firstMove = FALSE;
3641                                 } else {
3642                                   firstMove = TRUE;
3643                                 }
3644                             }
3645                         }
3646                     }
3647 #endif
3648                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3649                         /* Moves came from oldmoves or moves command
3650                            while we weren't doing anything else.
3651                            */
3652                         currentMove = forwardMostMove;
3653                         ClearHighlights();/*!!could figure this out*/
3654                         flipView = appData.flipView;
3655                         DrawPosition(TRUE, boards[currentMove]);
3656                         DisplayBothClocks();
3657                         snprintf(str, MSG_SIZ, "%s %s %s",
3658                                 gameInfo.white, _("vs."),  gameInfo.black);
3659                         DisplayTitle(str);
3660                         gameMode = IcsIdle;
3661                     } else {
3662                         /* Moves were history of an active game */
3663                         if (gameInfo.resultDetails != NULL) {
3664                             free(gameInfo.resultDetails);
3665                             gameInfo.resultDetails = NULL;
3666                         }
3667                     }
3668                     HistorySet(parseList, backwardMostMove,
3669                                forwardMostMove, currentMove-1);
3670                     DisplayMove(currentMove - 1);
3671                     if (started == STARTED_MOVES) next_out = i;
3672                     started = STARTED_NONE;
3673                     ics_getting_history = H_FALSE;
3674                     break;
3675
3676                   case STARTED_OBSERVE:
3677                     started = STARTED_NONE;
3678                     SendToICS(ics_prefix);
3679                     SendToICS("refresh\n");
3680                     break;
3681
3682                   default:
3683                     break;
3684                 }
3685                 if(bookHit) { // [HGM] book: simulate book reply
3686                     static char bookMove[MSG_SIZ]; // a bit generous?
3687
3688                     programStats.nodes = programStats.depth = programStats.time =
3689                     programStats.score = programStats.got_only_move = 0;
3690                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3691
3692                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3693                     strcat(bookMove, bookHit);
3694                     HandleMachineMove(bookMove, &first);
3695                 }
3696                 continue;
3697             }
3698
3699             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3700                  started == STARTED_HOLDINGS ||
3701                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3702                 /* Accumulate characters in move list or board */
3703                 parse[parse_pos++] = buf[i];
3704             }
3705
3706             /* Start of game messages.  Mostly we detect start of game
3707                when the first board image arrives.  On some versions
3708                of the ICS, though, we need to do a "refresh" after starting
3709                to observe in order to get the current board right away. */
3710             if (looking_at(buf, &i, "Adding game * to observation list")) {
3711                 started = STARTED_OBSERVE;
3712                 continue;
3713             }
3714
3715             /* Handle auto-observe */
3716             if (appData.autoObserve &&
3717                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3718                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3719                 char *player;
3720                 /* Choose the player that was highlighted, if any. */
3721                 if (star_match[0][0] == '\033' ||
3722                     star_match[1][0] != '\033') {
3723                     player = star_match[0];
3724                 } else {
3725                     player = star_match[2];
3726                 }
3727                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3728                         ics_prefix, StripHighlightAndTitle(player));
3729                 SendToICS(str);
3730
3731                 /* Save ratings from notify string */
3732                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3733                 player1Rating = string_to_rating(star_match[1]);
3734                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3735                 player2Rating = string_to_rating(star_match[3]);
3736
3737                 if (appData.debugMode)
3738                   fprintf(debugFP,
3739                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3740                           player1Name, player1Rating,
3741                           player2Name, player2Rating);
3742
3743                 continue;
3744             }
3745
3746             /* Deal with automatic examine mode after a game,
3747                and with IcsObserving -> IcsExamining transition */
3748             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3749                 looking_at(buf, &i, "has made you an examiner of game *")) {
3750
3751                 int gamenum = atoi(star_match[0]);
3752                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3753                     gamenum == ics_gamenum) {
3754                     /* We were already playing or observing this game;
3755                        no need to refetch history */
3756                     gameMode = IcsExamining;
3757                     if (pausing) {
3758                         pauseExamForwardMostMove = forwardMostMove;
3759                     } else if (currentMove < forwardMostMove) {
3760                         ForwardInner(forwardMostMove);
3761                     }
3762                 } else {
3763                     /* I don't think this case really can happen */
3764                     SendToICS(ics_prefix);
3765                     SendToICS("refresh\n");
3766                 }
3767                 continue;
3768             }
3769
3770             /* Error messages */
3771 //          if (ics_user_moved) {
3772             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3773                 if (looking_at(buf, &i, "Illegal move") ||
3774                     looking_at(buf, &i, "Not a legal move") ||
3775                     looking_at(buf, &i, "Your king is in check") ||
3776                     looking_at(buf, &i, "It isn't your turn") ||
3777                     looking_at(buf, &i, "It is not your move")) {
3778                     /* Illegal move */
3779                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3780                         currentMove = forwardMostMove-1;
3781                         DisplayMove(currentMove - 1); /* before DMError */
3782                         DrawPosition(FALSE, boards[currentMove]);
3783                         SwitchClocks(forwardMostMove-1); // [HGM] race
3784                         DisplayBothClocks();
3785                     }
3786                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3787                     ics_user_moved = 0;
3788                     continue;
3789                 }
3790             }
3791
3792             if (looking_at(buf, &i, "still have time") ||
3793                 looking_at(buf, &i, "not out of time") ||
3794                 looking_at(buf, &i, "either player is out of time") ||
3795                 looking_at(buf, &i, "has timeseal; checking")) {
3796                 /* We must have called his flag a little too soon */
3797                 whiteFlag = blackFlag = FALSE;
3798                 continue;
3799             }
3800
3801             if (looking_at(buf, &i, "added * seconds to") ||
3802                 looking_at(buf, &i, "seconds were added to")) {
3803                 /* Update the clocks */
3804                 SendToICS(ics_prefix);
3805                 SendToICS("refresh\n");
3806                 continue;
3807             }
3808
3809             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3810                 ics_clock_paused = TRUE;
3811                 StopClocks();
3812                 continue;
3813             }
3814
3815             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3816                 ics_clock_paused = FALSE;
3817                 StartClocks();
3818                 continue;
3819             }
3820
3821             /* Grab player ratings from the Creating: message.
3822                Note we have to check for the special case when
3823                the ICS inserts things like [white] or [black]. */
3824             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3825                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3826                 /* star_matches:
3827                    0    player 1 name (not necessarily white)
3828                    1    player 1 rating
3829                    2    empty, white, or black (IGNORED)
3830                    3    player 2 name (not necessarily black)
3831                    4    player 2 rating
3832
3833                    The names/ratings are sorted out when the game
3834                    actually starts (below).
3835                 */
3836                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3837                 player1Rating = string_to_rating(star_match[1]);
3838                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3839                 player2Rating = string_to_rating(star_match[4]);
3840
3841                 if (appData.debugMode)
3842                   fprintf(debugFP,
3843                           "Ratings from 'Creating:' %s %d, %s %d\n",
3844                           player1Name, player1Rating,
3845                           player2Name, player2Rating);
3846
3847                 continue;
3848             }
3849
3850             /* Improved generic start/end-of-game messages */
3851             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3852                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3853                 /* If tkind == 0: */
3854                 /* star_match[0] is the game number */
3855                 /*           [1] is the white player's name */
3856                 /*           [2] is the black player's name */
3857                 /* For end-of-game: */
3858                 /*           [3] is the reason for the game end */
3859                 /*           [4] is a PGN end game-token, preceded by " " */
3860                 /* For start-of-game: */
3861                 /*           [3] begins with "Creating" or "Continuing" */
3862                 /*           [4] is " *" or empty (don't care). */
3863                 int gamenum = atoi(star_match[0]);
3864                 char *whitename, *blackname, *why, *endtoken;
3865                 ChessMove endtype = EndOfFile;
3866
3867                 if (tkind == 0) {
3868                   whitename = star_match[1];
3869                   blackname = star_match[2];
3870                   why = star_match[3];
3871                   endtoken = star_match[4];
3872                 } else {
3873                   whitename = star_match[1];
3874                   blackname = star_match[3];
3875                   why = star_match[5];
3876                   endtoken = star_match[6];
3877                 }
3878
3879                 /* Game start messages */
3880                 if (strncmp(why, "Creating ", 9) == 0 ||
3881                     strncmp(why, "Continuing ", 11) == 0) {
3882                     gs_gamenum = gamenum;
3883                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3884                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3885                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3886 #if ZIPPY
3887                     if (appData.zippyPlay) {
3888                         ZippyGameStart(whitename, blackname);
3889                     }
3890 #endif /*ZIPPY*/
3891                     partnerBoardValid = FALSE; // [HGM] bughouse
3892                     continue;
3893                 }
3894
3895                 /* Game end messages */
3896                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3897                     ics_gamenum != gamenum) {
3898                     continue;
3899                 }
3900                 while (endtoken[0] == ' ') endtoken++;
3901                 switch (endtoken[0]) {
3902                   case '*':
3903                   default:
3904                     endtype = GameUnfinished;
3905                     break;
3906                   case '0':
3907                     endtype = BlackWins;
3908                     break;
3909                   case '1':
3910                     if (endtoken[1] == '/')
3911                       endtype = GameIsDrawn;
3912                     else
3913                       endtype = WhiteWins;
3914                     break;
3915                 }
3916                 GameEnds(endtype, why, GE_ICS);
3917 #if ZIPPY
3918                 if (appData.zippyPlay && first.initDone) {
3919                     ZippyGameEnd(endtype, why);
3920                     if (first.pr == NoProc) {
3921                       /* Start the next process early so that we'll
3922                          be ready for the next challenge */
3923                       StartChessProgram(&first);
3924                     }
3925                     /* Send "new" early, in case this command takes
3926                        a long time to finish, so that we'll be ready
3927                        for the next challenge. */
3928                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3929                     Reset(TRUE, TRUE);
3930                 }
3931 #endif /*ZIPPY*/
3932                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3933                 continue;
3934             }
3935
3936             if (looking_at(buf, &i, "Removing game * from observation") ||
3937                 looking_at(buf, &i, "no longer observing game *") ||
3938                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3939                 if (gameMode == IcsObserving &&
3940                     atoi(star_match[0]) == ics_gamenum)
3941                   {
3942                       /* icsEngineAnalyze */
3943                       if (appData.icsEngineAnalyze) {
3944                             ExitAnalyzeMode();
3945                             ModeHighlight();
3946                       }
3947                       StopClocks();
3948                       gameMode = IcsIdle;
3949                       ics_gamenum = -1;
3950                       ics_user_moved = FALSE;
3951                   }
3952                 continue;
3953             }
3954
3955             if (looking_at(buf, &i, "no longer examining game *")) {
3956                 if (gameMode == IcsExamining &&
3957                     atoi(star_match[0]) == ics_gamenum)
3958                   {
3959                       gameMode = IcsIdle;
3960                       ics_gamenum = -1;
3961                       ics_user_moved = FALSE;
3962                   }
3963                 continue;
3964             }
3965
3966             /* Advance leftover_start past any newlines we find,
3967                so only partial lines can get reparsed */
3968             if (looking_at(buf, &i, "\n")) {
3969                 prevColor = curColor;
3970                 if (curColor != ColorNormal) {
3971                     if (oldi > next_out) {
3972                         SendToPlayer(&buf[next_out], oldi - next_out);
3973                         next_out = oldi;
3974                     }
3975                     Colorize(ColorNormal, FALSE);
3976                     curColor = ColorNormal;
3977                 }
3978                 if (started == STARTED_BOARD) {
3979                     started = STARTED_NONE;
3980                     parse[parse_pos] = NULLCHAR;
3981                     ParseBoard12(parse);
3982                     ics_user_moved = 0;
3983
3984                     /* Send premove here */
3985                     if (appData.premove) {
3986                       char str[MSG_SIZ];
3987                       if (currentMove == 0 &&
3988                           gameMode == IcsPlayingWhite &&
3989                           appData.premoveWhite) {
3990                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3991                         if (appData.debugMode)
3992                           fprintf(debugFP, "Sending premove:\n");
3993                         SendToICS(str);
3994                       } else if (currentMove == 1 &&
3995                                  gameMode == IcsPlayingBlack &&
3996                                  appData.premoveBlack) {
3997                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3998                         if (appData.debugMode)
3999                           fprintf(debugFP, "Sending premove:\n");
4000                         SendToICS(str);
4001                       } else if (gotPremove) {
4002                         gotPremove = 0;
4003                         ClearPremoveHighlights();
4004                         if (appData.debugMode)
4005                           fprintf(debugFP, "Sending premove:\n");
4006                           UserMoveEvent(premoveFromX, premoveFromY,
4007                                         premoveToX, premoveToY,
4008                                         premovePromoChar);
4009                       }
4010                     }
4011
4012                     /* Usually suppress following prompt */
4013                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4014                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4015                         if (looking_at(buf, &i, "*% ")) {
4016                             savingComment = FALSE;
4017                             suppressKibitz = 0;
4018                         }
4019                     }
4020                     next_out = i;
4021                 } else if (started == STARTED_HOLDINGS) {
4022                     int gamenum;
4023                     char new_piece[MSG_SIZ];
4024                     started = STARTED_NONE;
4025                     parse[parse_pos] = NULLCHAR;
4026                     if (appData.debugMode)
4027                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4028                                                         parse, currentMove);
4029                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4030                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4031                         if (gameInfo.variant == VariantNormal) {
4032                           /* [HGM] We seem to switch variant during a game!
4033                            * Presumably no holdings were displayed, so we have
4034                            * to move the position two files to the right to
4035                            * create room for them!
4036                            */
4037                           VariantClass newVariant;
4038                           switch(gameInfo.boardWidth) { // base guess on board width
4039                                 case 9:  newVariant = VariantShogi; break;
4040                                 case 10: newVariant = VariantGreat; break;
4041                                 default: newVariant = VariantCrazyhouse; break;
4042                           }
4043                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4044                           /* Get a move list just to see the header, which
4045                              will tell us whether this is really bug or zh */
4046                           if (ics_getting_history == H_FALSE) {
4047                             ics_getting_history = H_REQUESTED;
4048                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4049                             SendToICS(str);
4050                           }
4051                         }
4052                         new_piece[0] = NULLCHAR;
4053                         sscanf(parse, "game %d white [%s black [%s <- %s",
4054                                &gamenum, white_holding, black_holding,
4055                                new_piece);
4056                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4057                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4058                         /* [HGM] copy holdings to board holdings area */
4059                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4060                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4061                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4062 #if ZIPPY
4063                         if (appData.zippyPlay && first.initDone) {
4064                             ZippyHoldings(white_holding, black_holding,
4065                                           new_piece);
4066                         }
4067 #endif /*ZIPPY*/
4068                         if (tinyLayout || smallLayout) {
4069                             char wh[16], bh[16];
4070                             PackHolding(wh, white_holding);
4071                             PackHolding(bh, black_holding);
4072                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4073                                     gameInfo.white, gameInfo.black);
4074                         } else {
4075                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4076                                     gameInfo.white, white_holding, _("vs."),
4077                                     gameInfo.black, black_holding);
4078                         }
4079                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4080                         DrawPosition(FALSE, boards[currentMove]);
4081                         DisplayTitle(str);
4082                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4083                         sscanf(parse, "game %d white [%s black [%s <- %s",
4084                                &gamenum, white_holding, black_holding,
4085                                new_piece);
4086                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4087                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4088                         /* [HGM] copy holdings to partner-board holdings area */
4089                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4090                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4091                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4092                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4093                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4094                       }
4095                     }
4096                     /* Suppress following prompt */
4097                     if (looking_at(buf, &i, "*% ")) {
4098                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4099                         savingComment = FALSE;
4100                         suppressKibitz = 0;
4101                     }
4102                     next_out = i;
4103                 }
4104                 continue;
4105             }
4106
4107             i++;                /* skip unparsed character and loop back */
4108         }
4109
4110         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4111 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4112 //          SendToPlayer(&buf[next_out], i - next_out);
4113             started != STARTED_HOLDINGS && leftover_start > next_out) {
4114             SendToPlayer(&buf[next_out], leftover_start - next_out);
4115             next_out = i;
4116         }
4117
4118         leftover_len = buf_len - leftover_start;
4119         /* if buffer ends with something we couldn't parse,
4120            reparse it after appending the next read */
4121
4122     } else if (count == 0) {
4123         RemoveInputSource(isr);
4124         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4125     } else {
4126         DisplayFatalError(_("Error reading from ICS"), error, 1);
4127     }
4128 }
4129
4130
4131 /* Board style 12 looks like this:
4132
4133    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4134
4135  * The "<12> " is stripped before it gets to this routine.  The two
4136  * trailing 0's (flip state and clock ticking) are later addition, and
4137  * some chess servers may not have them, or may have only the first.
4138  * Additional trailing fields may be added in the future.
4139  */
4140
4141 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4142
4143 #define RELATION_OBSERVING_PLAYED    0
4144 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4145 #define RELATION_PLAYING_MYMOVE      1
4146 #define RELATION_PLAYING_NOTMYMOVE  -1
4147 #define RELATION_EXAMINING           2
4148 #define RELATION_ISOLATED_BOARD     -3
4149 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4150
4151 void
4152 ParseBoard12 (char *string)
4153 {
4154     GameMode newGameMode;
4155     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4156     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4157     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4158     char to_play, board_chars[200];
4159     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4160     char black[32], white[32];
4161     Board board;
4162     int prevMove = currentMove;
4163     int ticking = 2;
4164     ChessMove moveType;
4165     int fromX, fromY, toX, toY;
4166     char promoChar;
4167     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4168     char *bookHit = NULL; // [HGM] book
4169     Boolean weird = FALSE, reqFlag = FALSE;
4170
4171     fromX = fromY = toX = toY = -1;
4172
4173     newGame = FALSE;
4174
4175     if (appData.debugMode)
4176       fprintf(debugFP, _("Parsing board: %s\n"), string);
4177
4178     move_str[0] = NULLCHAR;
4179     elapsed_time[0] = NULLCHAR;
4180     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4181         int  i = 0, j;
4182         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4183             if(string[i] == ' ') { ranks++; files = 0; }
4184             else files++;
4185             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4186             i++;
4187         }
4188         for(j = 0; j <i; j++) board_chars[j] = string[j];
4189         board_chars[i] = '\0';
4190         string += i + 1;
4191     }
4192     n = sscanf(string, PATTERN, &to_play, &double_push,
4193                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4194                &gamenum, white, black, &relation, &basetime, &increment,
4195                &white_stren, &black_stren, &white_time, &black_time,
4196                &moveNum, str, elapsed_time, move_str, &ics_flip,
4197                &ticking);
4198
4199     if (n < 21) {
4200         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4201         DisplayError(str, 0);
4202         return;
4203     }
4204
4205     /* Convert the move number to internal form */
4206     moveNum = (moveNum - 1) * 2;
4207     if (to_play == 'B') moveNum++;
4208     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4209       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4210                         0, 1);
4211       return;
4212     }
4213
4214     switch (relation) {
4215       case RELATION_OBSERVING_PLAYED:
4216       case RELATION_OBSERVING_STATIC:
4217         if (gamenum == -1) {
4218             /* Old ICC buglet */
4219             relation = RELATION_OBSERVING_STATIC;
4220         }
4221         newGameMode = IcsObserving;
4222         break;
4223       case RELATION_PLAYING_MYMOVE:
4224       case RELATION_PLAYING_NOTMYMOVE:
4225         newGameMode =
4226           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4227             IcsPlayingWhite : IcsPlayingBlack;
4228         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4229         break;
4230       case RELATION_EXAMINING:
4231         newGameMode = IcsExamining;
4232         break;
4233       case RELATION_ISOLATED_BOARD:
4234       default:
4235         /* Just display this board.  If user was doing something else,
4236            we will forget about it until the next board comes. */
4237         newGameMode = IcsIdle;
4238         break;
4239       case RELATION_STARTING_POSITION:
4240         newGameMode = gameMode;
4241         break;
4242     }
4243
4244     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4245         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4246          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4247       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4248       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4249       static int lastBgGame = -1;
4250       char *toSqr;
4251       for (k = 0; k < ranks; k++) {
4252         for (j = 0; j < files; j++)
4253           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4254         if(gameInfo.holdingsWidth > 1) {
4255              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4256              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4257         }
4258       }
4259       CopyBoard(partnerBoard, board);
4260       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4261         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4262         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4263       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4264       if(toSqr = strchr(str, '-')) {
4265         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4266         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4267       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4268       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4269       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4270       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4271       if(twoBoards) {
4272           DisplayWhiteClock(white_time*fac, to_play == 'W');
4273           DisplayBlackClock(black_time*fac, to_play != 'W');
4274           activePartner = to_play;
4275           if(gamenum != lastBgGame) {
4276               char buf[MSG_SIZ];
4277               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4278               DisplayTitle(buf);
4279           }
4280           lastBgGame = gamenum;
4281           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4282                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4283       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4284                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4285       DisplayMessage(partnerStatus, "");
4286         partnerBoardValid = TRUE;
4287       return;
4288     }
4289
4290     if(appData.dualBoard && appData.bgObserve) {
4291         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4292             SendToICS(ics_prefix), SendToICS("pobserve\n");
4293         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4294             char buf[MSG_SIZ];
4295             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4296             SendToICS(buf);
4297         }
4298     }
4299
4300     /* Modify behavior for initial board display on move listing
4301        of wild games.
4302        */
4303     switch (ics_getting_history) {
4304       case H_FALSE:
4305       case H_REQUESTED:
4306         break;
4307       case H_GOT_REQ_HEADER:
4308       case H_GOT_UNREQ_HEADER:
4309         /* This is the initial position of the current game */
4310         gamenum = ics_gamenum;
4311         moveNum = 0;            /* old ICS bug workaround */
4312         if (to_play == 'B') {
4313           startedFromSetupPosition = TRUE;
4314           blackPlaysFirst = TRUE;
4315           moveNum = 1;
4316           if (forwardMostMove == 0) forwardMostMove = 1;
4317           if (backwardMostMove == 0) backwardMostMove = 1;
4318           if (currentMove == 0) currentMove = 1;
4319         }
4320         newGameMode = gameMode;
4321         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4322         break;
4323       case H_GOT_UNWANTED_HEADER:
4324         /* This is an initial board that we don't want */
4325         return;
4326       case H_GETTING_MOVES:
4327         /* Should not happen */
4328         DisplayError(_("Error gathering move list: extra board"), 0);
4329         ics_getting_history = H_FALSE;
4330         return;
4331     }
4332
4333    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4334                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4335                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4336      /* [HGM] We seem to have switched variant unexpectedly
4337       * Try to guess new variant from board size
4338       */
4339           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4340           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4341           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4342           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4343           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4344           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4345           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4346           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4347           /* Get a move list just to see the header, which
4348              will tell us whether this is really bug or zh */
4349           if (ics_getting_history == H_FALSE) {
4350             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4351             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4352             SendToICS(str);
4353           }
4354     }
4355
4356     /* Take action if this is the first board of a new game, or of a
4357        different game than is currently being displayed.  */
4358     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4359         relation == RELATION_ISOLATED_BOARD) {
4360
4361         /* Forget the old game and get the history (if any) of the new one */
4362         if (gameMode != BeginningOfGame) {
4363           Reset(TRUE, TRUE);
4364         }
4365         newGame = TRUE;
4366         if (appData.autoRaiseBoard) BoardToTop();
4367         prevMove = -3;
4368         if (gamenum == -1) {
4369             newGameMode = IcsIdle;
4370         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4371                    appData.getMoveList && !reqFlag) {
4372             /* Need to get game history */
4373             ics_getting_history = H_REQUESTED;
4374             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4375             SendToICS(str);
4376         }
4377
4378         /* Initially flip the board to have black on the bottom if playing
4379            black or if the ICS flip flag is set, but let the user change
4380            it with the Flip View button. */
4381         flipView = appData.autoFlipView ?
4382           (newGameMode == IcsPlayingBlack) || ics_flip :
4383           appData.flipView;
4384
4385         /* Done with values from previous mode; copy in new ones */
4386         gameMode = newGameMode;
4387         ModeHighlight();
4388         ics_gamenum = gamenum;
4389         if (gamenum == gs_gamenum) {
4390             int klen = strlen(gs_kind);
4391             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4392             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4393             gameInfo.event = StrSave(str);
4394         } else {
4395             gameInfo.event = StrSave("ICS game");
4396         }
4397         gameInfo.site = StrSave(appData.icsHost);
4398         gameInfo.date = PGNDate();
4399         gameInfo.round = StrSave("-");
4400         gameInfo.white = StrSave(white);
4401         gameInfo.black = StrSave(black);
4402         timeControl = basetime * 60 * 1000;
4403         timeControl_2 = 0;
4404         timeIncrement = increment * 1000;
4405         movesPerSession = 0;
4406         gameInfo.timeControl = TimeControlTagValue();
4407         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4408   if (appData.debugMode) {
4409     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4410     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4411     setbuf(debugFP, NULL);
4412   }
4413
4414         gameInfo.outOfBook = NULL;
4415
4416         /* Do we have the ratings? */
4417         if (strcmp(player1Name, white) == 0 &&
4418             strcmp(player2Name, black) == 0) {
4419             if (appData.debugMode)
4420               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4421                       player1Rating, player2Rating);
4422             gameInfo.whiteRating = player1Rating;
4423             gameInfo.blackRating = player2Rating;
4424         } else if (strcmp(player2Name, white) == 0 &&
4425                    strcmp(player1Name, black) == 0) {
4426             if (appData.debugMode)
4427               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4428                       player2Rating, player1Rating);
4429             gameInfo.whiteRating = player2Rating;
4430             gameInfo.blackRating = player1Rating;
4431         }
4432         player1Name[0] = player2Name[0] = NULLCHAR;
4433
4434         /* Silence shouts if requested */
4435         if (appData.quietPlay &&
4436             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4437             SendToICS(ics_prefix);
4438             SendToICS("set shout 0\n");
4439         }
4440     }
4441
4442     /* Deal with midgame name changes */
4443     if (!newGame) {
4444         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4445             if (gameInfo.white) free(gameInfo.white);
4446             gameInfo.white = StrSave(white);
4447         }
4448         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4449             if (gameInfo.black) free(gameInfo.black);
4450             gameInfo.black = StrSave(black);
4451         }
4452     }
4453
4454     /* Throw away game result if anything actually changes in examine mode */
4455     if (gameMode == IcsExamining && !newGame) {
4456         gameInfo.result = GameUnfinished;
4457         if (gameInfo.resultDetails != NULL) {
4458             free(gameInfo.resultDetails);
4459             gameInfo.resultDetails = NULL;
4460         }
4461     }
4462
4463     /* In pausing && IcsExamining mode, we ignore boards coming
4464        in if they are in a different variation than we are. */
4465     if (pauseExamInvalid) return;
4466     if (pausing && gameMode == IcsExamining) {
4467         if (moveNum <= pauseExamForwardMostMove) {
4468             pauseExamInvalid = TRUE;
4469             forwardMostMove = pauseExamForwardMostMove;
4470             return;
4471         }
4472     }
4473
4474   if (appData.debugMode) {
4475     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4476   }
4477     /* Parse the board */
4478     for (k = 0; k < ranks; k++) {
4479       for (j = 0; j < files; j++)
4480         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4481       if(gameInfo.holdingsWidth > 1) {
4482            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4483            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4484       }
4485     }
4486     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4487       board[5][BOARD_RGHT+1] = WhiteAngel;
4488       board[6][BOARD_RGHT+1] = WhiteMarshall;
4489       board[1][0] = BlackMarshall;
4490       board[2][0] = BlackAngel;
4491       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4492     }
4493     CopyBoard(boards[moveNum], board);
4494     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4495     if (moveNum == 0) {
4496         startedFromSetupPosition =
4497           !CompareBoards(board, initialPosition);
4498         if(startedFromSetupPosition)
4499             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4500     }
4501
4502     /* [HGM] Set castling rights. Take the outermost Rooks,
4503        to make it also work for FRC opening positions. Note that board12
4504        is really defective for later FRC positions, as it has no way to
4505        indicate which Rook can castle if they are on the same side of King.
4506        For the initial position we grant rights to the outermost Rooks,
4507        and remember thos rights, and we then copy them on positions
4508        later in an FRC game. This means WB might not recognize castlings with
4509        Rooks that have moved back to their original position as illegal,
4510        but in ICS mode that is not its job anyway.
4511     */
4512     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4513     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4514
4515         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4516             if(board[0][i] == WhiteRook) j = i;
4517         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4518         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4519             if(board[0][i] == WhiteRook) j = i;
4520         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4521         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4522             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4523         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4524         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4525             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4526         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4527
4528         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4529         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4530         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4531             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4532         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4533             if(board[BOARD_HEIGHT-1][k] == bKing)
4534                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4535         if(gameInfo.variant == VariantTwoKings) {
4536             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4537             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4538             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4539         }
4540     } else { int r;
4541         r = boards[moveNum][CASTLING][0] = initialRights[0];
4542         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4543         r = boards[moveNum][CASTLING][1] = initialRights[1];
4544         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4545         r = boards[moveNum][CASTLING][3] = initialRights[3];
4546         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4547         r = boards[moveNum][CASTLING][4] = initialRights[4];
4548         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4549         /* wildcastle kludge: always assume King has rights */
4550         r = boards[moveNum][CASTLING][2] = initialRights[2];
4551         r = boards[moveNum][CASTLING][5] = initialRights[5];
4552     }
4553     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4554     boards[moveNum][EP_STATUS] = EP_NONE;
4555     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4556     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4557     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4558
4559
4560     if (ics_getting_history == H_GOT_REQ_HEADER ||
4561         ics_getting_history == H_GOT_UNREQ_HEADER) {
4562         /* This was an initial position from a move list, not
4563            the current position */
4564         return;
4565     }
4566
4567     /* Update currentMove and known move number limits */
4568     newMove = newGame || moveNum > forwardMostMove;
4569
4570     if (newGame) {
4571         forwardMostMove = backwardMostMove = currentMove = moveNum;
4572         if (gameMode == IcsExamining && moveNum == 0) {
4573           /* Workaround for ICS limitation: we are not told the wild
4574              type when starting to examine a game.  But if we ask for
4575              the move list, the move list header will tell us */
4576             ics_getting_history = H_REQUESTED;
4577             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4578             SendToICS(str);
4579         }
4580     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4581                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4582 #if ZIPPY
4583         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4584         /* [HGM] applied this also to an engine that is silently watching        */
4585         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4586             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4587             gameInfo.variant == currentlyInitializedVariant) {
4588           takeback = forwardMostMove - moveNum;
4589           for (i = 0; i < takeback; i++) {
4590             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4591             SendToProgram("undo\n", &first);
4592           }
4593         }
4594 #endif
4595
4596         forwardMostMove = moveNum;
4597         if (!pausing || currentMove > forwardMostMove)
4598           currentMove = forwardMostMove;
4599     } else {
4600         /* New part of history that is not contiguous with old part */
4601         if (pausing && gameMode == IcsExamining) {
4602             pauseExamInvalid = TRUE;
4603             forwardMostMove = pauseExamForwardMostMove;
4604             return;
4605         }
4606         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4607 #if ZIPPY
4608             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4609                 // [HGM] when we will receive the move list we now request, it will be
4610                 // fed to the engine from the first move on. So if the engine is not
4611                 // in the initial position now, bring it there.
4612                 InitChessProgram(&first, 0);
4613             }
4614 #endif
4615             ics_getting_history = H_REQUESTED;
4616             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4617             SendToICS(str);
4618         }
4619         forwardMostMove = backwardMostMove = currentMove = moveNum;
4620     }
4621
4622     /* Update the clocks */
4623     if (strchr(elapsed_time, '.')) {
4624       /* Time is in ms */
4625       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4626       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4627     } else {
4628       /* Time is in seconds */
4629       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4630       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4631     }
4632
4633
4634 #if ZIPPY
4635     if (appData.zippyPlay && newGame &&
4636         gameMode != IcsObserving && gameMode != IcsIdle &&
4637         gameMode != IcsExamining)
4638       ZippyFirstBoard(moveNum, basetime, increment);
4639 #endif
4640
4641     /* Put the move on the move list, first converting
4642        to canonical algebraic form. */
4643     if (moveNum > 0) {
4644   if (appData.debugMode) {
4645     if (appData.debugMode) { int f = forwardMostMove;
4646         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4647                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4648                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4649     }
4650     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4651     fprintf(debugFP, "moveNum = %d\n", moveNum);
4652     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4653     setbuf(debugFP, NULL);
4654   }
4655         if (moveNum <= backwardMostMove) {
4656             /* We don't know what the board looked like before
4657                this move.  Punt. */
4658           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4659             strcat(parseList[moveNum - 1], " ");
4660             strcat(parseList[moveNum - 1], elapsed_time);
4661             moveList[moveNum - 1][0] = NULLCHAR;
4662         } else if (strcmp(move_str, "none") == 0) {
4663             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4664             /* Again, we don't know what the board looked like;
4665                this is really the start of the game. */
4666             parseList[moveNum - 1][0] = NULLCHAR;
4667             moveList[moveNum - 1][0] = NULLCHAR;
4668             backwardMostMove = moveNum;
4669             startedFromSetupPosition = TRUE;
4670             fromX = fromY = toX = toY = -1;
4671         } else {
4672           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4673           //                 So we parse the long-algebraic move string in stead of the SAN move
4674           int valid; char buf[MSG_SIZ], *prom;
4675
4676           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4677                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4678           // str looks something like "Q/a1-a2"; kill the slash
4679           if(str[1] == '/')
4680             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4681           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4682           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4683                 strcat(buf, prom); // long move lacks promo specification!
4684           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4685                 if(appData.debugMode)
4686                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4687                 safeStrCpy(move_str, buf, MSG_SIZ);
4688           }
4689           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4690                                 &fromX, &fromY, &toX, &toY, &promoChar)
4691                || ParseOneMove(buf, moveNum - 1, &moveType,
4692                                 &fromX, &fromY, &toX, &toY, &promoChar);
4693           // end of long SAN patch
4694           if (valid) {
4695             (void) CoordsToAlgebraic(boards[moveNum - 1],
4696                                      PosFlags(moveNum - 1),
4697                                      fromY, fromX, toY, toX, promoChar,
4698                                      parseList[moveNum-1]);
4699             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4700               case MT_NONE:
4701               case MT_STALEMATE:
4702               default:
4703                 break;
4704               case MT_CHECK:
4705                 if(gameInfo.variant != VariantShogi)
4706                     strcat(parseList[moveNum - 1], "+");
4707                 break;
4708               case MT_CHECKMATE:
4709               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4710                 strcat(parseList[moveNum - 1], "#");
4711                 break;
4712             }
4713             strcat(parseList[moveNum - 1], " ");
4714             strcat(parseList[moveNum - 1], elapsed_time);
4715             /* currentMoveString is set as a side-effect of ParseOneMove */
4716             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4717             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4718             strcat(moveList[moveNum - 1], "\n");
4719
4720             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4721                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4722               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4723                 ChessSquare old, new = boards[moveNum][k][j];
4724                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4725                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4726                   if(old == new) continue;
4727                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4728                   else if(new == WhiteWazir || new == BlackWazir) {
4729                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4730                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4731                       else boards[moveNum][k][j] = old; // preserve type of Gold
4732                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4733                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4734               }
4735           } else {
4736             /* Move from ICS was illegal!?  Punt. */
4737             if (appData.debugMode) {
4738               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4739               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4740             }
4741             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4742             strcat(parseList[moveNum - 1], " ");
4743             strcat(parseList[moveNum - 1], elapsed_time);
4744             moveList[moveNum - 1][0] = NULLCHAR;
4745             fromX = fromY = toX = toY = -1;
4746           }
4747         }
4748   if (appData.debugMode) {
4749     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4750     setbuf(debugFP, NULL);
4751   }
4752
4753 #if ZIPPY
4754         /* Send move to chess program (BEFORE animating it). */
4755         if (appData.zippyPlay && !newGame && newMove &&
4756            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4757
4758             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4759                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4760                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4761                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4762                             move_str);
4763                     DisplayError(str, 0);
4764                 } else {
4765                     if (first.sendTime) {
4766                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4767                     }
4768                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4769                     if (firstMove && !bookHit) {
4770                         firstMove = FALSE;
4771                         if (first.useColors) {
4772                           SendToProgram(gameMode == IcsPlayingWhite ?
4773                                         "white\ngo\n" :
4774                                         "black\ngo\n", &first);
4775                         } else {
4776                           SendToProgram("go\n", &first);
4777                         }
4778                         first.maybeThinking = TRUE;
4779                     }
4780                 }
4781             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4782               if (moveList[moveNum - 1][0] == NULLCHAR) {
4783                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4784                 DisplayError(str, 0);
4785               } else {
4786                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4787                 SendMoveToProgram(moveNum - 1, &first);
4788               }
4789             }
4790         }
4791 #endif
4792     }
4793
4794     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4795         /* If move comes from a remote source, animate it.  If it
4796            isn't remote, it will have already been animated. */
4797         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4798             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4799         }
4800         if (!pausing && appData.highlightLastMove) {
4801             SetHighlights(fromX, fromY, toX, toY);
4802         }
4803     }
4804
4805     /* Start the clocks */
4806     whiteFlag = blackFlag = FALSE;
4807     appData.clockMode = !(basetime == 0 && increment == 0);
4808     if (ticking == 0) {
4809       ics_clock_paused = TRUE;
4810       StopClocks();
4811     } else if (ticking == 1) {
4812       ics_clock_paused = FALSE;
4813     }
4814     if (gameMode == IcsIdle ||
4815         relation == RELATION_OBSERVING_STATIC ||
4816         relation == RELATION_EXAMINING ||
4817         ics_clock_paused)
4818       DisplayBothClocks();
4819     else
4820       StartClocks();
4821
4822     /* Display opponents and material strengths */
4823     if (gameInfo.variant != VariantBughouse &&
4824         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4825         if (tinyLayout || smallLayout) {
4826             if(gameInfo.variant == VariantNormal)
4827               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4828                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4829                     basetime, increment);
4830             else
4831               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4832                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4833                     basetime, increment, (int) gameInfo.variant);
4834         } else {
4835             if(gameInfo.variant == VariantNormal)
4836               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4837                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4838                     basetime, increment);
4839             else
4840               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4841                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4842                     basetime, increment, VariantName(gameInfo.variant));
4843         }
4844         DisplayTitle(str);
4845   if (appData.debugMode) {
4846     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4847   }
4848     }
4849
4850
4851     /* Display the board */
4852     if (!pausing && !appData.noGUI) {
4853
4854       if (appData.premove)
4855           if (!gotPremove ||
4856              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4857              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4858               ClearPremoveHighlights();
4859
4860       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4861         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4862       DrawPosition(j, boards[currentMove]);
4863
4864       DisplayMove(moveNum - 1);
4865       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4866             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4867               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4868         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4869       }
4870     }
4871
4872     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4873 #if ZIPPY
4874     if(bookHit) { // [HGM] book: simulate book reply
4875         static char bookMove[MSG_SIZ]; // a bit generous?
4876
4877         programStats.nodes = programStats.depth = programStats.time =
4878         programStats.score = programStats.got_only_move = 0;
4879         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4880
4881         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4882         strcat(bookMove, bookHit);
4883         HandleMachineMove(bookMove, &first);
4884     }
4885 #endif
4886 }
4887
4888 void
4889 GetMoveListEvent ()
4890 {
4891     char buf[MSG_SIZ];
4892     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4893         ics_getting_history = H_REQUESTED;
4894         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4895         SendToICS(buf);
4896     }
4897 }
4898
4899 void
4900 AnalysisPeriodicEvent (int force)
4901 {
4902     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4903          && !force) || !appData.periodicUpdates)
4904       return;
4905
4906     /* Send . command to Crafty to collect stats */
4907     SendToProgram(".\n", &first);
4908
4909     /* Don't send another until we get a response (this makes
4910        us stop sending to old Crafty's which don't understand
4911        the "." command (sending illegal cmds resets node count & time,
4912        which looks bad)) */
4913     programStats.ok_to_send = 0;
4914 }
4915
4916 void
4917 ics_update_width (int new_width)
4918 {
4919         ics_printf("set width %d\n", new_width);
4920 }
4921
4922 void
4923 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4924 {
4925     char buf[MSG_SIZ];
4926
4927     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4928         // null move in variant where engine does not understand it (for analysis purposes)
4929         SendBoard(cps, moveNum + 1); // send position after move in stead.
4930         return;
4931     }
4932     if (cps->useUsermove) {
4933       SendToProgram("usermove ", cps);
4934     }
4935     if (cps->useSAN) {
4936       char *space;
4937       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4938         int len = space - parseList[moveNum];
4939         memcpy(buf, parseList[moveNum], len);
4940         buf[len++] = '\n';
4941         buf[len] = NULLCHAR;
4942       } else {
4943         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4944       }
4945       SendToProgram(buf, cps);
4946     } else {
4947       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4948         AlphaRank(moveList[moveNum], 4);
4949         SendToProgram(moveList[moveNum], cps);
4950         AlphaRank(moveList[moveNum], 4); // and back
4951       } else
4952       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4953        * the engine. It would be nice to have a better way to identify castle
4954        * moves here. */
4955       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4956                                                                          && cps->useOOCastle) {
4957         int fromX = moveList[moveNum][0] - AAA;
4958         int fromY = moveList[moveNum][1] - ONE;
4959         int toX = moveList[moveNum][2] - AAA;
4960         int toY = moveList[moveNum][3] - ONE;
4961         if((boards[moveNum][fromY][fromX] == WhiteKing
4962             && boards[moveNum][toY][toX] == WhiteRook)
4963            || (boards[moveNum][fromY][fromX] == BlackKing
4964                && boards[moveNum][toY][toX] == BlackRook)) {
4965           if(toX > fromX) SendToProgram("O-O\n", cps);
4966           else SendToProgram("O-O-O\n", cps);
4967         }
4968         else SendToProgram(moveList[moveNum], cps);
4969       } else
4970       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4971         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4972           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4973           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4974                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4975         } else
4976           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4977                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4978         SendToProgram(buf, cps);
4979       }
4980       else SendToProgram(moveList[moveNum], cps);
4981       /* End of additions by Tord */
4982     }
4983
4984     /* [HGM] setting up the opening has brought engine in force mode! */
4985     /*       Send 'go' if we are in a mode where machine should play. */
4986     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4987         (gameMode == TwoMachinesPlay   ||
4988 #if ZIPPY
4989          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4990 #endif
4991          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4992         SendToProgram("go\n", cps);
4993   if (appData.debugMode) {
4994     fprintf(debugFP, "(extra)\n");
4995   }
4996     }
4997     setboardSpoiledMachineBlack = 0;
4998 }
4999
5000 void
5001 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5002 {
5003     char user_move[MSG_SIZ];
5004     char suffix[4];
5005
5006     if(gameInfo.variant == VariantSChess && promoChar) {
5007         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5008         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5009     } else suffix[0] = NULLCHAR;
5010
5011     switch (moveType) {
5012       default:
5013         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5014                 (int)moveType, fromX, fromY, toX, toY);
5015         DisplayError(user_move + strlen("say "), 0);
5016         break;
5017       case WhiteKingSideCastle:
5018       case BlackKingSideCastle:
5019       case WhiteQueenSideCastleWild:
5020       case BlackQueenSideCastleWild:
5021       /* PUSH Fabien */
5022       case WhiteHSideCastleFR:
5023       case BlackHSideCastleFR:
5024       /* POP Fabien */
5025         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5026         break;
5027       case WhiteQueenSideCastle:
5028       case BlackQueenSideCastle:
5029       case WhiteKingSideCastleWild:
5030       case BlackKingSideCastleWild:
5031       /* PUSH Fabien */
5032       case WhiteASideCastleFR:
5033       case BlackASideCastleFR:
5034       /* POP Fabien */
5035         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5036         break;
5037       case WhiteNonPromotion:
5038       case BlackNonPromotion:
5039         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5040         break;
5041       case WhitePromotion:
5042       case BlackPromotion:
5043         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5044           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5045                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5046                 PieceToChar(WhiteFerz));
5047         else if(gameInfo.variant == VariantGreat)
5048           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5049                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5050                 PieceToChar(WhiteMan));
5051         else
5052           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5053                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5054                 promoChar);
5055         break;
5056       case WhiteDrop:
5057       case BlackDrop:
5058       drop:
5059         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5060                  ToUpper(PieceToChar((ChessSquare) fromX)),
5061                  AAA + toX, ONE + toY);
5062         break;
5063       case IllegalMove:  /* could be a variant we don't quite understand */
5064         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5065       case NormalMove:
5066       case WhiteCapturesEnPassant:
5067       case BlackCapturesEnPassant:
5068         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5069                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5070         break;
5071     }
5072     SendToICS(user_move);
5073     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5074         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5075 }
5076
5077 void
5078 UploadGameEvent ()
5079 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5080     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5081     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5082     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5083       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5084       return;
5085     }
5086     if(gameMode != IcsExamining) { // is this ever not the case?
5087         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5088
5089         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5090           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5091         } else { // on FICS we must first go to general examine mode
5092           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5093         }
5094         if(gameInfo.variant != VariantNormal) {
5095             // try figure out wild number, as xboard names are not always valid on ICS
5096             for(i=1; i<=36; i++) {
5097               snprintf(buf, MSG_SIZ, "wild/%d", i);
5098                 if(StringToVariant(buf) == gameInfo.variant) break;
5099             }
5100             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5101             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5102             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5103         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5104         SendToICS(ics_prefix);
5105         SendToICS(buf);
5106         if(startedFromSetupPosition || backwardMostMove != 0) {
5107           fen = PositionToFEN(backwardMostMove, NULL);
5108           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5109             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5110             SendToICS(buf);
5111           } else { // FICS: everything has to set by separate bsetup commands
5112             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5113             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5114             SendToICS(buf);
5115             if(!WhiteOnMove(backwardMostMove)) {
5116                 SendToICS("bsetup tomove black\n");
5117             }
5118             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5119             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5120             SendToICS(buf);
5121             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5122             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5123             SendToICS(buf);
5124             i = boards[backwardMostMove][EP_STATUS];
5125             if(i >= 0) { // set e.p.
5126               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5127                 SendToICS(buf);
5128             }
5129             bsetup++;
5130           }
5131         }
5132       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5133             SendToICS("bsetup done\n"); // switch to normal examining.
5134     }
5135     for(i = backwardMostMove; i<last; i++) {
5136         char buf[20];
5137         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5138         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5139             int len = strlen(moveList[i]);
5140             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5141             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5142         }
5143         SendToICS(buf);
5144     }
5145     SendToICS(ics_prefix);
5146     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5147 }
5148
5149 void
5150 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5151 {
5152     if (rf == DROP_RANK) {
5153       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5154       sprintf(move, "%c@%c%c\n",
5155                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5156     } else {
5157         if (promoChar == 'x' || promoChar == NULLCHAR) {
5158           sprintf(move, "%c%c%c%c\n",
5159                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5160         } else {
5161             sprintf(move, "%c%c%c%c%c\n",
5162                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5163         }
5164     }
5165 }
5166
5167 void
5168 ProcessICSInitScript (FILE *f)
5169 {
5170     char buf[MSG_SIZ];
5171
5172     while (fgets(buf, MSG_SIZ, f)) {
5173         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5174     }
5175
5176     fclose(f);
5177 }
5178
5179
5180 static int lastX, lastY, selectFlag, dragging;
5181
5182 void
5183 Sweep (int step)
5184 {
5185     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5186     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5187     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5188     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5189     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5190     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5191     do {
5192         promoSweep -= step;
5193         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5194         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5195         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5196         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5197         if(!step) step = -1;
5198     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5199             appData.testLegality && (promoSweep == king ||
5200             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5201     if(toX >= 0) {
5202         int victim = boards[currentMove][toY][toX];
5203         boards[currentMove][toY][toX] = promoSweep;
5204         DrawPosition(FALSE, boards[currentMove]);
5205         boards[currentMove][toY][toX] = victim;
5206     } else
5207     ChangeDragPiece(promoSweep);
5208 }
5209
5210 int
5211 PromoScroll (int x, int y)
5212 {
5213   int step = 0;
5214
5215   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5216   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5217   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5218   if(!step) return FALSE;
5219   lastX = x; lastY = y;
5220   if((promoSweep < BlackPawn) == flipView) step = -step;
5221   if(step > 0) selectFlag = 1;
5222   if(!selectFlag) Sweep(step);
5223   return FALSE;
5224 }
5225
5226 void
5227 NextPiece (int step)
5228 {
5229     ChessSquare piece = boards[currentMove][toY][toX];
5230     do {
5231         pieceSweep -= step;
5232         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5233         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5234         if(!step) step = -1;
5235     } while(PieceToChar(pieceSweep) == '.');
5236     boards[currentMove][toY][toX] = pieceSweep;
5237     DrawPosition(FALSE, boards[currentMove]);
5238     boards[currentMove][toY][toX] = piece;
5239 }
5240 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5241 void
5242 AlphaRank (char *move, int n)
5243 {
5244 //    char *p = move, c; int x, y;
5245
5246     if (appData.debugMode) {
5247         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5248     }
5249
5250     if(move[1]=='*' &&
5251        move[2]>='0' && move[2]<='9' &&
5252        move[3]>='a' && move[3]<='x'    ) {
5253         move[1] = '@';
5254         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5255         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5256     } else
5257     if(move[0]>='0' && move[0]<='9' &&
5258        move[1]>='a' && move[1]<='x' &&
5259        move[2]>='0' && move[2]<='9' &&
5260        move[3]>='a' && move[3]<='x'    ) {
5261         /* input move, Shogi -> normal */
5262         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5263         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5264         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5265         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5266     } else
5267     if(move[1]=='@' &&
5268        move[3]>='0' && move[3]<='9' &&
5269        move[2]>='a' && move[2]<='x'    ) {
5270         move[1] = '*';
5271         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5272         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5273     } else
5274     if(
5275        move[0]>='a' && move[0]<='x' &&
5276        move[3]>='0' && move[3]<='9' &&
5277        move[2]>='a' && move[2]<='x'    ) {
5278          /* output move, normal -> Shogi */
5279         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5280         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5281         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5282         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5283         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5284     }
5285     if (appData.debugMode) {
5286         fprintf(debugFP, "   out = '%s'\n", move);
5287     }
5288 }
5289
5290 char yy_textstr[8000];
5291
5292 /* Parser for moves from gnuchess, ICS, or user typein box */
5293 Boolean
5294 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5295 {
5296     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5297
5298     switch (*moveType) {
5299       case WhitePromotion:
5300       case BlackPromotion:
5301       case WhiteNonPromotion:
5302       case BlackNonPromotion:
5303       case NormalMove:
5304       case WhiteCapturesEnPassant:
5305       case BlackCapturesEnPassant:
5306       case WhiteKingSideCastle:
5307       case WhiteQueenSideCastle:
5308       case BlackKingSideCastle:
5309       case BlackQueenSideCastle:
5310       case WhiteKingSideCastleWild:
5311       case WhiteQueenSideCastleWild:
5312       case BlackKingSideCastleWild:
5313       case BlackQueenSideCastleWild:
5314       /* Code added by Tord: */
5315       case WhiteHSideCastleFR:
5316       case WhiteASideCastleFR:
5317       case BlackHSideCastleFR:
5318       case BlackASideCastleFR:
5319       /* End of code added by Tord */
5320       case IllegalMove:         /* bug or odd chess variant */
5321         *fromX = currentMoveString[0] - AAA;
5322         *fromY = currentMoveString[1] - ONE;
5323         *toX = currentMoveString[2] - AAA;
5324         *toY = currentMoveString[3] - ONE;
5325         *promoChar = currentMoveString[4];
5326         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5327             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5328     if (appData.debugMode) {
5329         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5330     }
5331             *fromX = *fromY = *toX = *toY = 0;
5332             return FALSE;
5333         }
5334         if (appData.testLegality) {
5335           return (*moveType != IllegalMove);
5336         } else {
5337           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5338                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5339         }
5340
5341       case WhiteDrop:
5342       case BlackDrop:
5343         *fromX = *moveType == WhiteDrop ?
5344           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5345           (int) CharToPiece(ToLower(currentMoveString[0]));
5346         *fromY = DROP_RANK;
5347         *toX = currentMoveString[2] - AAA;
5348         *toY = currentMoveString[3] - ONE;
5349         *promoChar = NULLCHAR;
5350         return TRUE;
5351
5352       case AmbiguousMove:
5353       case ImpossibleMove:
5354       case EndOfFile:
5355       case ElapsedTime:
5356       case Comment:
5357       case PGNTag:
5358       case NAG:
5359       case WhiteWins:
5360       case BlackWins:
5361       case GameIsDrawn:
5362       default:
5363     if (appData.debugMode) {
5364         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5365     }
5366         /* bug? */
5367         *fromX = *fromY = *toX = *toY = 0;
5368         *promoChar = NULLCHAR;
5369         return FALSE;
5370     }
5371 }
5372
5373 Boolean pushed = FALSE;
5374 char *lastParseAttempt;
5375
5376 void
5377 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5378 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5379   int fromX, fromY, toX, toY; char promoChar;
5380   ChessMove moveType;
5381   Boolean valid;
5382   int nr = 0;
5383
5384   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5385     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5386     pushed = TRUE;
5387   }
5388   endPV = forwardMostMove;
5389   do {
5390     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5391     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5392     lastParseAttempt = pv;
5393     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5394     if(!valid && nr == 0 &&
5395        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5396         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5397         // Hande case where played move is different from leading PV move
5398         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5399         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5400         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5401         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5402           endPV += 2; // if position different, keep this
5403           moveList[endPV-1][0] = fromX + AAA;
5404           moveList[endPV-1][1] = fromY + ONE;
5405           moveList[endPV-1][2] = toX + AAA;
5406           moveList[endPV-1][3] = toY + ONE;
5407           parseList[endPV-1][0] = NULLCHAR;
5408           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5409         }
5410       }
5411     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5412     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5413     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5414     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5415         valid++; // allow comments in PV
5416         continue;
5417     }
5418     nr++;
5419     if(endPV+1 > framePtr) break; // no space, truncate
5420     if(!valid) break;
5421     endPV++;
5422     CopyBoard(boards[endPV], boards[endPV-1]);
5423     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5424     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5425     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5426     CoordsToAlgebraic(boards[endPV - 1],
5427                              PosFlags(endPV - 1),
5428                              fromY, fromX, toY, toX, promoChar,
5429                              parseList[endPV - 1]);
5430   } while(valid);
5431   if(atEnd == 2) return; // used hidden, for PV conversion
5432   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5433   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5434   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5435                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5436   DrawPosition(TRUE, boards[currentMove]);
5437 }
5438
5439 int
5440 MultiPV (ChessProgramState *cps)
5441 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5442         int i;
5443         for(i=0; i<cps->nrOptions; i++)
5444             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5445                 return i;
5446         return -1;
5447 }
5448
5449 Boolean
5450 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5451 {
5452         int startPV, multi, lineStart, origIndex = index;
5453         char *p, buf2[MSG_SIZ];
5454
5455         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5456         lastX = x; lastY = y;
5457         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5458         lineStart = startPV = index;
5459         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5460         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5461         index = startPV;
5462         do{ while(buf[index] && buf[index] != '\n') index++;
5463         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5464         buf[index] = 0;
5465         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5466                 int n = first.option[multi].value;
5467                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5468                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5469                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5470                 first.option[multi].value = n;
5471                 *start = *end = 0;
5472                 return FALSE;
5473         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5474                 ExcludeClick(origIndex - lineStart);
5475                 return FALSE;
5476         }
5477         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5478         *start = startPV; *end = index-1;
5479         return TRUE;
5480 }
5481
5482 char *
5483 PvToSAN (char *pv)
5484 {
5485         static char buf[10*MSG_SIZ];
5486         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5487         *buf = NULLCHAR;
5488         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5489         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5490         for(i = forwardMostMove; i<endPV; i++){
5491             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5492             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5493             k += strlen(buf+k);
5494         }
5495         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5496         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5497         endPV = savedEnd;
5498         return buf;
5499 }
5500
5501 Boolean
5502 LoadPV (int x, int y)
5503 { // called on right mouse click to load PV
5504   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5505   lastX = x; lastY = y;
5506   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5507   return TRUE;
5508 }
5509
5510 void
5511 UnLoadPV ()
5512 {
5513   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5514   if(endPV < 0) return;
5515   if(appData.autoCopyPV) CopyFENToClipboard();
5516   endPV = -1;
5517   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5518         Boolean saveAnimate = appData.animate;
5519         if(pushed) {
5520             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5521                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5522             } else storedGames--; // abandon shelved tail of original game
5523         }
5524         pushed = FALSE;
5525         forwardMostMove = currentMove;
5526         currentMove = oldFMM;
5527         appData.animate = FALSE;
5528         ToNrEvent(forwardMostMove);
5529         appData.animate = saveAnimate;
5530   }
5531   currentMove = forwardMostMove;
5532   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5533   ClearPremoveHighlights();
5534   DrawPosition(TRUE, boards[currentMove]);
5535 }
5536
5537 void
5538 MovePV (int x, int y, int h)
5539 { // step through PV based on mouse coordinates (called on mouse move)
5540   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5541
5542   // we must somehow check if right button is still down (might be released off board!)
5543   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5544   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5545   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5546   if(!step) return;
5547   lastX = x; lastY = y;
5548
5549   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5550   if(endPV < 0) return;
5551   if(y < margin) step = 1; else
5552   if(y > h - margin) step = -1;
5553   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5554   currentMove += step;
5555   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5556   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5557                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5558   DrawPosition(FALSE, boards[currentMove]);
5559 }
5560
5561
5562 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5563 // All positions will have equal probability, but the current method will not provide a unique
5564 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5565 #define DARK 1
5566 #define LITE 2
5567 #define ANY 3
5568
5569 int squaresLeft[4];
5570 int piecesLeft[(int)BlackPawn];
5571 int seed, nrOfShuffles;
5572
5573 void
5574 GetPositionNumber ()
5575 {       // sets global variable seed
5576         int i;
5577
5578         seed = appData.defaultFrcPosition;
5579         if(seed < 0) { // randomize based on time for negative FRC position numbers
5580                 for(i=0; i<50; i++) seed += random();
5581                 seed = random() ^ random() >> 8 ^ random() << 8;
5582                 if(seed<0) seed = -seed;
5583         }
5584 }
5585
5586 int
5587 put (Board board, int pieceType, int rank, int n, int shade)
5588 // put the piece on the (n-1)-th empty squares of the given shade
5589 {
5590         int i;
5591
5592         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5593                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5594                         board[rank][i] = (ChessSquare) pieceType;
5595                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5596                         squaresLeft[ANY]--;
5597                         piecesLeft[pieceType]--;
5598                         return i;
5599                 }
5600         }
5601         return -1;
5602 }
5603
5604
5605 void
5606 AddOnePiece (Board board, int pieceType, int rank, int shade)
5607 // calculate where the next piece goes, (any empty square), and put it there
5608 {
5609         int i;
5610
5611         i = seed % squaresLeft[shade];
5612         nrOfShuffles *= squaresLeft[shade];
5613         seed /= squaresLeft[shade];
5614         put(board, pieceType, rank, i, shade);
5615 }
5616
5617 void
5618 AddTwoPieces (Board board, int pieceType, int rank)
5619 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5620 {
5621         int i, n=squaresLeft[ANY], j=n-1, k;
5622
5623         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5624         i = seed % k;  // pick one
5625         nrOfShuffles *= k;
5626         seed /= k;
5627         while(i >= j) i -= j--;
5628         j = n - 1 - j; i += j;
5629         put(board, pieceType, rank, j, ANY);
5630         put(board, pieceType, rank, i, ANY);
5631 }
5632
5633 void
5634 SetUpShuffle (Board board, int number)
5635 {
5636         int i, p, first=1;
5637
5638         GetPositionNumber(); nrOfShuffles = 1;
5639
5640         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5641         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5642         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5643
5644         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5645
5646         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5647             p = (int) board[0][i];
5648             if(p < (int) BlackPawn) piecesLeft[p] ++;
5649             board[0][i] = EmptySquare;
5650         }
5651
5652         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5653             // shuffles restricted to allow normal castling put KRR first
5654             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5655                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5656             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5657                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5658             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5659                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5660             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5661                 put(board, WhiteRook, 0, 0, ANY);
5662             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5663         }
5664
5665         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5666             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5667             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5668                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5669                 while(piecesLeft[p] >= 2) {
5670                     AddOnePiece(board, p, 0, LITE);
5671                     AddOnePiece(board, p, 0, DARK);
5672                 }
5673                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5674             }
5675
5676         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5677             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5678             // but we leave King and Rooks for last, to possibly obey FRC restriction
5679             if(p == (int)WhiteRook) continue;
5680             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5681             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5682         }
5683
5684         // now everything is placed, except perhaps King (Unicorn) and Rooks
5685
5686         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5687             // Last King gets castling rights
5688             while(piecesLeft[(int)WhiteUnicorn]) {
5689                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5690                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5691             }
5692
5693             while(piecesLeft[(int)WhiteKing]) {
5694                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5695                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5696             }
5697
5698
5699         } else {
5700             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5701             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5702         }
5703
5704         // Only Rooks can be left; simply place them all
5705         while(piecesLeft[(int)WhiteRook]) {
5706                 i = put(board, WhiteRook, 0, 0, ANY);
5707                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5708                         if(first) {
5709                                 first=0;
5710                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5711                         }
5712                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5713                 }
5714         }
5715         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5716             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5717         }
5718
5719         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5720 }
5721
5722 int
5723 SetCharTable (char *table, const char * map)
5724 /* [HGM] moved here from winboard.c because of its general usefulness */
5725 /*       Basically a safe strcpy that uses the last character as King */
5726 {
5727     int result = FALSE; int NrPieces;
5728
5729     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5730                     && NrPieces >= 12 && !(NrPieces&1)) {
5731         int i; /* [HGM] Accept even length from 12 to 34 */
5732
5733         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5734         for( i=0; i<NrPieces/2-1; i++ ) {
5735             table[i] = map[i];
5736             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5737         }
5738         table[(int) WhiteKing]  = map[NrPieces/2-1];
5739         table[(int) BlackKing]  = map[NrPieces-1];
5740
5741         result = TRUE;
5742     }
5743
5744     return result;
5745 }
5746
5747 void
5748 Prelude (Board board)
5749 {       // [HGM] superchess: random selection of exo-pieces
5750         int i, j, k; ChessSquare p;
5751         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5752
5753         GetPositionNumber(); // use FRC position number
5754
5755         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5756             SetCharTable(pieceToChar, appData.pieceToCharTable);
5757             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5758                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5759         }
5760
5761         j = seed%4;                 seed /= 4;
5762         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5763         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5764         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5765         j = seed%3 + (seed%3 >= j); seed /= 3;
5766         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5767         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5768         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5769         j = seed%3;                 seed /= 3;
5770         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5771         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5772         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5773         j = seed%2 + (seed%2 >= j); seed /= 2;
5774         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5775         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5776         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5777         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5778         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5779         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5780         put(board, exoPieces[0],    0, 0, ANY);
5781         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5782 }
5783
5784 void
5785 InitPosition (int redraw)
5786 {
5787     ChessSquare (* pieces)[BOARD_FILES];
5788     int i, j, pawnRow, overrule,
5789     oldx = gameInfo.boardWidth,
5790     oldy = gameInfo.boardHeight,
5791     oldh = gameInfo.holdingsWidth;
5792     static int oldv;
5793
5794     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5795
5796     /* [AS] Initialize pv info list [HGM] and game status */
5797     {
5798         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5799             pvInfoList[i].depth = 0;
5800             boards[i][EP_STATUS] = EP_NONE;
5801             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5802         }
5803
5804         initialRulePlies = 0; /* 50-move counter start */
5805
5806         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5807         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5808     }
5809
5810
5811     /* [HGM] logic here is completely changed. In stead of full positions */
5812     /* the initialized data only consist of the two backranks. The switch */
5813     /* selects which one we will use, which is than copied to the Board   */
5814     /* initialPosition, which for the rest is initialized by Pawns and    */
5815     /* empty squares. This initial position is then copied to boards[0],  */
5816     /* possibly after shuffling, so that it remains available.            */
5817
5818     gameInfo.holdingsWidth = 0; /* default board sizes */
5819     gameInfo.boardWidth    = 8;
5820     gameInfo.boardHeight   = 8;
5821     gameInfo.holdingsSize  = 0;
5822     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5823     for(i=0; i<BOARD_FILES-2; i++)
5824       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5825     initialPosition[EP_STATUS] = EP_NONE;
5826     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5827     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5828          SetCharTable(pieceNickName, appData.pieceNickNames);
5829     else SetCharTable(pieceNickName, "............");
5830     pieces = FIDEArray;
5831
5832     switch (gameInfo.variant) {
5833     case VariantFischeRandom:
5834       shuffleOpenings = TRUE;
5835     default:
5836       break;
5837     case VariantShatranj:
5838       pieces = ShatranjArray;
5839       nrCastlingRights = 0;
5840       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5841       break;
5842     case VariantMakruk:
5843       pieces = makrukArray;
5844       nrCastlingRights = 0;
5845       startedFromSetupPosition = TRUE;
5846       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5847       break;
5848     case VariantTwoKings:
5849       pieces = twoKingsArray;
5850       break;
5851     case VariantGrand:
5852       pieces = GrandArray;
5853       nrCastlingRights = 0;
5854       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5855       gameInfo.boardWidth = 10;
5856       gameInfo.boardHeight = 10;
5857       gameInfo.holdingsSize = 7;
5858       break;
5859     case VariantCapaRandom:
5860       shuffleOpenings = TRUE;
5861     case VariantCapablanca:
5862       pieces = CapablancaArray;
5863       gameInfo.boardWidth = 10;
5864       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5865       break;
5866     case VariantGothic:
5867       pieces = GothicArray;
5868       gameInfo.boardWidth = 10;
5869       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5870       break;
5871     case VariantSChess:
5872       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5873       gameInfo.holdingsSize = 7;
5874       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5875       break;
5876     case VariantJanus:
5877       pieces = JanusArray;
5878       gameInfo.boardWidth = 10;
5879       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5880       nrCastlingRights = 6;
5881         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5882         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5883         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5884         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5885         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5886         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5887       break;
5888     case VariantFalcon:
5889       pieces = FalconArray;
5890       gameInfo.boardWidth = 10;
5891       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5892       break;
5893     case VariantXiangqi:
5894       pieces = XiangqiArray;
5895       gameInfo.boardWidth  = 9;
5896       gameInfo.boardHeight = 10;
5897       nrCastlingRights = 0;
5898       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5899       break;
5900     case VariantShogi:
5901       pieces = ShogiArray;
5902       gameInfo.boardWidth  = 9;
5903       gameInfo.boardHeight = 9;
5904       gameInfo.holdingsSize = 7;
5905       nrCastlingRights = 0;
5906       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5907       break;
5908     case VariantCourier:
5909       pieces = CourierArray;
5910       gameInfo.boardWidth  = 12;
5911       nrCastlingRights = 0;
5912       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5913       break;
5914     case VariantKnightmate:
5915       pieces = KnightmateArray;
5916       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5917       break;
5918     case VariantSpartan:
5919       pieces = SpartanArray;
5920       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5921       break;
5922     case VariantFairy:
5923       pieces = fairyArray;
5924       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5925       break;
5926     case VariantGreat:
5927       pieces = GreatArray;
5928       gameInfo.boardWidth = 10;
5929       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5930       gameInfo.holdingsSize = 8;
5931       break;
5932     case VariantSuper:
5933       pieces = FIDEArray;
5934       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5935       gameInfo.holdingsSize = 8;
5936       startedFromSetupPosition = TRUE;
5937       break;
5938     case VariantCrazyhouse:
5939     case VariantBughouse:
5940       pieces = FIDEArray;
5941       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5942       gameInfo.holdingsSize = 5;
5943       break;
5944     case VariantWildCastle:
5945       pieces = FIDEArray;
5946       /* !!?shuffle with kings guaranteed to be on d or e file */
5947       shuffleOpenings = 1;
5948       break;
5949     case VariantNoCastle:
5950       pieces = FIDEArray;
5951       nrCastlingRights = 0;
5952       /* !!?unconstrained back-rank shuffle */
5953       shuffleOpenings = 1;
5954       break;
5955     }
5956
5957     overrule = 0;
5958     if(appData.NrFiles >= 0) {
5959         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5960         gameInfo.boardWidth = appData.NrFiles;
5961     }
5962     if(appData.NrRanks >= 0) {
5963         gameInfo.boardHeight = appData.NrRanks;
5964     }
5965     if(appData.holdingsSize >= 0) {
5966         i = appData.holdingsSize;
5967         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5968         gameInfo.holdingsSize = i;
5969     }
5970     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5971     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5972         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5973
5974     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5975     if(pawnRow < 1) pawnRow = 1;
5976     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5977
5978     /* User pieceToChar list overrules defaults */
5979     if(appData.pieceToCharTable != NULL)
5980         SetCharTable(pieceToChar, appData.pieceToCharTable);
5981
5982     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5983
5984         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5985             s = (ChessSquare) 0; /* account holding counts in guard band */
5986         for( i=0; i<BOARD_HEIGHT; i++ )
5987             initialPosition[i][j] = s;
5988
5989         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5990         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5991         initialPosition[pawnRow][j] = WhitePawn;
5992         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5993         if(gameInfo.variant == VariantXiangqi) {
5994             if(j&1) {
5995                 initialPosition[pawnRow][j] =
5996                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5997                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5998                    initialPosition[2][j] = WhiteCannon;
5999                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6000                 }
6001             }
6002         }
6003         if(gameInfo.variant == VariantGrand) {
6004             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6005                initialPosition[0][j] = WhiteRook;
6006                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6007             }
6008         }
6009         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6010     }
6011     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6012
6013             j=BOARD_LEFT+1;
6014             initialPosition[1][j] = WhiteBishop;
6015             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6016             j=BOARD_RGHT-2;
6017             initialPosition[1][j] = WhiteRook;
6018             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6019     }
6020
6021     if( nrCastlingRights == -1) {
6022         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6023         /*       This sets default castling rights from none to normal corners   */
6024         /* Variants with other castling rights must set them themselves above    */
6025         nrCastlingRights = 6;
6026
6027         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6028         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6029         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6030         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6031         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6032         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6033      }
6034
6035      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6036      if(gameInfo.variant == VariantGreat) { // promotion commoners
6037         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6038         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6039         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6040         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6041      }
6042      if( gameInfo.variant == VariantSChess ) {
6043       initialPosition[1][0] = BlackMarshall;
6044       initialPosition[2][0] = BlackAngel;
6045       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6046       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6047       initialPosition[1][1] = initialPosition[2][1] = 
6048       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6049      }
6050   if (appData.debugMode) {
6051     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6052   }
6053     if(shuffleOpenings) {
6054         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6055         startedFromSetupPosition = TRUE;
6056     }
6057     if(startedFromPositionFile) {
6058       /* [HGM] loadPos: use PositionFile for every new game */
6059       CopyBoard(initialPosition, filePosition);
6060       for(i=0; i<nrCastlingRights; i++)
6061           initialRights[i] = filePosition[CASTLING][i];
6062       startedFromSetupPosition = TRUE;
6063     }
6064
6065     CopyBoard(boards[0], initialPosition);
6066
6067     if(oldx != gameInfo.boardWidth ||
6068        oldy != gameInfo.boardHeight ||
6069        oldv != gameInfo.variant ||
6070        oldh != gameInfo.holdingsWidth
6071                                          )
6072             InitDrawingSizes(-2 ,0);
6073
6074     oldv = gameInfo.variant;
6075     if (redraw)
6076       DrawPosition(TRUE, boards[currentMove]);
6077 }
6078
6079 void
6080 SendBoard (ChessProgramState *cps, int moveNum)
6081 {
6082     char message[MSG_SIZ];
6083
6084     if (cps->useSetboard) {
6085       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6086       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6087       SendToProgram(message, cps);
6088       free(fen);
6089
6090     } else {
6091       ChessSquare *bp;
6092       int i, j, left=0, right=BOARD_WIDTH;
6093       /* Kludge to set black to move, avoiding the troublesome and now
6094        * deprecated "black" command.
6095        */
6096       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6097         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6098
6099       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6100
6101       SendToProgram("edit\n", cps);
6102       SendToProgram("#\n", cps);
6103       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6104         bp = &boards[moveNum][i][left];
6105         for (j = left; j < right; j++, bp++) {
6106           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6107           if ((int) *bp < (int) BlackPawn) {
6108             if(j == BOARD_RGHT+1)
6109                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6110             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6111             if(message[0] == '+' || message[0] == '~') {
6112               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6113                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6114                         AAA + j, ONE + i);
6115             }
6116             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6117                 message[1] = BOARD_RGHT   - 1 - j + '1';
6118                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6119             }
6120             SendToProgram(message, cps);
6121           }
6122         }
6123       }
6124
6125       SendToProgram("c\n", cps);
6126       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6127         bp = &boards[moveNum][i][left];
6128         for (j = left; j < right; j++, bp++) {
6129           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6130           if (((int) *bp != (int) EmptySquare)
6131               && ((int) *bp >= (int) BlackPawn)) {
6132             if(j == BOARD_LEFT-2)
6133                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6134             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6135                     AAA + j, ONE + i);
6136             if(message[0] == '+' || message[0] == '~') {
6137               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6138                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6139                         AAA + j, ONE + i);
6140             }
6141             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6142                 message[1] = BOARD_RGHT   - 1 - j + '1';
6143                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6144             }
6145             SendToProgram(message, cps);
6146           }
6147         }
6148       }
6149
6150       SendToProgram(".\n", cps);
6151     }
6152     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6153 }
6154
6155 char exclusionHeader[MSG_SIZ];
6156 int exCnt, excludePtr;
6157 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6158 static Exclusion excluTab[200];
6159 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6160
6161 static void
6162 WriteMap (int s)
6163 {
6164     int j;
6165     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6166     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6167 }
6168
6169 static void
6170 ClearMap ()
6171 {
6172     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6173     excludePtr = 24; exCnt = 0;
6174     WriteMap(0);
6175 }
6176
6177 static void
6178 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6179 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6180     char buf[2*MOVE_LEN], *p;
6181     Exclusion *e = excluTab;
6182     int i;
6183     for(i=0; i<exCnt; i++)
6184         if(e[i].ff == fromX && e[i].fr == fromY &&
6185            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6186     if(i == exCnt) { // was not in exclude list; add it
6187         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6188         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6189             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6190             return; // abort
6191         }
6192         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6193         excludePtr++; e[i].mark = excludePtr++;
6194         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6195         exCnt++;
6196     }
6197     exclusionHeader[e[i].mark] = state;
6198 }
6199
6200 static int
6201 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6202 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6203     char buf[MSG_SIZ];
6204     int j, k;
6205     ChessMove moveType;
6206     if((signed char)promoChar == -1) { // kludge to indicate best move
6207         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6208             return 1; // if unparsable, abort
6209     }
6210     // update exclusion map (resolving toggle by consulting existing state)
6211     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6212     j = k%8; k >>= 3;
6213     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6214     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6215          excludeMap[k] |=   1<<j;
6216     else excludeMap[k] &= ~(1<<j);
6217     // update header
6218     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6219     // inform engine
6220     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6221     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6222     SendToProgram(buf, &first);
6223     return (state == '+');
6224 }
6225
6226 static void
6227 ExcludeClick (int index)
6228 {
6229     int i, j;
6230     Exclusion *e = excluTab;
6231     if(index < 25) { // none, best or tail clicked
6232         if(index < 13) { // none: include all
6233             WriteMap(0); // clear map
6234             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6235             SendToProgram("include all\n", &first); // and inform engine
6236         } else if(index > 18) { // tail
6237             if(exclusionHeader[19] == '-') { // tail was excluded
6238                 SendToProgram("include all\n", &first);
6239                 WriteMap(0); // clear map completely
6240                 // now re-exclude selected moves
6241                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6242                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6243             } else { // tail was included or in mixed state
6244                 SendToProgram("exclude all\n", &first);
6245                 WriteMap(0xFF); // fill map completely
6246                 // now re-include selected moves
6247                 j = 0; // count them
6248                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6249                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6250                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6251             }
6252         } else { // best
6253             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6254         }
6255     } else {
6256         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6257             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6258             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6259             break;
6260         }
6261     }
6262 }
6263
6264 ChessSquare
6265 DefaultPromoChoice (int white)
6266 {
6267     ChessSquare result;
6268     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6269         result = WhiteFerz; // no choice
6270     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6271         result= WhiteKing; // in Suicide Q is the last thing we want
6272     else if(gameInfo.variant == VariantSpartan)
6273         result = white ? WhiteQueen : WhiteAngel;
6274     else result = WhiteQueen;
6275     if(!white) result = WHITE_TO_BLACK result;
6276     return result;
6277 }
6278
6279 static int autoQueen; // [HGM] oneclick
6280
6281 int
6282 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6283 {
6284     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6285     /* [HGM] add Shogi promotions */
6286     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6287     ChessSquare piece;
6288     ChessMove moveType;
6289     Boolean premove;
6290
6291     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6292     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6293
6294     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6295       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6296         return FALSE;
6297
6298     piece = boards[currentMove][fromY][fromX];
6299     if(gameInfo.variant == VariantShogi) {
6300         promotionZoneSize = BOARD_HEIGHT/3;
6301         highestPromotingPiece = (int)WhiteFerz;
6302     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6303         promotionZoneSize = 3;
6304     }
6305
6306     // Treat Lance as Pawn when it is not representing Amazon
6307     if(gameInfo.variant != VariantSuper) {
6308         if(piece == WhiteLance) piece = WhitePawn; else
6309         if(piece == BlackLance) piece = BlackPawn;
6310     }
6311
6312     // next weed out all moves that do not touch the promotion zone at all
6313     if((int)piece >= BlackPawn) {
6314         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6315              return FALSE;
6316         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6317     } else {
6318         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6319            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6320     }
6321
6322     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6323
6324     // weed out mandatory Shogi promotions
6325     if(gameInfo.variant == VariantShogi) {
6326         if(piece >= BlackPawn) {
6327             if(toY == 0 && piece == BlackPawn ||
6328                toY == 0 && piece == BlackQueen ||
6329                toY <= 1 && piece == BlackKnight) {
6330                 *promoChoice = '+';
6331                 return FALSE;
6332             }
6333         } else {
6334             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6335                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6336                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6337                 *promoChoice = '+';
6338                 return FALSE;
6339             }
6340         }
6341     }
6342
6343     // weed out obviously illegal Pawn moves
6344     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6345         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6346         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6347         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6348         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6349         // note we are not allowed to test for valid (non-)capture, due to premove
6350     }
6351
6352     // we either have a choice what to promote to, or (in Shogi) whether to promote
6353     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6354         *promoChoice = PieceToChar(BlackFerz);  // no choice
6355         return FALSE;
6356     }
6357     // no sense asking what we must promote to if it is going to explode...
6358     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6359         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6360         return FALSE;
6361     }
6362     // give caller the default choice even if we will not make it
6363     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6364     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6365     if(        sweepSelect && gameInfo.variant != VariantGreat
6366                            && gameInfo.variant != VariantGrand
6367                            && gameInfo.variant != VariantSuper) return FALSE;
6368     if(autoQueen) return FALSE; // predetermined
6369
6370     // suppress promotion popup on illegal moves that are not premoves
6371     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6372               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6373     if(appData.testLegality && !premove) {
6374         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6375                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6376         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6377             return FALSE;
6378     }
6379
6380     return TRUE;
6381 }
6382
6383 int
6384 InPalace (int row, int column)
6385 {   /* [HGM] for Xiangqi */
6386     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6387          column < (BOARD_WIDTH + 4)/2 &&
6388          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6389     return FALSE;
6390 }
6391
6392 int
6393 PieceForSquare (int x, int y)
6394 {
6395   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6396      return -1;
6397   else
6398      return boards[currentMove][y][x];
6399 }
6400
6401 int
6402 OKToStartUserMove (int x, int y)
6403 {
6404     ChessSquare from_piece;
6405     int white_piece;
6406
6407     if (matchMode) return FALSE;
6408     if (gameMode == EditPosition) return TRUE;
6409
6410     if (x >= 0 && y >= 0)
6411       from_piece = boards[currentMove][y][x];
6412     else
6413       from_piece = EmptySquare;
6414
6415     if (from_piece == EmptySquare) return FALSE;
6416
6417     white_piece = (int)from_piece >= (int)WhitePawn &&
6418       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6419
6420     switch (gameMode) {
6421       case AnalyzeFile:
6422       case TwoMachinesPlay:
6423       case EndOfGame:
6424         return FALSE;
6425
6426       case IcsObserving:
6427       case IcsIdle:
6428         return FALSE;
6429
6430       case MachinePlaysWhite:
6431       case IcsPlayingBlack:
6432         if (appData.zippyPlay) return FALSE;
6433         if (white_piece) {
6434             DisplayMoveError(_("You are playing Black"));
6435             return FALSE;
6436         }
6437         break;
6438
6439       case MachinePlaysBlack:
6440       case IcsPlayingWhite:
6441         if (appData.zippyPlay) return FALSE;
6442         if (!white_piece) {
6443             DisplayMoveError(_("You are playing White"));
6444             return FALSE;
6445         }
6446         break;
6447
6448       case PlayFromGameFile:
6449             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6450       case EditGame:
6451         if (!white_piece && WhiteOnMove(currentMove)) {
6452             DisplayMoveError(_("It is White's turn"));
6453             return FALSE;
6454         }
6455         if (white_piece && !WhiteOnMove(currentMove)) {
6456             DisplayMoveError(_("It is Black's turn"));
6457             return FALSE;
6458         }
6459         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6460             /* Editing correspondence game history */
6461             /* Could disallow this or prompt for confirmation */
6462             cmailOldMove = -1;
6463         }
6464         break;
6465
6466       case BeginningOfGame:
6467         if (appData.icsActive) return FALSE;
6468         if (!appData.noChessProgram) {
6469             if (!white_piece) {
6470                 DisplayMoveError(_("You are playing White"));
6471                 return FALSE;
6472             }
6473         }
6474         break;
6475
6476       case Training:
6477         if (!white_piece && WhiteOnMove(currentMove)) {
6478             DisplayMoveError(_("It is White's turn"));
6479             return FALSE;
6480         }
6481         if (white_piece && !WhiteOnMove(currentMove)) {
6482             DisplayMoveError(_("It is Black's turn"));
6483             return FALSE;
6484         }
6485         break;
6486
6487       default:
6488       case IcsExamining:
6489         break;
6490     }
6491     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6492         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6493         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6494         && gameMode != AnalyzeFile && gameMode != Training) {
6495         DisplayMoveError(_("Displayed position is not current"));
6496         return FALSE;
6497     }
6498     return TRUE;
6499 }
6500
6501 Boolean
6502 OnlyMove (int *x, int *y, Boolean captures) 
6503 {
6504     DisambiguateClosure cl;
6505     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6506     switch(gameMode) {
6507       case MachinePlaysBlack:
6508       case IcsPlayingWhite:
6509       case BeginningOfGame:
6510         if(!WhiteOnMove(currentMove)) return FALSE;
6511         break;
6512       case MachinePlaysWhite:
6513       case IcsPlayingBlack:
6514         if(WhiteOnMove(currentMove)) return FALSE;
6515         break;
6516       case EditGame:
6517         break;
6518       default:
6519         return FALSE;
6520     }
6521     cl.pieceIn = EmptySquare;
6522     cl.rfIn = *y;
6523     cl.ffIn = *x;
6524     cl.rtIn = -1;
6525     cl.ftIn = -1;
6526     cl.promoCharIn = NULLCHAR;
6527     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6528     if( cl.kind == NormalMove ||
6529         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6530         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6531         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6532       fromX = cl.ff;
6533       fromY = cl.rf;
6534       *x = cl.ft;
6535       *y = cl.rt;
6536       return TRUE;
6537     }
6538     if(cl.kind != ImpossibleMove) return FALSE;
6539     cl.pieceIn = EmptySquare;
6540     cl.rfIn = -1;
6541     cl.ffIn = -1;
6542     cl.rtIn = *y;
6543     cl.ftIn = *x;
6544     cl.promoCharIn = NULLCHAR;
6545     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6546     if( cl.kind == NormalMove ||
6547         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6548         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6549         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6550       fromX = cl.ff;
6551       fromY = cl.rf;
6552       *x = cl.ft;
6553       *y = cl.rt;
6554       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6555       return TRUE;
6556     }
6557     return FALSE;
6558 }
6559
6560 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6561 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6562 int lastLoadGameUseList = FALSE;
6563 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6564 ChessMove lastLoadGameStart = EndOfFile;
6565 int doubleClick;
6566
6567 void
6568 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6569 {
6570     ChessMove moveType;
6571     ChessSquare pup;
6572     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6573
6574     /* Check if the user is playing in turn.  This is complicated because we
6575        let the user "pick up" a piece before it is his turn.  So the piece he
6576        tried to pick up may have been captured by the time he puts it down!
6577        Therefore we use the color the user is supposed to be playing in this
6578        test, not the color of the piece that is currently on the starting
6579        square---except in EditGame mode, where the user is playing both
6580        sides; fortunately there the capture race can't happen.  (It can
6581        now happen in IcsExamining mode, but that's just too bad.  The user
6582        will get a somewhat confusing message in that case.)
6583        */
6584
6585     switch (gameMode) {
6586       case AnalyzeFile:
6587       case TwoMachinesPlay:
6588       case EndOfGame:
6589       case IcsObserving:
6590       case IcsIdle:
6591         /* We switched into a game mode where moves are not accepted,
6592            perhaps while the mouse button was down. */
6593         return;
6594
6595       case MachinePlaysWhite:
6596         /* User is moving for Black */
6597         if (WhiteOnMove(currentMove)) {
6598             DisplayMoveError(_("It is White's turn"));
6599             return;
6600         }
6601         break;
6602
6603       case MachinePlaysBlack:
6604         /* User is moving for White */
6605         if (!WhiteOnMove(currentMove)) {
6606             DisplayMoveError(_("It is Black's turn"));
6607             return;
6608         }
6609         break;
6610
6611       case PlayFromGameFile:
6612             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6613       case EditGame:
6614       case IcsExamining:
6615       case BeginningOfGame:
6616       case AnalyzeMode:
6617       case Training:
6618         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6619         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6620             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6621             /* User is moving for Black */
6622             if (WhiteOnMove(currentMove)) {
6623                 DisplayMoveError(_("It is White's turn"));
6624                 return;
6625             }
6626         } else {
6627             /* User is moving for White */
6628             if (!WhiteOnMove(currentMove)) {
6629                 DisplayMoveError(_("It is Black's turn"));
6630                 return;
6631             }
6632         }
6633         break;
6634
6635       case IcsPlayingBlack:
6636         /* User is moving for Black */
6637         if (WhiteOnMove(currentMove)) {
6638             if (!appData.premove) {
6639                 DisplayMoveError(_("It is White's turn"));
6640             } else if (toX >= 0 && toY >= 0) {
6641                 premoveToX = toX;
6642                 premoveToY = toY;
6643                 premoveFromX = fromX;
6644                 premoveFromY = fromY;
6645                 premovePromoChar = promoChar;
6646                 gotPremove = 1;
6647                 if (appData.debugMode)
6648                     fprintf(debugFP, "Got premove: fromX %d,"
6649                             "fromY %d, toX %d, toY %d\n",
6650                             fromX, fromY, toX, toY);
6651             }
6652             return;
6653         }
6654         break;
6655
6656       case IcsPlayingWhite:
6657         /* User is moving for White */
6658         if (!WhiteOnMove(currentMove)) {
6659             if (!appData.premove) {
6660                 DisplayMoveError(_("It is Black's turn"));
6661             } else if (toX >= 0 && toY >= 0) {
6662                 premoveToX = toX;
6663                 premoveToY = toY;
6664                 premoveFromX = fromX;
6665                 premoveFromY = fromY;
6666                 premovePromoChar = promoChar;
6667                 gotPremove = 1;
6668                 if (appData.debugMode)
6669                     fprintf(debugFP, "Got premove: fromX %d,"
6670                             "fromY %d, toX %d, toY %d\n",
6671                             fromX, fromY, toX, toY);
6672             }
6673             return;
6674         }
6675         break;
6676
6677       default:
6678         break;
6679
6680       case EditPosition:
6681         /* EditPosition, empty square, or different color piece;
6682            click-click move is possible */
6683         if (toX == -2 || toY == -2) {
6684             boards[0][fromY][fromX] = EmptySquare;
6685             DrawPosition(FALSE, boards[currentMove]);
6686             return;
6687         } else if (toX >= 0 && toY >= 0) {
6688             boards[0][toY][toX] = boards[0][fromY][fromX];
6689             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6690                 if(boards[0][fromY][0] != EmptySquare) {
6691                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6692                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6693                 }
6694             } else
6695             if(fromX == BOARD_RGHT+1) {
6696                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6697                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6698                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6699                 }
6700             } else
6701             boards[0][fromY][fromX] = gatingPiece;
6702             DrawPosition(FALSE, boards[currentMove]);
6703             return;
6704         }
6705         return;
6706     }
6707
6708     if(toX < 0 || toY < 0) return;
6709     pup = boards[currentMove][toY][toX];
6710
6711     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6712     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6713          if( pup != EmptySquare ) return;
6714          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6715            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6716                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6717            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6718            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6719            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6720            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6721          fromY = DROP_RANK;
6722     }
6723
6724     /* [HGM] always test for legality, to get promotion info */
6725     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6726                                          fromY, fromX, toY, toX, promoChar);
6727
6728     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6729
6730     /* [HGM] but possibly ignore an IllegalMove result */
6731     if (appData.testLegality) {
6732         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6733             DisplayMoveError(_("Illegal move"));
6734             return;
6735         }
6736     }
6737
6738     if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6739         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6740              ClearPremoveHighlights(); // was included
6741         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6742         return;
6743     }
6744
6745     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6746 }
6747
6748 /* Common tail of UserMoveEvent and DropMenuEvent */
6749 int
6750 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6751 {
6752     char *bookHit = 0;
6753
6754     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6755         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6756         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6757         if(WhiteOnMove(currentMove)) {
6758             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6759         } else {
6760             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6761         }
6762     }
6763
6764     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6765        move type in caller when we know the move is a legal promotion */
6766     if(moveType == NormalMove && promoChar)
6767         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6768
6769     /* [HGM] <popupFix> The following if has been moved here from
6770        UserMoveEvent(). Because it seemed to belong here (why not allow
6771        piece drops in training games?), and because it can only be
6772        performed after it is known to what we promote. */
6773     if (gameMode == Training) {
6774       /* compare the move played on the board to the next move in the
6775        * game. If they match, display the move and the opponent's response.
6776        * If they don't match, display an error message.
6777        */
6778       int saveAnimate;
6779       Board testBoard;
6780       CopyBoard(testBoard, boards[currentMove]);
6781       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6782
6783       if (CompareBoards(testBoard, boards[currentMove+1])) {
6784         ForwardInner(currentMove+1);
6785
6786         /* Autoplay the opponent's response.
6787          * if appData.animate was TRUE when Training mode was entered,
6788          * the response will be animated.
6789          */
6790         saveAnimate = appData.animate;
6791         appData.animate = animateTraining;
6792         ForwardInner(currentMove+1);
6793         appData.animate = saveAnimate;
6794
6795         /* check for the end of the game */
6796         if (currentMove >= forwardMostMove) {
6797           gameMode = PlayFromGameFile;
6798           ModeHighlight();
6799           SetTrainingModeOff();
6800           DisplayInformation(_("End of game"));
6801         }
6802       } else {
6803         DisplayError(_("Incorrect move"), 0);
6804       }
6805       return 1;
6806     }
6807
6808   /* Ok, now we know that the move is good, so we can kill
6809      the previous line in Analysis Mode */
6810   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6811                                 && currentMove < forwardMostMove) {
6812     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6813     else forwardMostMove = currentMove;
6814   }
6815
6816   ClearMap();
6817
6818   /* If we need the chess program but it's dead, restart it */
6819   ResurrectChessProgram();
6820
6821   /* A user move restarts a paused game*/
6822   if (pausing)
6823     PauseEvent();
6824
6825   thinkOutput[0] = NULLCHAR;
6826
6827   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6828
6829   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6830     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6831     return 1;
6832   }
6833
6834   if (gameMode == BeginningOfGame) {
6835     if (appData.noChessProgram) {
6836       gameMode = EditGame;
6837       SetGameInfo();
6838     } else {
6839       char buf[MSG_SIZ];
6840       gameMode = MachinePlaysBlack;
6841       StartClocks();
6842       SetGameInfo();
6843       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6844       DisplayTitle(buf);
6845       if (first.sendName) {
6846         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6847         SendToProgram(buf, &first);
6848       }
6849       StartClocks();
6850     }
6851     ModeHighlight();
6852   }
6853
6854   /* Relay move to ICS or chess engine */
6855   if (appData.icsActive) {
6856     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6857         gameMode == IcsExamining) {
6858       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6859         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6860         SendToICS("draw ");
6861         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6862       }
6863       // also send plain move, in case ICS does not understand atomic claims
6864       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6865       ics_user_moved = 1;
6866     }
6867   } else {
6868     if (first.sendTime && (gameMode == BeginningOfGame ||
6869                            gameMode == MachinePlaysWhite ||
6870                            gameMode == MachinePlaysBlack)) {
6871       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6872     }
6873     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6874          // [HGM] book: if program might be playing, let it use book
6875         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6876         first.maybeThinking = TRUE;
6877     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6878         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6879         SendBoard(&first, currentMove+1);
6880     } else SendMoveToProgram(forwardMostMove-1, &first);
6881     if (currentMove == cmailOldMove + 1) {
6882       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6883     }
6884   }
6885
6886   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6887
6888   switch (gameMode) {
6889   case EditGame:
6890     if(appData.testLegality)
6891     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6892     case MT_NONE:
6893     case MT_CHECK:
6894       break;
6895     case MT_CHECKMATE:
6896     case MT_STAINMATE:
6897       if (WhiteOnMove(currentMove)) {
6898         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6899       } else {
6900         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6901       }
6902       break;
6903     case MT_STALEMATE:
6904       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6905       break;
6906     }
6907     break;
6908
6909   case MachinePlaysBlack:
6910   case MachinePlaysWhite:
6911     /* disable certain menu options while machine is thinking */
6912     SetMachineThinkingEnables();
6913     break;
6914
6915   default:
6916     break;
6917   }
6918
6919   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6920   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6921
6922   if(bookHit) { // [HGM] book: simulate book reply
6923         static char bookMove[MSG_SIZ]; // a bit generous?
6924
6925         programStats.nodes = programStats.depth = programStats.time =
6926         programStats.score = programStats.got_only_move = 0;
6927         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6928
6929         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6930         strcat(bookMove, bookHit);
6931         HandleMachineMove(bookMove, &first);
6932   }
6933   return 1;
6934 }
6935
6936 void
6937 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6938 {
6939     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6940     Markers *m = (Markers *) closure;
6941     if(rf == fromY && ff == fromX)
6942         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6943                          || kind == WhiteCapturesEnPassant
6944                          || kind == BlackCapturesEnPassant);
6945     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6946 }
6947
6948 void
6949 MarkTargetSquares (int clear)
6950 {
6951   int x, y;
6952   if(clear) // no reason to ever suppress clearing
6953     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6954   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6955      !appData.testLegality || gameMode == EditPosition) return;
6956   if(!clear) {
6957     int capt = 0;
6958     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6959     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6960       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6961       if(capt)
6962       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6963     }
6964   }
6965   DrawPosition(FALSE, NULL);
6966 }
6967
6968 int
6969 Explode (Board board, int fromX, int fromY, int toX, int toY)
6970 {
6971     if(gameInfo.variant == VariantAtomic &&
6972        (board[toY][toX] != EmptySquare ||                     // capture?
6973         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6974                          board[fromY][fromX] == BlackPawn   )
6975       )) {
6976         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6977         return TRUE;
6978     }
6979     return FALSE;
6980 }
6981
6982 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6983
6984 int
6985 CanPromote (ChessSquare piece, int y)
6986 {
6987         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6988         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6989         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6990            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6991            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6992                                                   gameInfo.variant == VariantMakruk) return FALSE;
6993         return (piece == BlackPawn && y == 1 ||
6994                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6995                 piece == BlackLance && y == 1 ||
6996                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6997 }
6998
6999 void
7000 LeftClick (ClickType clickType, int xPix, int yPix)
7001 {
7002     int x, y;
7003     Boolean saveAnimate;
7004     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7005     char promoChoice = NULLCHAR;
7006     ChessSquare piece;
7007     static TimeMark lastClickTime, prevClickTime;
7008
7009     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7010
7011     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7012
7013     if (clickType == Press) ErrorPopDown();
7014
7015     x = EventToSquare(xPix, BOARD_WIDTH);
7016     y = EventToSquare(yPix, BOARD_HEIGHT);
7017     if (!flipView && y >= 0) {
7018         y = BOARD_HEIGHT - 1 - y;
7019     }
7020     if (flipView && x >= 0) {
7021         x = BOARD_WIDTH - 1 - x;
7022     }
7023
7024     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7025         defaultPromoChoice = promoSweep;
7026         promoSweep = EmptySquare;   // terminate sweep
7027         promoDefaultAltered = TRUE;
7028         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7029     }
7030
7031     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7032         if(clickType == Release) return; // ignore upclick of click-click destination
7033         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7034         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7035         if(gameInfo.holdingsWidth &&
7036                 (WhiteOnMove(currentMove)
7037                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7038                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7039             // click in right holdings, for determining promotion piece
7040             ChessSquare p = boards[currentMove][y][x];
7041             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7042             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7043             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7044                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7045                 fromX = fromY = -1;
7046                 return;
7047             }
7048         }
7049         DrawPosition(FALSE, boards[currentMove]);
7050         return;
7051     }
7052
7053     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7054     if(clickType == Press
7055             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7056               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7057               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7058         return;
7059
7060     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7061         // could be static click on premove from-square: abort premove
7062         gotPremove = 0;
7063         ClearPremoveHighlights();
7064     }
7065
7066     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7067         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7068
7069     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7070         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7071                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7072         defaultPromoChoice = DefaultPromoChoice(side);
7073     }
7074
7075     autoQueen = appData.alwaysPromoteToQueen;
7076
7077     if (fromX == -1) {
7078       int originalY = y;
7079       gatingPiece = EmptySquare;
7080       if (clickType != Press) {
7081         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7082             DragPieceEnd(xPix, yPix); dragging = 0;
7083             DrawPosition(FALSE, NULL);
7084         }
7085         return;
7086       }
7087       doubleClick = FALSE;
7088       fromX = x; fromY = y; toX = toY = -1;
7089       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7090          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7091          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7092             /* First square */
7093             if (OKToStartUserMove(fromX, fromY)) {
7094                 second = 0;
7095                 MarkTargetSquares(0);
7096                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7097                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7098                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7099                     promoSweep = defaultPromoChoice;
7100                     selectFlag = 0; lastX = xPix; lastY = yPix;
7101                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7102                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7103                 }
7104                 if (appData.highlightDragging) {
7105                     SetHighlights(fromX, fromY, -1, -1);
7106                 } else {
7107                     ClearHighlights();
7108                 }
7109             } else fromX = fromY = -1;
7110             return;
7111         }
7112     }
7113
7114     /* fromX != -1 */
7115     if (clickType == Press && gameMode != EditPosition) {
7116         ChessSquare fromP;
7117         ChessSquare toP;
7118         int frc;
7119
7120         // ignore off-board to clicks
7121         if(y < 0 || x < 0) return;
7122
7123         /* Check if clicking again on the same color piece */
7124         fromP = boards[currentMove][fromY][fromX];
7125         toP = boards[currentMove][y][x];
7126         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7127         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7128              WhitePawn <= toP && toP <= WhiteKing &&
7129              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7130              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7131             (BlackPawn <= fromP && fromP <= BlackKing &&
7132              BlackPawn <= toP && toP <= BlackKing &&
7133              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7134              !(fromP == BlackKing && toP == BlackRook && frc))) {
7135             /* Clicked again on same color piece -- changed his mind */
7136             second = (x == fromX && y == fromY);
7137             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7138                 second = FALSE; // first double-click rather than scond click
7139                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7140             }
7141             promoDefaultAltered = FALSE;
7142             MarkTargetSquares(1);
7143            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7144             if (appData.highlightDragging) {
7145                 SetHighlights(x, y, -1, -1);
7146             } else {
7147                 ClearHighlights();
7148             }
7149             if (OKToStartUserMove(x, y)) {
7150                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7151                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7152                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7153                  gatingPiece = boards[currentMove][fromY][fromX];
7154                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7155                 fromX = x;
7156                 fromY = y; dragging = 1;
7157                 MarkTargetSquares(0);
7158                 DragPieceBegin(xPix, yPix, FALSE);
7159                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7160                     promoSweep = defaultPromoChoice;
7161                     selectFlag = 0; lastX = xPix; lastY = yPix;
7162                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7163                 }
7164             }
7165            }
7166            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7167            second = FALSE; 
7168         }
7169         // ignore clicks on holdings
7170         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7171     }
7172
7173     if (clickType == Release && x == fromX && y == fromY) {
7174         DragPieceEnd(xPix, yPix); dragging = 0;
7175         if(clearFlag) {
7176             // a deferred attempt to click-click move an empty square on top of a piece
7177             boards[currentMove][y][x] = EmptySquare;
7178             ClearHighlights();
7179             DrawPosition(FALSE, boards[currentMove]);
7180             fromX = fromY = -1; clearFlag = 0;
7181             return;
7182         }
7183         if (appData.animateDragging) {
7184             /* Undo animation damage if any */
7185             DrawPosition(FALSE, NULL);
7186         }
7187         if (second || sweepSelecting) {
7188             /* Second up/down in same square; just abort move */
7189             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7190             second = sweepSelecting = 0;
7191             fromX = fromY = -1;
7192             gatingPiece = EmptySquare;
7193             ClearHighlights();
7194             gotPremove = 0;
7195             ClearPremoveHighlights();
7196         } else {
7197             /* First upclick in same square; start click-click mode */
7198             SetHighlights(x, y, -1, -1);
7199         }
7200         return;
7201     }
7202
7203     clearFlag = 0;
7204
7205     /* we now have a different from- and (possibly off-board) to-square */
7206     /* Completed move */
7207     if(!sweepSelecting) {
7208         toX = x;
7209         toY = y;
7210     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7211
7212     saveAnimate = appData.animate;
7213     if (clickType == Press) {
7214         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7215             // must be Edit Position mode with empty-square selected
7216             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7217             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7218             return;
7219         }
7220         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7221           if(appData.sweepSelect) {
7222             ChessSquare piece = boards[currentMove][fromY][fromX];
7223             promoSweep = defaultPromoChoice;
7224             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7225             selectFlag = 0; lastX = xPix; lastY = yPix;
7226             Sweep(0); // Pawn that is going to promote: preview promotion piece
7227             sweepSelecting = 1;
7228             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7229             MarkTargetSquares(1);
7230           }
7231           return; // promo popup appears on up-click
7232         }
7233         /* Finish clickclick move */
7234         if (appData.animate || appData.highlightLastMove) {
7235             SetHighlights(fromX, fromY, toX, toY);
7236         } else {
7237             ClearHighlights();
7238         }
7239     } else {
7240         /* Finish drag move */
7241         if (appData.highlightLastMove) {
7242             SetHighlights(fromX, fromY, toX, toY);
7243         } else {
7244             ClearHighlights();
7245         }
7246         DragPieceEnd(xPix, yPix); dragging = 0;
7247         /* Don't animate move and drag both */
7248         appData.animate = FALSE;
7249     }
7250     MarkTargetSquares(1);
7251
7252     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7253     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7254         ChessSquare piece = boards[currentMove][fromY][fromX];
7255         if(gameMode == EditPosition && piece != EmptySquare &&
7256            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7257             int n;
7258
7259             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7260                 n = PieceToNumber(piece - (int)BlackPawn);
7261                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7262                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7263                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7264             } else
7265             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7266                 n = PieceToNumber(piece);
7267                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7268                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7269                 boards[currentMove][n][BOARD_WIDTH-2]++;
7270             }
7271             boards[currentMove][fromY][fromX] = EmptySquare;
7272         }
7273         ClearHighlights();
7274         fromX = fromY = -1;
7275         DrawPosition(TRUE, boards[currentMove]);
7276         return;
7277     }
7278
7279     // off-board moves should not be highlighted
7280     if(x < 0 || y < 0) ClearHighlights();
7281
7282     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7283
7284     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7285         SetHighlights(fromX, fromY, toX, toY);
7286         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7287             // [HGM] super: promotion to captured piece selected from holdings
7288             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7289             promotionChoice = TRUE;
7290             // kludge follows to temporarily execute move on display, without promoting yet
7291             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7292             boards[currentMove][toY][toX] = p;
7293             DrawPosition(FALSE, boards[currentMove]);
7294             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7295             boards[currentMove][toY][toX] = q;
7296             DisplayMessage("Click in holdings to choose piece", "");
7297             return;
7298         }
7299         PromotionPopUp();
7300     } else {
7301         int oldMove = currentMove;
7302         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7303         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7304         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7305         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7306            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7307             DrawPosition(TRUE, boards[currentMove]);
7308         fromX = fromY = -1;
7309     }
7310     appData.animate = saveAnimate;
7311     if (appData.animate || appData.animateDragging) {
7312         /* Undo animation damage if needed */
7313         DrawPosition(FALSE, NULL);
7314     }
7315 }
7316
7317 int
7318 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7319 {   // front-end-free part taken out of PieceMenuPopup
7320     int whichMenu; int xSqr, ySqr;
7321
7322     if(seekGraphUp) { // [HGM] seekgraph
7323         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7324         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7325         return -2;
7326     }
7327
7328     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7329          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7330         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7331         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7332         if(action == Press)   {
7333             originalFlip = flipView;
7334             flipView = !flipView; // temporarily flip board to see game from partners perspective
7335             DrawPosition(TRUE, partnerBoard);
7336             DisplayMessage(partnerStatus, "");
7337             partnerUp = TRUE;
7338         } else if(action == Release) {
7339             flipView = originalFlip;
7340             DrawPosition(TRUE, boards[currentMove]);
7341             partnerUp = FALSE;
7342         }
7343         return -2;
7344     }
7345
7346     xSqr = EventToSquare(x, BOARD_WIDTH);
7347     ySqr = EventToSquare(y, BOARD_HEIGHT);
7348     if (action == Release) {
7349         if(pieceSweep != EmptySquare) {
7350             EditPositionMenuEvent(pieceSweep, toX, toY);
7351             pieceSweep = EmptySquare;
7352         } else UnLoadPV(); // [HGM] pv
7353     }
7354     if (action != Press) return -2; // return code to be ignored
7355     switch (gameMode) {
7356       case IcsExamining:
7357         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7358       case EditPosition:
7359         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7360         if (xSqr < 0 || ySqr < 0) return -1;
7361         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7362         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7363         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7364         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7365         NextPiece(0);
7366         return 2; // grab
7367       case IcsObserving:
7368         if(!appData.icsEngineAnalyze) return -1;
7369       case IcsPlayingWhite:
7370       case IcsPlayingBlack:
7371         if(!appData.zippyPlay) goto noZip;
7372       case AnalyzeMode:
7373       case AnalyzeFile:
7374       case MachinePlaysWhite:
7375       case MachinePlaysBlack:
7376       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7377         if (!appData.dropMenu) {
7378           LoadPV(x, y);
7379           return 2; // flag front-end to grab mouse events
7380         }
7381         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7382            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7383       case EditGame:
7384       noZip:
7385         if (xSqr < 0 || ySqr < 0) return -1;
7386         if (!appData.dropMenu || appData.testLegality &&
7387             gameInfo.variant != VariantBughouse &&
7388             gameInfo.variant != VariantCrazyhouse) return -1;
7389         whichMenu = 1; // drop menu
7390         break;
7391       default:
7392         return -1;
7393     }
7394
7395     if (((*fromX = xSqr) < 0) ||
7396         ((*fromY = ySqr) < 0)) {
7397         *fromX = *fromY = -1;
7398         return -1;
7399     }
7400     if (flipView)
7401       *fromX = BOARD_WIDTH - 1 - *fromX;
7402     else
7403       *fromY = BOARD_HEIGHT - 1 - *fromY;
7404
7405     return whichMenu;
7406 }
7407
7408 void
7409 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7410 {
7411 //    char * hint = lastHint;
7412     FrontEndProgramStats stats;
7413
7414     stats.which = cps == &first ? 0 : 1;
7415     stats.depth = cpstats->depth;
7416     stats.nodes = cpstats->nodes;
7417     stats.score = cpstats->score;
7418     stats.time = cpstats->time;
7419     stats.pv = cpstats->movelist;
7420     stats.hint = lastHint;
7421     stats.an_move_index = 0;
7422     stats.an_move_count = 0;
7423
7424     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7425         stats.hint = cpstats->move_name;
7426         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7427         stats.an_move_count = cpstats->nr_moves;
7428     }
7429
7430     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
7431
7432     SetProgramStats( &stats );
7433 }
7434
7435 void
7436 ClearEngineOutputPane (int which)
7437 {
7438     static FrontEndProgramStats dummyStats;
7439     dummyStats.which = which;
7440     dummyStats.pv = "#";
7441     SetProgramStats( &dummyStats );
7442 }
7443
7444 #define MAXPLAYERS 500
7445
7446 char *
7447 TourneyStandings (int display)
7448 {
7449     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7450     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7451     char result, *p, *names[MAXPLAYERS];
7452
7453     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7454         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7455     names[0] = p = strdup(appData.participants);
7456     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7457
7458     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7459
7460     while(result = appData.results[nr]) {
7461         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7462         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7463         wScore = bScore = 0;
7464         switch(result) {
7465           case '+': wScore = 2; break;
7466           case '-': bScore = 2; break;
7467           case '=': wScore = bScore = 1; break;
7468           case ' ':
7469           case '*': return strdup("busy"); // tourney not finished
7470         }
7471         score[w] += wScore;
7472         score[b] += bScore;
7473         games[w]++;
7474         games[b]++;
7475         nr++;
7476     }
7477     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7478     for(w=0; w<nPlayers; w++) {
7479         bScore = -1;
7480         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7481         ranking[w] = b; points[w] = bScore; score[b] = -2;
7482     }
7483     p = malloc(nPlayers*34+1);
7484     for(w=0; w<nPlayers && w<display; w++)
7485         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7486     free(names[0]);
7487     return p;
7488 }
7489
7490 void
7491 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7492 {       // count all piece types
7493         int p, f, r;
7494         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7495         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7496         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7497                 p = board[r][f];
7498                 pCnt[p]++;
7499                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7500                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7501                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7502                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7503                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7504                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7505         }
7506 }
7507
7508 int
7509 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7510 {
7511         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7512         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7513
7514         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7515         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7516         if(myPawns == 2 && nMine == 3) // KPP
7517             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7518         if(myPawns == 1 && nMine == 2) // KP
7519             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7520         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7521             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7522         if(myPawns) return FALSE;
7523         if(pCnt[WhiteRook+side])
7524             return pCnt[BlackRook-side] ||
7525                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7526                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7527                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7528         if(pCnt[WhiteCannon+side]) {
7529             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7530             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7531         }
7532         if(pCnt[WhiteKnight+side])
7533             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7534         return FALSE;
7535 }
7536
7537 int
7538 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7539 {
7540         VariantClass v = gameInfo.variant;
7541
7542         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7543         if(v == VariantShatranj) return TRUE; // always winnable through baring
7544         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7545         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7546
7547         if(v == VariantXiangqi) {
7548                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7549
7550                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7551                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7552                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7553                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7554                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7555                 if(stale) // we have at least one last-rank P plus perhaps C
7556                     return majors // KPKX
7557                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7558                 else // KCA*E*
7559                     return pCnt[WhiteFerz+side] // KCAK
7560                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7561                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7562                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7563
7564         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7565                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7566
7567                 if(nMine == 1) return FALSE; // bare King
7568                 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
7569                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7570                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7571                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7572                 if(pCnt[WhiteKnight+side])
7573                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7574                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7575                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7576                 if(nBishops)
7577                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7578                 if(pCnt[WhiteAlfil+side])
7579                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7580                 if(pCnt[WhiteWazir+side])
7581                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7582         }
7583
7584         return TRUE;
7585 }
7586
7587 int
7588 CompareWithRights (Board b1, Board b2)
7589 {
7590     int rights = 0;
7591     if(!CompareBoards(b1, b2)) return FALSE;
7592     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7593     /* compare castling rights */
7594     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7595            rights++; /* King lost rights, while rook still had them */
7596     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7597         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7598            rights++; /* but at least one rook lost them */
7599     }
7600     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7601            rights++;
7602     if( b1[CASTLING][5] != NoRights ) {
7603         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7604            rights++;
7605     }
7606     return rights == 0;
7607 }
7608
7609 int
7610 Adjudicate (ChessProgramState *cps)
7611 {       // [HGM] some adjudications useful with buggy engines
7612         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7613         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7614         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7615         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7616         int k, count = 0; static int bare = 1;
7617         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7618         Boolean canAdjudicate = !appData.icsActive;
7619
7620         // most tests only when we understand the game, i.e. legality-checking on
7621             if( appData.testLegality )
7622             {   /* [HGM] Some more adjudications for obstinate engines */
7623                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7624                 static int moveCount = 6;
7625                 ChessMove result;
7626                 char *reason = NULL;
7627
7628                 /* Count what is on board. */
7629                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7630
7631                 /* Some material-based adjudications that have to be made before stalemate test */
7632                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7633                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7634                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7635                      if(canAdjudicate && appData.checkMates) {
7636                          if(engineOpponent)
7637                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7638                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7639                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7640                          return 1;
7641                      }
7642                 }
7643
7644                 /* Bare King in Shatranj (loses) or Losers (wins) */
7645                 if( nrW == 1 || nrB == 1) {
7646                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7647                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7648                      if(canAdjudicate && appData.checkMates) {
7649                          if(engineOpponent)
7650                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7651                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7652                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7653                          return 1;
7654                      }
7655                   } else
7656                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7657                   {    /* bare King */
7658                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7659                         if(canAdjudicate && appData.checkMates) {
7660                             /* but only adjudicate if adjudication enabled */
7661                             if(engineOpponent)
7662                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7663                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7664                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7665                             return 1;
7666                         }
7667                   }
7668                 } else bare = 1;
7669
7670
7671             // don't wait for engine to announce game end if we can judge ourselves
7672             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7673               case MT_CHECK:
7674                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7675                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7676                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7677                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7678                             checkCnt++;
7679                         if(checkCnt >= 2) {
7680                             reason = "Xboard adjudication: 3rd check";
7681                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7682                             break;
7683                         }
7684                     }
7685                 }
7686               case MT_NONE:
7687               default:
7688                 break;
7689               case MT_STALEMATE:
7690               case MT_STAINMATE:
7691                 reason = "Xboard adjudication: Stalemate";
7692                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7693                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7694                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7695                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7696                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7697                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7698                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7699                                                                         EP_CHECKMATE : EP_WINS);
7700                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7701                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7702                 }
7703                 break;
7704               case MT_CHECKMATE:
7705                 reason = "Xboard adjudication: Checkmate";
7706                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7707                 break;
7708             }
7709
7710                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7711                     case EP_STALEMATE:
7712                         result = GameIsDrawn; break;
7713                     case EP_CHECKMATE:
7714                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7715                     case EP_WINS:
7716                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7717                     default:
7718                         result = EndOfFile;
7719                 }
7720                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7721                     if(engineOpponent)
7722                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7723                     GameEnds( result, reason, GE_XBOARD );
7724                     return 1;
7725                 }
7726
7727                 /* Next absolutely insufficient mating material. */
7728                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7729                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7730                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7731
7732                      /* always flag draws, for judging claims */
7733                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7734
7735                      if(canAdjudicate && appData.materialDraws) {
7736                          /* but only adjudicate them if adjudication enabled */
7737                          if(engineOpponent) {
7738                            SendToProgram("force\n", engineOpponent); // suppress reply
7739                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7740                          }
7741                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7742                          return 1;
7743                      }
7744                 }
7745
7746                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7747                 if(gameInfo.variant == VariantXiangqi ?
7748                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7749                  : nrW + nrB == 4 &&
7750                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7751                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7752                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7753                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7754                    ) ) {
7755                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7756                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7757                           if(engineOpponent) {
7758                             SendToProgram("force\n", engineOpponent); // suppress reply
7759                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7760                           }
7761                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7762                           return 1;
7763                      }
7764                 } else moveCount = 6;
7765             }
7766
7767         // Repetition draws and 50-move rule can be applied independently of legality testing
7768
7769                 /* Check for rep-draws */
7770                 count = 0;
7771                 for(k = forwardMostMove-2;
7772                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7773                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7774                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7775                     k-=2)
7776                 {   int rights=0;
7777                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7778                         /* compare castling rights */
7779                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7780                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7781                                 rights++; /* King lost rights, while rook still had them */
7782                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7783                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7784                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7785                                    rights++; /* but at least one rook lost them */
7786                         }
7787                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7788                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7789                                 rights++;
7790                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7791                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7792                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7793                                    rights++;
7794                         }
7795                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7796                             && appData.drawRepeats > 1) {
7797                              /* adjudicate after user-specified nr of repeats */
7798                              int result = GameIsDrawn;
7799                              char *details = "XBoard adjudication: repetition draw";
7800                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7801                                 // [HGM] xiangqi: check for forbidden perpetuals
7802                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7803                                 for(m=forwardMostMove; m>k; m-=2) {
7804                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7805                                         ourPerpetual = 0; // the current mover did not always check
7806                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7807                                         hisPerpetual = 0; // the opponent did not always check
7808                                 }
7809                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7810                                                                         ourPerpetual, hisPerpetual);
7811                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7812                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7813                                     details = "Xboard adjudication: perpetual checking";
7814                                 } else
7815                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7816                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7817                                 } else
7818                                 // Now check for perpetual chases
7819                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7820                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7821                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7822                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7823                                         static char resdet[MSG_SIZ];
7824                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7825                                         details = resdet;
7826                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7827                                     } else
7828                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7829                                         break; // Abort repetition-checking loop.
7830                                 }
7831                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7832                              }
7833                              if(engineOpponent) {
7834                                SendToProgram("force\n", engineOpponent); // suppress reply
7835                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7836                              }
7837                              GameEnds( result, details, GE_XBOARD );
7838                              return 1;
7839                         }
7840                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7841                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7842                     }
7843                 }
7844
7845                 /* Now we test for 50-move draws. Determine ply count */
7846                 count = forwardMostMove;
7847                 /* look for last irreversble move */
7848                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7849                     count--;
7850                 /* if we hit starting position, add initial plies */
7851                 if( count == backwardMostMove )
7852                     count -= initialRulePlies;
7853                 count = forwardMostMove - count;
7854                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7855                         // adjust reversible move counter for checks in Xiangqi
7856                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7857                         if(i < backwardMostMove) i = backwardMostMove;
7858                         while(i <= forwardMostMove) {
7859                                 lastCheck = inCheck; // check evasion does not count
7860                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7861                                 if(inCheck || lastCheck) count--; // check does not count
7862                                 i++;
7863                         }
7864                 }
7865                 if( count >= 100)
7866                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7867                          /* this is used to judge if draw claims are legal */
7868                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7869                          if(engineOpponent) {
7870                            SendToProgram("force\n", engineOpponent); // suppress reply
7871                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7872                          }
7873                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7874                          return 1;
7875                 }
7876
7877                 /* if draw offer is pending, treat it as a draw claim
7878                  * when draw condition present, to allow engines a way to
7879                  * claim draws before making their move to avoid a race
7880                  * condition occurring after their move
7881                  */
7882                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7883                          char *p = NULL;
7884                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7885                              p = "Draw claim: 50-move rule";
7886                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7887                              p = "Draw claim: 3-fold repetition";
7888                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7889                              p = "Draw claim: insufficient mating material";
7890                          if( p != NULL && canAdjudicate) {
7891                              if(engineOpponent) {
7892                                SendToProgram("force\n", engineOpponent); // suppress reply
7893                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7894                              }
7895                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7896                              return 1;
7897                          }
7898                 }
7899
7900                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7901                     if(engineOpponent) {
7902                       SendToProgram("force\n", engineOpponent); // suppress reply
7903                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7904                     }
7905                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7906                     return 1;
7907                 }
7908         return 0;
7909 }
7910
7911 char *
7912 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7913 {   // [HGM] book: this routine intercepts moves to simulate book replies
7914     char *bookHit = NULL;
7915
7916     //first determine if the incoming move brings opponent into his book
7917     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7918         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7919     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7920     if(bookHit != NULL && !cps->bookSuspend) {
7921         // make sure opponent is not going to reply after receiving move to book position
7922         SendToProgram("force\n", cps);
7923         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7924     }
7925     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7926     // now arrange restart after book miss
7927     if(bookHit) {
7928         // after a book hit we never send 'go', and the code after the call to this routine
7929         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7930         char buf[MSG_SIZ], *move = bookHit;
7931         if(cps->useSAN) {
7932             int fromX, fromY, toX, toY;
7933             char promoChar;
7934             ChessMove moveType;
7935             move = buf + 30;
7936             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7937                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7938                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7939                                     PosFlags(forwardMostMove),
7940                                     fromY, fromX, toY, toX, promoChar, move);
7941             } else {
7942                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7943                 bookHit = NULL;
7944             }
7945         }
7946         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7947         SendToProgram(buf, cps);
7948         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7949     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7950         SendToProgram("go\n", cps);
7951         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7952     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7953         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7954             SendToProgram("go\n", cps);
7955         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7956     }
7957     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7958 }
7959
7960 int
7961 LoadError (char *errmess, ChessProgramState *cps)
7962 {   // unloads engine and switches back to -ncp mode if it was first
7963     if(cps->initDone) return FALSE;
7964     cps->isr = NULL; // this should suppress further error popups from breaking pipes
7965     DestroyChildProcess(cps->pr, 9 ); // just to be sure
7966     cps->pr = NoProc; 
7967     if(cps == &first) {
7968         appData.noChessProgram = TRUE;
7969         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
7970         gameMode = BeginningOfGame; ModeHighlight();
7971         SetNCPMode();
7972     }
7973     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
7974     DisplayMessage("", ""); // erase waiting message
7975     if(errmess) DisplayError(errmess, 0); // announce reason, if given
7976     return TRUE;
7977 }
7978
7979 char *savedMessage;
7980 ChessProgramState *savedState;
7981 void
7982 DeferredBookMove (void)
7983 {
7984         if(savedState->lastPing != savedState->lastPong)
7985                     ScheduleDelayedEvent(DeferredBookMove, 10);
7986         else
7987         HandleMachineMove(savedMessage, savedState);
7988 }
7989
7990 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7991
7992 void
7993 HandleMachineMove (char *message, ChessProgramState *cps)
7994 {
7995     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7996     char realname[MSG_SIZ];
7997     int fromX, fromY, toX, toY;
7998     ChessMove moveType;
7999     char promoChar;
8000     char *p, *pv=buf1;
8001     int machineWhite, oldError;
8002     char *bookHit;
8003
8004     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8005         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8006         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8007             DisplayError(_("Invalid pairing from pairing engine"), 0);
8008             return;
8009         }
8010         pairingReceived = 1;
8011         NextMatchGame();
8012         return; // Skim the pairing messages here.
8013     }
8014
8015     oldError = cps->userError; cps->userError = 0;
8016
8017 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8018     /*
8019      * Kludge to ignore BEL characters
8020      */
8021     while (*message == '\007') message++;
8022
8023     /*
8024      * [HGM] engine debug message: ignore lines starting with '#' character
8025      */
8026     if(cps->debug && *message == '#') return;
8027
8028     /*
8029      * Look for book output
8030      */
8031     if (cps == &first && bookRequested) {
8032         if (message[0] == '\t' || message[0] == ' ') {
8033             /* Part of the book output is here; append it */
8034             strcat(bookOutput, message);
8035             strcat(bookOutput, "  \n");
8036             return;
8037         } else if (bookOutput[0] != NULLCHAR) {
8038             /* All of book output has arrived; display it */
8039             char *p = bookOutput;
8040             while (*p != NULLCHAR) {
8041                 if (*p == '\t') *p = ' ';
8042                 p++;
8043             }
8044             DisplayInformation(bookOutput);
8045             bookRequested = FALSE;
8046             /* Fall through to parse the current output */
8047         }
8048     }
8049
8050     /*
8051      * Look for machine move.
8052      */
8053     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8054         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8055     {
8056         /* This method is only useful on engines that support ping */
8057         if (cps->lastPing != cps->lastPong) {
8058           if (gameMode == BeginningOfGame) {
8059             /* Extra move from before last new; ignore */
8060             if (appData.debugMode) {
8061                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8062             }
8063           } else {
8064             if (appData.debugMode) {
8065                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8066                         cps->which, gameMode);
8067             }
8068
8069             SendToProgram("undo\n", cps);
8070           }
8071           return;
8072         }
8073
8074         switch (gameMode) {
8075           case BeginningOfGame:
8076             /* Extra move from before last reset; ignore */
8077             if (appData.debugMode) {
8078                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8079             }
8080             return;
8081
8082           case EndOfGame:
8083           case IcsIdle:
8084           default:
8085             /* Extra move after we tried to stop.  The mode test is
8086                not a reliable way of detecting this problem, but it's
8087                the best we can do on engines that don't support ping.
8088             */
8089             if (appData.debugMode) {
8090                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8091                         cps->which, gameMode);
8092             }
8093             SendToProgram("undo\n", cps);
8094             return;
8095
8096           case MachinePlaysWhite:
8097           case IcsPlayingWhite:
8098             machineWhite = TRUE;
8099             break;
8100
8101           case MachinePlaysBlack:
8102           case IcsPlayingBlack:
8103             machineWhite = FALSE;
8104             break;
8105
8106           case TwoMachinesPlay:
8107             machineWhite = (cps->twoMachinesColor[0] == 'w');
8108             break;
8109         }
8110         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8111             if (appData.debugMode) {
8112                 fprintf(debugFP,
8113                         "Ignoring move out of turn by %s, gameMode %d"
8114                         ", forwardMost %d\n",
8115                         cps->which, gameMode, forwardMostMove);
8116             }
8117             return;
8118         }
8119
8120         if(cps->alphaRank) AlphaRank(machineMove, 4);
8121         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8122                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8123             /* Machine move could not be parsed; ignore it. */
8124           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8125                     machineMove, _(cps->which));
8126             DisplayError(buf1, 0);
8127             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8128                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8129             if (gameMode == TwoMachinesPlay) {
8130               GameEnds(machineWhite ? BlackWins : WhiteWins,
8131                        buf1, GE_XBOARD);
8132             }
8133             return;
8134         }
8135
8136         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8137         /* So we have to redo legality test with true e.p. status here,  */
8138         /* to make sure an illegal e.p. capture does not slip through,   */
8139         /* to cause a forfeit on a justified illegal-move complaint      */
8140         /* of the opponent.                                              */
8141         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8142            ChessMove moveType;
8143            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8144                              fromY, fromX, toY, toX, promoChar);
8145             if(moveType == IllegalMove) {
8146               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8147                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8148                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8149                            buf1, GE_XBOARD);
8150                 return;
8151            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8152            /* [HGM] Kludge to handle engines that send FRC-style castling
8153               when they shouldn't (like TSCP-Gothic) */
8154            switch(moveType) {
8155              case WhiteASideCastleFR:
8156              case BlackASideCastleFR:
8157                toX+=2;
8158                currentMoveString[2]++;
8159                break;
8160              case WhiteHSideCastleFR:
8161              case BlackHSideCastleFR:
8162                toX--;
8163                currentMoveString[2]--;
8164                break;
8165              default: ; // nothing to do, but suppresses warning of pedantic compilers
8166            }
8167         }
8168         hintRequested = FALSE;
8169         lastHint[0] = NULLCHAR;
8170         bookRequested = FALSE;
8171         /* Program may be pondering now */
8172         cps->maybeThinking = TRUE;
8173         if (cps->sendTime == 2) cps->sendTime = 1;
8174         if (cps->offeredDraw) cps->offeredDraw--;
8175
8176         /* [AS] Save move info*/
8177         pvInfoList[ forwardMostMove ].score = programStats.score;
8178         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8179         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8180
8181         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8182
8183         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8184         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8185             int count = 0;
8186
8187             while( count < adjudicateLossPlies ) {
8188                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8189
8190                 if( count & 1 ) {
8191                     score = -score; /* Flip score for winning side */
8192                 }
8193
8194                 if( score > adjudicateLossThreshold ) {
8195                     break;
8196                 }
8197
8198                 count++;
8199             }
8200
8201             if( count >= adjudicateLossPlies ) {
8202                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8203
8204                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8205                     "Xboard adjudication",
8206                     GE_XBOARD );
8207
8208                 return;
8209             }
8210         }
8211
8212         if(Adjudicate(cps)) {
8213             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8214             return; // [HGM] adjudicate: for all automatic game ends
8215         }
8216
8217 #if ZIPPY
8218         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8219             first.initDone) {
8220           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8221                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8222                 SendToICS("draw ");
8223                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8224           }
8225           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8226           ics_user_moved = 1;
8227           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8228                 char buf[3*MSG_SIZ];
8229
8230                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8231                         programStats.score / 100.,
8232                         programStats.depth,
8233                         programStats.time / 100.,
8234                         (unsigned int)programStats.nodes,
8235                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8236                         programStats.movelist);
8237                 SendToICS(buf);
8238 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8239           }
8240         }
8241 #endif
8242
8243         /* [AS] Clear stats for next move */
8244         ClearProgramStats();
8245         thinkOutput[0] = NULLCHAR;
8246         hiddenThinkOutputState = 0;
8247
8248         bookHit = NULL;
8249         if (gameMode == TwoMachinesPlay) {
8250             /* [HGM] relaying draw offers moved to after reception of move */
8251             /* and interpreting offer as claim if it brings draw condition */
8252             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8253                 SendToProgram("draw\n", cps->other);
8254             }
8255             if (cps->other->sendTime) {
8256                 SendTimeRemaining(cps->other,
8257                                   cps->other->twoMachinesColor[0] == 'w');
8258             }
8259             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8260             if (firstMove && !bookHit) {
8261                 firstMove = FALSE;
8262                 if (cps->other->useColors) {
8263                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8264                 }
8265                 SendToProgram("go\n", cps->other);
8266             }
8267             cps->other->maybeThinking = TRUE;
8268         }
8269
8270         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8271
8272         if (!pausing && appData.ringBellAfterMoves) {
8273             RingBell();
8274         }
8275
8276         /*
8277          * Reenable menu items that were disabled while
8278          * machine was thinking
8279          */
8280         if (gameMode != TwoMachinesPlay)
8281             SetUserThinkingEnables();
8282
8283         // [HGM] book: after book hit opponent has received move and is now in force mode
8284         // force the book reply into it, and then fake that it outputted this move by jumping
8285         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8286         if(bookHit) {
8287                 static char bookMove[MSG_SIZ]; // a bit generous?
8288
8289                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8290                 strcat(bookMove, bookHit);
8291                 message = bookMove;
8292                 cps = cps->other;
8293                 programStats.nodes = programStats.depth = programStats.time =
8294                 programStats.score = programStats.got_only_move = 0;
8295                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8296
8297                 if(cps->lastPing != cps->lastPong) {
8298                     savedMessage = message; // args for deferred call
8299                     savedState = cps;
8300                     ScheduleDelayedEvent(DeferredBookMove, 10);
8301                     return;
8302                 }
8303                 goto FakeBookMove;
8304         }
8305
8306         return;
8307     }
8308
8309     /* Set special modes for chess engines.  Later something general
8310      *  could be added here; for now there is just one kludge feature,
8311      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8312      *  when "xboard" is given as an interactive command.
8313      */
8314     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8315         cps->useSigint = FALSE;
8316         cps->useSigterm = FALSE;
8317     }
8318     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8319       ParseFeatures(message+8, cps);
8320       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8321     }
8322
8323     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8324                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8325       int dummy, s=6; char buf[MSG_SIZ];
8326       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8327       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8328       if(startedFromSetupPosition) return;
8329       ParseFEN(boards[0], &dummy, message+s);
8330       DrawPosition(TRUE, boards[0]);
8331       startedFromSetupPosition = TRUE;
8332       return;
8333     }
8334     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8335      * want this, I was asked to put it in, and obliged.
8336      */
8337     if (!strncmp(message, "setboard ", 9)) {
8338         Board initial_position;
8339
8340         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8341
8342         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8343             DisplayError(_("Bad FEN received from engine"), 0);
8344             return ;
8345         } else {
8346            Reset(TRUE, FALSE);
8347            CopyBoard(boards[0], initial_position);
8348            initialRulePlies = FENrulePlies;
8349            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8350            else gameMode = MachinePlaysBlack;
8351            DrawPosition(FALSE, boards[currentMove]);
8352         }
8353         return;
8354     }
8355
8356     /*
8357      * Look for communication commands
8358      */
8359     if (!strncmp(message, "telluser ", 9)) {
8360         if(message[9] == '\\' && message[10] == '\\')
8361             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8362         PlayTellSound();
8363         DisplayNote(message + 9);
8364         return;
8365     }
8366     if (!strncmp(message, "tellusererror ", 14)) {
8367         cps->userError = 1;
8368         if(message[14] == '\\' && message[15] == '\\')
8369             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8370         PlayTellSound();
8371         DisplayError(message + 14, 0);
8372         return;
8373     }
8374     if (!strncmp(message, "tellopponent ", 13)) {
8375       if (appData.icsActive) {
8376         if (loggedOn) {
8377           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8378           SendToICS(buf1);
8379         }
8380       } else {
8381         DisplayNote(message + 13);
8382       }
8383       return;
8384     }
8385     if (!strncmp(message, "tellothers ", 11)) {
8386       if (appData.icsActive) {
8387         if (loggedOn) {
8388           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8389           SendToICS(buf1);
8390         }
8391       }
8392       return;
8393     }
8394     if (!strncmp(message, "tellall ", 8)) {
8395       if (appData.icsActive) {
8396         if (loggedOn) {
8397           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8398           SendToICS(buf1);
8399         }
8400       } else {
8401         DisplayNote(message + 8);
8402       }
8403       return;
8404     }
8405     if (strncmp(message, "warning", 7) == 0) {
8406         /* Undocumented feature, use tellusererror in new code */
8407         DisplayError(message, 0);
8408         return;
8409     }
8410     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8411         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8412         strcat(realname, " query");
8413         AskQuestion(realname, buf2, buf1, cps->pr);
8414         return;
8415     }
8416     /* Commands from the engine directly to ICS.  We don't allow these to be
8417      *  sent until we are logged on. Crafty kibitzes have been known to
8418      *  interfere with the login process.
8419      */
8420     if (loggedOn) {
8421         if (!strncmp(message, "tellics ", 8)) {
8422             SendToICS(message + 8);
8423             SendToICS("\n");
8424             return;
8425         }
8426         if (!strncmp(message, "tellicsnoalias ", 15)) {
8427             SendToICS(ics_prefix);
8428             SendToICS(message + 15);
8429             SendToICS("\n");
8430             return;
8431         }
8432         /* The following are for backward compatibility only */
8433         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8434             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8435             SendToICS(ics_prefix);
8436             SendToICS(message);
8437             SendToICS("\n");
8438             return;
8439         }
8440     }
8441     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8442         return;
8443     }
8444     /*
8445      * If the move is illegal, cancel it and redraw the board.
8446      * Also deal with other error cases.  Matching is rather loose
8447      * here to accommodate engines written before the spec.
8448      */
8449     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8450         strncmp(message, "Error", 5) == 0) {
8451         if (StrStr(message, "name") ||
8452             StrStr(message, "rating") || StrStr(message, "?") ||
8453             StrStr(message, "result") || StrStr(message, "board") ||
8454             StrStr(message, "bk") || StrStr(message, "computer") ||
8455             StrStr(message, "variant") || StrStr(message, "hint") ||
8456             StrStr(message, "random") || StrStr(message, "depth") ||
8457             StrStr(message, "accepted")) {
8458             return;
8459         }
8460         if (StrStr(message, "protover")) {
8461           /* Program is responding to input, so it's apparently done
8462              initializing, and this error message indicates it is
8463              protocol version 1.  So we don't need to wait any longer
8464              for it to initialize and send feature commands. */
8465           FeatureDone(cps, 1);
8466           cps->protocolVersion = 1;
8467           return;
8468         }
8469         cps->maybeThinking = FALSE;
8470
8471         if (StrStr(message, "draw")) {
8472             /* Program doesn't have "draw" command */
8473             cps->sendDrawOffers = 0;
8474             return;
8475         }
8476         if (cps->sendTime != 1 &&
8477             (StrStr(message, "time") || StrStr(message, "otim"))) {
8478           /* Program apparently doesn't have "time" or "otim" command */
8479           cps->sendTime = 0;
8480           return;
8481         }
8482         if (StrStr(message, "analyze")) {
8483             cps->analysisSupport = FALSE;
8484             cps->analyzing = FALSE;
8485 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8486             EditGameEvent(); // [HGM] try to preserve loaded game
8487             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8488             DisplayError(buf2, 0);
8489             return;
8490         }
8491         if (StrStr(message, "(no matching move)st")) {
8492           /* Special kludge for GNU Chess 4 only */
8493           cps->stKludge = TRUE;
8494           SendTimeControl(cps, movesPerSession, timeControl,
8495                           timeIncrement, appData.searchDepth,
8496                           searchTime);
8497           return;
8498         }
8499         if (StrStr(message, "(no matching move)sd")) {
8500           /* Special kludge for GNU Chess 4 only */
8501           cps->sdKludge = TRUE;
8502           SendTimeControl(cps, movesPerSession, timeControl,
8503                           timeIncrement, appData.searchDepth,
8504                           searchTime);
8505           return;
8506         }
8507         if (!StrStr(message, "llegal")) {
8508             return;
8509         }
8510         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8511             gameMode == IcsIdle) return;
8512         if (forwardMostMove <= backwardMostMove) return;
8513         if (pausing) PauseEvent();
8514       if(appData.forceIllegal) {
8515             // [HGM] illegal: machine refused move; force position after move into it
8516           SendToProgram("force\n", cps);
8517           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8518                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8519                 // when black is to move, while there might be nothing on a2 or black
8520                 // might already have the move. So send the board as if white has the move.
8521                 // But first we must change the stm of the engine, as it refused the last move
8522                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8523                 if(WhiteOnMove(forwardMostMove)) {
8524                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8525                     SendBoard(cps, forwardMostMove); // kludgeless board
8526                 } else {
8527                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8528                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8529                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8530                 }
8531           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8532             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8533                  gameMode == TwoMachinesPlay)
8534               SendToProgram("go\n", cps);
8535             return;
8536       } else
8537         if (gameMode == PlayFromGameFile) {
8538             /* Stop reading this game file */
8539             gameMode = EditGame;
8540             ModeHighlight();
8541         }
8542         /* [HGM] illegal-move claim should forfeit game when Xboard */
8543         /* only passes fully legal moves                            */
8544         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8545             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8546                                 "False illegal-move claim", GE_XBOARD );
8547             return; // do not take back move we tested as valid
8548         }
8549         currentMove = forwardMostMove-1;
8550         DisplayMove(currentMove-1); /* before DisplayMoveError */
8551         SwitchClocks(forwardMostMove-1); // [HGM] race
8552         DisplayBothClocks();
8553         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8554                 parseList[currentMove], _(cps->which));
8555         DisplayMoveError(buf1);
8556         DrawPosition(FALSE, boards[currentMove]);
8557
8558         SetUserThinkingEnables();
8559         return;
8560     }
8561     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8562         /* Program has a broken "time" command that
8563            outputs a string not ending in newline.
8564            Don't use it. */
8565         cps->sendTime = 0;
8566     }
8567
8568     /*
8569      * If chess program startup fails, exit with an error message.
8570      * Attempts to recover here are futile. [HGM] Well, we try anyway
8571      */
8572     if ((StrStr(message, "unknown host") != NULL)
8573         || (StrStr(message, "No remote directory") != NULL)
8574         || (StrStr(message, "not found") != NULL)
8575         || (StrStr(message, "No such file") != NULL)
8576         || (StrStr(message, "can't alloc") != NULL)
8577         || (StrStr(message, "Permission denied") != NULL)) {
8578
8579         cps->maybeThinking = FALSE;
8580         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8581                 _(cps->which), cps->program, cps->host, message);
8582         RemoveInputSource(cps->isr);
8583         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8584             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8585             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8586         }
8587         return;
8588     }
8589
8590     /*
8591      * Look for hint output
8592      */
8593     if (sscanf(message, "Hint: %s", buf1) == 1) {
8594         if (cps == &first && hintRequested) {
8595             hintRequested = FALSE;
8596             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8597                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8598                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8599                                     PosFlags(forwardMostMove),
8600                                     fromY, fromX, toY, toX, promoChar, buf1);
8601                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8602                 DisplayInformation(buf2);
8603             } else {
8604                 /* Hint move could not be parsed!? */
8605               snprintf(buf2, sizeof(buf2),
8606                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8607                         buf1, _(cps->which));
8608                 DisplayError(buf2, 0);
8609             }
8610         } else {
8611           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8612         }
8613         return;
8614     }
8615
8616     /*
8617      * Ignore other messages if game is not in progress
8618      */
8619     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8620         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8621
8622     /*
8623      * look for win, lose, draw, or draw offer
8624      */
8625     if (strncmp(message, "1-0", 3) == 0) {
8626         char *p, *q, *r = "";
8627         p = strchr(message, '{');
8628         if (p) {
8629             q = strchr(p, '}');
8630             if (q) {
8631                 *q = NULLCHAR;
8632                 r = p + 1;
8633             }
8634         }
8635         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8636         return;
8637     } else if (strncmp(message, "0-1", 3) == 0) {
8638         char *p, *q, *r = "";
8639         p = strchr(message, '{');
8640         if (p) {
8641             q = strchr(p, '}');
8642             if (q) {
8643                 *q = NULLCHAR;
8644                 r = p + 1;
8645             }
8646         }
8647         /* Kludge for Arasan 4.1 bug */
8648         if (strcmp(r, "Black resigns") == 0) {
8649             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8650             return;
8651         }
8652         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8653         return;
8654     } else if (strncmp(message, "1/2", 3) == 0) {
8655         char *p, *q, *r = "";
8656         p = strchr(message, '{');
8657         if (p) {
8658             q = strchr(p, '}');
8659             if (q) {
8660                 *q = NULLCHAR;
8661                 r = p + 1;
8662             }
8663         }
8664
8665         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8666         return;
8667
8668     } else if (strncmp(message, "White resign", 12) == 0) {
8669         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8670         return;
8671     } else if (strncmp(message, "Black resign", 12) == 0) {
8672         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8673         return;
8674     } else if (strncmp(message, "White matches", 13) == 0 ||
8675                strncmp(message, "Black matches", 13) == 0   ) {
8676         /* [HGM] ignore GNUShogi noises */
8677         return;
8678     } else if (strncmp(message, "White", 5) == 0 &&
8679                message[5] != '(' &&
8680                StrStr(message, "Black") == NULL) {
8681         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8682         return;
8683     } else if (strncmp(message, "Black", 5) == 0 &&
8684                message[5] != '(') {
8685         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8686         return;
8687     } else if (strcmp(message, "resign") == 0 ||
8688                strcmp(message, "computer resigns") == 0) {
8689         switch (gameMode) {
8690           case MachinePlaysBlack:
8691           case IcsPlayingBlack:
8692             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8693             break;
8694           case MachinePlaysWhite:
8695           case IcsPlayingWhite:
8696             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8697             break;
8698           case TwoMachinesPlay:
8699             if (cps->twoMachinesColor[0] == 'w')
8700               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8701             else
8702               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8703             break;
8704           default:
8705             /* can't happen */
8706             break;
8707         }
8708         return;
8709     } else if (strncmp(message, "opponent mates", 14) == 0) {
8710         switch (gameMode) {
8711           case MachinePlaysBlack:
8712           case IcsPlayingBlack:
8713             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8714             break;
8715           case MachinePlaysWhite:
8716           case IcsPlayingWhite:
8717             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8718             break;
8719           case TwoMachinesPlay:
8720             if (cps->twoMachinesColor[0] == 'w')
8721               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8722             else
8723               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8724             break;
8725           default:
8726             /* can't happen */
8727             break;
8728         }
8729         return;
8730     } else if (strncmp(message, "computer mates", 14) == 0) {
8731         switch (gameMode) {
8732           case MachinePlaysBlack:
8733           case IcsPlayingBlack:
8734             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8735             break;
8736           case MachinePlaysWhite:
8737           case IcsPlayingWhite:
8738             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8739             break;
8740           case TwoMachinesPlay:
8741             if (cps->twoMachinesColor[0] == 'w')
8742               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8743             else
8744               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8745             break;
8746           default:
8747             /* can't happen */
8748             break;
8749         }
8750         return;
8751     } else if (strncmp(message, "checkmate", 9) == 0) {
8752         if (WhiteOnMove(forwardMostMove)) {
8753             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8754         } else {
8755             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8756         }
8757         return;
8758     } else if (strstr(message, "Draw") != NULL ||
8759                strstr(message, "game is a draw") != NULL) {
8760         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8761         return;
8762     } else if (strstr(message, "offer") != NULL &&
8763                strstr(message, "draw") != NULL) {
8764 #if ZIPPY
8765         if (appData.zippyPlay && first.initDone) {
8766             /* Relay offer to ICS */
8767             SendToICS(ics_prefix);
8768             SendToICS("draw\n");
8769         }
8770 #endif
8771         cps->offeredDraw = 2; /* valid until this engine moves twice */
8772         if (gameMode == TwoMachinesPlay) {
8773             if (cps->other->offeredDraw) {
8774                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8775             /* [HGM] in two-machine mode we delay relaying draw offer      */
8776             /* until after we also have move, to see if it is really claim */
8777             }
8778         } else if (gameMode == MachinePlaysWhite ||
8779                    gameMode == MachinePlaysBlack) {
8780           if (userOfferedDraw) {
8781             DisplayInformation(_("Machine accepts your draw offer"));
8782             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8783           } else {
8784             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8785           }
8786         }
8787     }
8788
8789
8790     /*
8791      * Look for thinking output
8792      */
8793     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8794           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8795                                 ) {
8796         int plylev, mvleft, mvtot, curscore, time;
8797         char mvname[MOVE_LEN];
8798         u64 nodes; // [DM]
8799         char plyext;
8800         int ignore = FALSE;
8801         int prefixHint = FALSE;
8802         mvname[0] = NULLCHAR;
8803
8804         switch (gameMode) {
8805           case MachinePlaysBlack:
8806           case IcsPlayingBlack:
8807             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8808             break;
8809           case MachinePlaysWhite:
8810           case IcsPlayingWhite:
8811             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8812             break;
8813           case AnalyzeMode:
8814           case AnalyzeFile:
8815             break;
8816           case IcsObserving: /* [DM] icsEngineAnalyze */
8817             if (!appData.icsEngineAnalyze) ignore = TRUE;
8818             break;
8819           case TwoMachinesPlay:
8820             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8821                 ignore = TRUE;
8822             }
8823             break;
8824           default:
8825             ignore = TRUE;
8826             break;
8827         }
8828
8829         if (!ignore) {
8830             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8831             buf1[0] = NULLCHAR;
8832             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8833                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8834
8835                 if (plyext != ' ' && plyext != '\t') {
8836                     time *= 100;
8837                 }
8838
8839                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8840                 if( cps->scoreIsAbsolute &&
8841                     ( gameMode == MachinePlaysBlack ||
8842                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8843                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8844                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8845                      !WhiteOnMove(currentMove)
8846                     ) )
8847                 {
8848                     curscore = -curscore;
8849                 }
8850
8851                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8852
8853                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8854                         char buf[MSG_SIZ];
8855                         FILE *f;
8856                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8857                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8858                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8859                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8860                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8861                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8862                                 fclose(f);
8863                         } else DisplayError(_("failed writing PV"), 0);
8864                 }
8865
8866                 tempStats.depth = plylev;
8867                 tempStats.nodes = nodes;
8868                 tempStats.time = time;
8869                 tempStats.score = curscore;
8870                 tempStats.got_only_move = 0;
8871
8872                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8873                         int ticklen;
8874
8875                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8876                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8877                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8878                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8879                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8880                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8881                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8882                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8883                 }
8884
8885                 /* Buffer overflow protection */
8886                 if (pv[0] != NULLCHAR) {
8887                     if (strlen(pv) >= sizeof(tempStats.movelist)
8888                         && appData.debugMode) {
8889                         fprintf(debugFP,
8890                                 "PV is too long; using the first %u bytes.\n",
8891                                 (unsigned) sizeof(tempStats.movelist) - 1);
8892                     }
8893
8894                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8895                 } else {
8896                     sprintf(tempStats.movelist, " no PV\n");
8897                 }
8898
8899                 if (tempStats.seen_stat) {
8900                     tempStats.ok_to_send = 1;
8901                 }
8902
8903                 if (strchr(tempStats.movelist, '(') != NULL) {
8904                     tempStats.line_is_book = 1;
8905                     tempStats.nr_moves = 0;
8906                     tempStats.moves_left = 0;
8907                 } else {
8908                     tempStats.line_is_book = 0;
8909                 }
8910
8911                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8912                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8913
8914                 SendProgramStatsToFrontend( cps, &tempStats );
8915
8916                 /*
8917                     [AS] Protect the thinkOutput buffer from overflow... this
8918                     is only useful if buf1 hasn't overflowed first!
8919                 */
8920                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8921                          plylev,
8922                          (gameMode == TwoMachinesPlay ?
8923                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8924                          ((double) curscore) / 100.0,
8925                          prefixHint ? lastHint : "",
8926                          prefixHint ? " " : "" );
8927
8928                 if( buf1[0] != NULLCHAR ) {
8929                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8930
8931                     if( strlen(pv) > max_len ) {
8932                         if( appData.debugMode) {
8933                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8934                         }
8935                         pv[max_len+1] = '\0';
8936                     }
8937
8938                     strcat( thinkOutput, pv);
8939                 }
8940
8941                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8942                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8943                     DisplayMove(currentMove - 1);
8944                 }
8945                 return;
8946
8947             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8948                 /* crafty (9.25+) says "(only move) <move>"
8949                  * if there is only 1 legal move
8950                  */
8951                 sscanf(p, "(only move) %s", buf1);
8952                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8953                 sprintf(programStats.movelist, "%s (only move)", buf1);
8954                 programStats.depth = 1;
8955                 programStats.nr_moves = 1;
8956                 programStats.moves_left = 1;
8957                 programStats.nodes = 1;
8958                 programStats.time = 1;
8959                 programStats.got_only_move = 1;
8960
8961                 /* Not really, but we also use this member to
8962                    mean "line isn't going to change" (Crafty
8963                    isn't searching, so stats won't change) */
8964                 programStats.line_is_book = 1;
8965
8966                 SendProgramStatsToFrontend( cps, &programStats );
8967
8968                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8969                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8970                     DisplayMove(currentMove - 1);
8971                 }
8972                 return;
8973             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8974                               &time, &nodes, &plylev, &mvleft,
8975                               &mvtot, mvname) >= 5) {
8976                 /* The stat01: line is from Crafty (9.29+) in response
8977                    to the "." command */
8978                 programStats.seen_stat = 1;
8979                 cps->maybeThinking = TRUE;
8980
8981                 if (programStats.got_only_move || !appData.periodicUpdates)
8982                   return;
8983
8984                 programStats.depth = plylev;
8985                 programStats.time = time;
8986                 programStats.nodes = nodes;
8987                 programStats.moves_left = mvleft;
8988                 programStats.nr_moves = mvtot;
8989                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8990                 programStats.ok_to_send = 1;
8991                 programStats.movelist[0] = '\0';
8992
8993                 SendProgramStatsToFrontend( cps, &programStats );
8994
8995                 return;
8996
8997             } else if (strncmp(message,"++",2) == 0) {
8998                 /* Crafty 9.29+ outputs this */
8999                 programStats.got_fail = 2;
9000                 return;
9001
9002             } else if (strncmp(message,"--",2) == 0) {
9003                 /* Crafty 9.29+ outputs this */
9004                 programStats.got_fail = 1;
9005                 return;
9006
9007             } else if (thinkOutput[0] != NULLCHAR &&
9008                        strncmp(message, "    ", 4) == 0) {
9009                 unsigned message_len;
9010
9011                 p = message;
9012                 while (*p && *p == ' ') p++;
9013
9014                 message_len = strlen( p );
9015
9016                 /* [AS] Avoid buffer overflow */
9017                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9018                     strcat(thinkOutput, " ");
9019                     strcat(thinkOutput, p);
9020                 }
9021
9022                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9023                     strcat(programStats.movelist, " ");
9024                     strcat(programStats.movelist, p);
9025                 }
9026
9027                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9028                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9029                     DisplayMove(currentMove - 1);
9030                 }
9031                 return;
9032             }
9033         }
9034         else {
9035             buf1[0] = NULLCHAR;
9036
9037             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9038                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9039             {
9040                 ChessProgramStats cpstats;
9041
9042                 if (plyext != ' ' && plyext != '\t') {
9043                     time *= 100;
9044                 }
9045
9046                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9047                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9048                     curscore = -curscore;
9049                 }
9050
9051                 cpstats.depth = plylev;
9052                 cpstats.nodes = nodes;
9053                 cpstats.time = time;
9054                 cpstats.score = curscore;
9055                 cpstats.got_only_move = 0;
9056                 cpstats.movelist[0] = '\0';
9057
9058                 if (buf1[0] != NULLCHAR) {
9059                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9060                 }
9061
9062                 cpstats.ok_to_send = 0;
9063                 cpstats.line_is_book = 0;
9064                 cpstats.nr_moves = 0;
9065                 cpstats.moves_left = 0;
9066
9067                 SendProgramStatsToFrontend( cps, &cpstats );
9068             }
9069         }
9070     }
9071 }
9072
9073
9074 /* Parse a game score from the character string "game", and
9075    record it as the history of the current game.  The game
9076    score is NOT assumed to start from the standard position.
9077    The display is not updated in any way.
9078    */
9079 void
9080 ParseGameHistory (char *game)
9081 {
9082     ChessMove moveType;
9083     int fromX, fromY, toX, toY, boardIndex;
9084     char promoChar;
9085     char *p, *q;
9086     char buf[MSG_SIZ];
9087
9088     if (appData.debugMode)
9089       fprintf(debugFP, "Parsing game history: %s\n", game);
9090
9091     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9092     gameInfo.site = StrSave(appData.icsHost);
9093     gameInfo.date = PGNDate();
9094     gameInfo.round = StrSave("-");
9095
9096     /* Parse out names of players */
9097     while (*game == ' ') game++;
9098     p = buf;
9099     while (*game != ' ') *p++ = *game++;
9100     *p = NULLCHAR;
9101     gameInfo.white = StrSave(buf);
9102     while (*game == ' ') game++;
9103     p = buf;
9104     while (*game != ' ' && *game != '\n') *p++ = *game++;
9105     *p = NULLCHAR;
9106     gameInfo.black = StrSave(buf);
9107
9108     /* Parse moves */
9109     boardIndex = blackPlaysFirst ? 1 : 0;
9110     yynewstr(game);
9111     for (;;) {
9112         yyboardindex = boardIndex;
9113         moveType = (ChessMove) Myylex();
9114         switch (moveType) {
9115           case IllegalMove:             /* maybe suicide chess, etc. */
9116   if (appData.debugMode) {
9117     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9118     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9119     setbuf(debugFP, NULL);
9120   }
9121           case WhitePromotion:
9122           case BlackPromotion:
9123           case WhiteNonPromotion:
9124           case BlackNonPromotion:
9125           case NormalMove:
9126           case WhiteCapturesEnPassant:
9127           case BlackCapturesEnPassant:
9128           case WhiteKingSideCastle:
9129           case WhiteQueenSideCastle:
9130           case BlackKingSideCastle:
9131           case BlackQueenSideCastle:
9132           case WhiteKingSideCastleWild:
9133           case WhiteQueenSideCastleWild:
9134           case BlackKingSideCastleWild:
9135           case BlackQueenSideCastleWild:
9136           /* PUSH Fabien */
9137           case WhiteHSideCastleFR:
9138           case WhiteASideCastleFR:
9139           case BlackHSideCastleFR:
9140           case BlackASideCastleFR:
9141           /* POP Fabien */
9142             fromX = currentMoveString[0] - AAA;
9143             fromY = currentMoveString[1] - ONE;
9144             toX = currentMoveString[2] - AAA;
9145             toY = currentMoveString[3] - ONE;
9146             promoChar = currentMoveString[4];
9147             break;
9148           case WhiteDrop:
9149           case BlackDrop:
9150             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9151             fromX = moveType == WhiteDrop ?
9152               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9153             (int) CharToPiece(ToLower(currentMoveString[0]));
9154             fromY = DROP_RANK;
9155             toX = currentMoveString[2] - AAA;
9156             toY = currentMoveString[3] - ONE;
9157             promoChar = NULLCHAR;
9158             break;
9159           case AmbiguousMove:
9160             /* bug? */
9161             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9162   if (appData.debugMode) {
9163     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9164     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9165     setbuf(debugFP, NULL);
9166   }
9167             DisplayError(buf, 0);
9168             return;
9169           case ImpossibleMove:
9170             /* bug? */
9171             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9172   if (appData.debugMode) {
9173     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9174     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9175     setbuf(debugFP, NULL);
9176   }
9177             DisplayError(buf, 0);
9178             return;
9179           case EndOfFile:
9180             if (boardIndex < backwardMostMove) {
9181                 /* Oops, gap.  How did that happen? */
9182                 DisplayError(_("Gap in move list"), 0);
9183                 return;
9184             }
9185             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9186             if (boardIndex > forwardMostMove) {
9187                 forwardMostMove = boardIndex;
9188             }
9189             return;
9190           case ElapsedTime:
9191             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9192                 strcat(parseList[boardIndex-1], " ");
9193                 strcat(parseList[boardIndex-1], yy_text);
9194             }
9195             continue;
9196           case Comment:
9197           case PGNTag:
9198           case NAG:
9199           default:
9200             /* ignore */
9201             continue;
9202           case WhiteWins:
9203           case BlackWins:
9204           case GameIsDrawn:
9205           case GameUnfinished:
9206             if (gameMode == IcsExamining) {
9207                 if (boardIndex < backwardMostMove) {
9208                     /* Oops, gap.  How did that happen? */
9209                     return;
9210                 }
9211                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9212                 return;
9213             }
9214             gameInfo.result = moveType;
9215             p = strchr(yy_text, '{');
9216             if (p == NULL) p = strchr(yy_text, '(');
9217             if (p == NULL) {
9218                 p = yy_text;
9219                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9220             } else {
9221                 q = strchr(p, *p == '{' ? '}' : ')');
9222                 if (q != NULL) *q = NULLCHAR;
9223                 p++;
9224             }
9225             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9226             gameInfo.resultDetails = StrSave(p);
9227             continue;
9228         }
9229         if (boardIndex >= forwardMostMove &&
9230             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9231             backwardMostMove = blackPlaysFirst ? 1 : 0;
9232             return;
9233         }
9234         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9235                                  fromY, fromX, toY, toX, promoChar,
9236                                  parseList[boardIndex]);
9237         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9238         /* currentMoveString is set as a side-effect of yylex */
9239         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9240         strcat(moveList[boardIndex], "\n");
9241         boardIndex++;
9242         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9243         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9244           case MT_NONE:
9245           case MT_STALEMATE:
9246           default:
9247             break;
9248           case MT_CHECK:
9249             if(gameInfo.variant != VariantShogi)
9250                 strcat(parseList[boardIndex - 1], "+");
9251             break;
9252           case MT_CHECKMATE:
9253           case MT_STAINMATE:
9254             strcat(parseList[boardIndex - 1], "#");
9255             break;
9256         }
9257     }
9258 }
9259
9260
9261 /* Apply a move to the given board  */
9262 void
9263 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9264 {
9265   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9266   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9267
9268     /* [HGM] compute & store e.p. status and castling rights for new position */
9269     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9270
9271       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9272       oldEP = (signed char)board[EP_STATUS];
9273       board[EP_STATUS] = EP_NONE;
9274
9275   if (fromY == DROP_RANK) {
9276         /* must be first */
9277         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9278             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9279             return;
9280         }
9281         piece = board[toY][toX] = (ChessSquare) fromX;
9282   } else {
9283       int i;
9284
9285       if( board[toY][toX] != EmptySquare )
9286            board[EP_STATUS] = EP_CAPTURE;
9287
9288       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9289            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9290                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9291       } else
9292       if( board[fromY][fromX] == WhitePawn ) {
9293            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9294                board[EP_STATUS] = EP_PAWN_MOVE;
9295            if( toY-fromY==2) {
9296                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9297                         gameInfo.variant != VariantBerolina || toX < fromX)
9298                       board[EP_STATUS] = toX | berolina;
9299                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9300                         gameInfo.variant != VariantBerolina || toX > fromX)
9301                       board[EP_STATUS] = toX;
9302            }
9303       } else
9304       if( board[fromY][fromX] == BlackPawn ) {
9305            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9306                board[EP_STATUS] = EP_PAWN_MOVE;
9307            if( toY-fromY== -2) {
9308                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9309                         gameInfo.variant != VariantBerolina || toX < fromX)
9310                       board[EP_STATUS] = toX | berolina;
9311                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9312                         gameInfo.variant != VariantBerolina || toX > fromX)
9313                       board[EP_STATUS] = toX;
9314            }
9315        }
9316
9317        for(i=0; i<nrCastlingRights; i++) {
9318            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9319               board[CASTLING][i] == toX   && castlingRank[i] == toY
9320              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9321        }
9322
9323        if(gameInfo.variant == VariantSChess) { // update virginity
9324            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9325            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9326            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9327            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9328        }
9329
9330      if (fromX == toX && fromY == toY) return;
9331
9332      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9333      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9334      if(gameInfo.variant == VariantKnightmate)
9335          king += (int) WhiteUnicorn - (int) WhiteKing;
9336
9337     /* Code added by Tord: */
9338     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9339     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9340         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9341       board[fromY][fromX] = EmptySquare;
9342       board[toY][toX] = EmptySquare;
9343       if((toX > fromX) != (piece == WhiteRook)) {
9344         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9345       } else {
9346         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9347       }
9348     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9349                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9350       board[fromY][fromX] = EmptySquare;
9351       board[toY][toX] = EmptySquare;
9352       if((toX > fromX) != (piece == BlackRook)) {
9353         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9354       } else {
9355         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9356       }
9357     /* End of code added by Tord */
9358
9359     } else if (board[fromY][fromX] == king
9360         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9361         && toY == fromY && toX > fromX+1) {
9362         board[fromY][fromX] = EmptySquare;
9363         board[toY][toX] = king;
9364         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9365         board[fromY][BOARD_RGHT-1] = EmptySquare;
9366     } else if (board[fromY][fromX] == king
9367         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9368                && toY == fromY && toX < fromX-1) {
9369         board[fromY][fromX] = EmptySquare;
9370         board[toY][toX] = king;
9371         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9372         board[fromY][BOARD_LEFT] = EmptySquare;
9373     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9374                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9375                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9376                ) {
9377         /* white pawn promotion */
9378         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9379         if(gameInfo.variant==VariantBughouse ||
9380            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9381             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9382         board[fromY][fromX] = EmptySquare;
9383     } else if ((fromY >= BOARD_HEIGHT>>1)
9384                && (toX != fromX)
9385                && gameInfo.variant != VariantXiangqi
9386                && gameInfo.variant != VariantBerolina
9387                && (board[fromY][fromX] == WhitePawn)
9388                && (board[toY][toX] == EmptySquare)) {
9389         board[fromY][fromX] = EmptySquare;
9390         board[toY][toX] = WhitePawn;
9391         captured = board[toY - 1][toX];
9392         board[toY - 1][toX] = EmptySquare;
9393     } else if ((fromY == BOARD_HEIGHT-4)
9394                && (toX == fromX)
9395                && gameInfo.variant == VariantBerolina
9396                && (board[fromY][fromX] == WhitePawn)
9397                && (board[toY][toX] == EmptySquare)) {
9398         board[fromY][fromX] = EmptySquare;
9399         board[toY][toX] = WhitePawn;
9400         if(oldEP & EP_BEROLIN_A) {
9401                 captured = board[fromY][fromX-1];
9402                 board[fromY][fromX-1] = EmptySquare;
9403         }else{  captured = board[fromY][fromX+1];
9404                 board[fromY][fromX+1] = EmptySquare;
9405         }
9406     } else if (board[fromY][fromX] == king
9407         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9408                && toY == fromY && toX > fromX+1) {
9409         board[fromY][fromX] = EmptySquare;
9410         board[toY][toX] = king;
9411         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9412         board[fromY][BOARD_RGHT-1] = EmptySquare;
9413     } else if (board[fromY][fromX] == king
9414         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9415                && toY == fromY && toX < fromX-1) {
9416         board[fromY][fromX] = EmptySquare;
9417         board[toY][toX] = king;
9418         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9419         board[fromY][BOARD_LEFT] = EmptySquare;
9420     } else if (fromY == 7 && fromX == 3
9421                && board[fromY][fromX] == BlackKing
9422                && toY == 7 && toX == 5) {
9423         board[fromY][fromX] = EmptySquare;
9424         board[toY][toX] = BlackKing;
9425         board[fromY][7] = EmptySquare;
9426         board[toY][4] = BlackRook;
9427     } else if (fromY == 7 && fromX == 3
9428                && board[fromY][fromX] == BlackKing
9429                && toY == 7 && toX == 1) {
9430         board[fromY][fromX] = EmptySquare;
9431         board[toY][toX] = BlackKing;
9432         board[fromY][0] = EmptySquare;
9433         board[toY][2] = BlackRook;
9434     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9435                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9436                && toY < promoRank && promoChar
9437                ) {
9438         /* black pawn promotion */
9439         board[toY][toX] = CharToPiece(ToLower(promoChar));
9440         if(gameInfo.variant==VariantBughouse ||
9441            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9442             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9443         board[fromY][fromX] = EmptySquare;
9444     } else if ((fromY < BOARD_HEIGHT>>1)
9445                && (toX != fromX)
9446                && gameInfo.variant != VariantXiangqi
9447                && gameInfo.variant != VariantBerolina
9448                && (board[fromY][fromX] == BlackPawn)
9449                && (board[toY][toX] == EmptySquare)) {
9450         board[fromY][fromX] = EmptySquare;
9451         board[toY][toX] = BlackPawn;
9452         captured = board[toY + 1][toX];
9453         board[toY + 1][toX] = EmptySquare;
9454     } else if ((fromY == 3)
9455                && (toX == fromX)
9456                && gameInfo.variant == VariantBerolina
9457                && (board[fromY][fromX] == BlackPawn)
9458                && (board[toY][toX] == EmptySquare)) {
9459         board[fromY][fromX] = EmptySquare;
9460         board[toY][toX] = BlackPawn;
9461         if(oldEP & EP_BEROLIN_A) {
9462                 captured = board[fromY][fromX-1];
9463                 board[fromY][fromX-1] = EmptySquare;
9464         }else{  captured = board[fromY][fromX+1];
9465                 board[fromY][fromX+1] = EmptySquare;
9466         }
9467     } else {
9468         board[toY][toX] = board[fromY][fromX];
9469         board[fromY][fromX] = EmptySquare;
9470     }
9471   }
9472
9473     if (gameInfo.holdingsWidth != 0) {
9474
9475       /* !!A lot more code needs to be written to support holdings  */
9476       /* [HGM] OK, so I have written it. Holdings are stored in the */
9477       /* penultimate board files, so they are automaticlly stored   */
9478       /* in the game history.                                       */
9479       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9480                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9481         /* Delete from holdings, by decreasing count */
9482         /* and erasing image if necessary            */
9483         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9484         if(p < (int) BlackPawn) { /* white drop */
9485              p -= (int)WhitePawn;
9486                  p = PieceToNumber((ChessSquare)p);
9487              if(p >= gameInfo.holdingsSize) p = 0;
9488              if(--board[p][BOARD_WIDTH-2] <= 0)
9489                   board[p][BOARD_WIDTH-1] = EmptySquare;
9490              if((int)board[p][BOARD_WIDTH-2] < 0)
9491                         board[p][BOARD_WIDTH-2] = 0;
9492         } else {                  /* black drop */
9493              p -= (int)BlackPawn;
9494                  p = PieceToNumber((ChessSquare)p);
9495              if(p >= gameInfo.holdingsSize) p = 0;
9496              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9497                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9498              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9499                         board[BOARD_HEIGHT-1-p][1] = 0;
9500         }
9501       }
9502       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9503           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9504         /* [HGM] holdings: Add to holdings, if holdings exist */
9505         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9506                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9507                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9508         }
9509         p = (int) captured;
9510         if (p >= (int) BlackPawn) {
9511           p -= (int)BlackPawn;
9512           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9513                   /* in Shogi restore piece to its original  first */
9514                   captured = (ChessSquare) (DEMOTED captured);
9515                   p = DEMOTED p;
9516           }
9517           p = PieceToNumber((ChessSquare)p);
9518           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9519           board[p][BOARD_WIDTH-2]++;
9520           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9521         } else {
9522           p -= (int)WhitePawn;
9523           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9524                   captured = (ChessSquare) (DEMOTED captured);
9525                   p = DEMOTED p;
9526           }
9527           p = PieceToNumber((ChessSquare)p);
9528           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9529           board[BOARD_HEIGHT-1-p][1]++;
9530           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9531         }
9532       }
9533     } else if (gameInfo.variant == VariantAtomic) {
9534       if (captured != EmptySquare) {
9535         int y, x;
9536         for (y = toY-1; y <= toY+1; y++) {
9537           for (x = toX-1; x <= toX+1; x++) {
9538             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9539                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9540               board[y][x] = EmptySquare;
9541             }
9542           }
9543         }
9544         board[toY][toX] = EmptySquare;
9545       }
9546     }
9547     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9548         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9549     } else
9550     if(promoChar == '+') {
9551         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9552         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9553     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9554         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9555         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9556            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9557         board[toY][toX] = newPiece;
9558     }
9559     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9560                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9561         // [HGM] superchess: take promotion piece out of holdings
9562         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9563         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9564             if(!--board[k][BOARD_WIDTH-2])
9565                 board[k][BOARD_WIDTH-1] = EmptySquare;
9566         } else {
9567             if(!--board[BOARD_HEIGHT-1-k][1])
9568                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9569         }
9570     }
9571
9572 }
9573
9574 /* Updates forwardMostMove */
9575 void
9576 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9577 {
9578 //    forwardMostMove++; // [HGM] bare: moved downstream
9579
9580     (void) CoordsToAlgebraic(boards[forwardMostMove],
9581                              PosFlags(forwardMostMove),
9582                              fromY, fromX, toY, toX, promoChar,
9583                              parseList[forwardMostMove]);
9584
9585     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9586         int timeLeft; static int lastLoadFlag=0; int king, piece;
9587         piece = boards[forwardMostMove][fromY][fromX];
9588         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9589         if(gameInfo.variant == VariantKnightmate)
9590             king += (int) WhiteUnicorn - (int) WhiteKing;
9591         if(forwardMostMove == 0) {
9592             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9593                 fprintf(serverMoves, "%s;", UserName());
9594             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9595                 fprintf(serverMoves, "%s;", second.tidy);
9596             fprintf(serverMoves, "%s;", first.tidy);
9597             if(gameMode == MachinePlaysWhite)
9598                 fprintf(serverMoves, "%s;", UserName());
9599             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9600                 fprintf(serverMoves, "%s;", second.tidy);
9601         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9602         lastLoadFlag = loadFlag;
9603         // print base move
9604         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9605         // print castling suffix
9606         if( toY == fromY && piece == king ) {
9607             if(toX-fromX > 1)
9608                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9609             if(fromX-toX >1)
9610                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9611         }
9612         // e.p. suffix
9613         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9614              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9615              boards[forwardMostMove][toY][toX] == EmptySquare
9616              && fromX != toX && fromY != toY)
9617                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9618         // promotion suffix
9619         if(promoChar != NULLCHAR) {
9620             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9621                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9622                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9623             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9624         }
9625         if(!loadFlag) {
9626                 char buf[MOVE_LEN*2], *p; int len;
9627             fprintf(serverMoves, "/%d/%d",
9628                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9629             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9630             else                      timeLeft = blackTimeRemaining/1000;
9631             fprintf(serverMoves, "/%d", timeLeft);
9632                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9633                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9634                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9635                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9636             fprintf(serverMoves, "/%s", buf);
9637         }
9638         fflush(serverMoves);
9639     }
9640
9641     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9642         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9643       return;
9644     }
9645     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9646     if (commentList[forwardMostMove+1] != NULL) {
9647         free(commentList[forwardMostMove+1]);
9648         commentList[forwardMostMove+1] = NULL;
9649     }
9650     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9651     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9652     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9653     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9654     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9655     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9656     adjustedClock = FALSE;
9657     gameInfo.result = GameUnfinished;
9658     if (gameInfo.resultDetails != NULL) {
9659         free(gameInfo.resultDetails);
9660         gameInfo.resultDetails = NULL;
9661     }
9662     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9663                               moveList[forwardMostMove - 1]);
9664     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9665       case MT_NONE:
9666       case MT_STALEMATE:
9667       default:
9668         break;
9669       case MT_CHECK:
9670         if(gameInfo.variant != VariantShogi)
9671             strcat(parseList[forwardMostMove - 1], "+");
9672         break;
9673       case MT_CHECKMATE:
9674       case MT_STAINMATE:
9675         strcat(parseList[forwardMostMove - 1], "#");
9676         break;
9677     }
9678
9679 }
9680
9681 /* Updates currentMove if not pausing */
9682 void
9683 ShowMove (int fromX, int fromY, int toX, int toY)
9684 {
9685     int instant = (gameMode == PlayFromGameFile) ?
9686         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9687     if(appData.noGUI) return;
9688     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9689         if (!instant) {
9690             if (forwardMostMove == currentMove + 1) {
9691                 AnimateMove(boards[forwardMostMove - 1],
9692                             fromX, fromY, toX, toY);
9693             }
9694             if (appData.highlightLastMove) {
9695                 SetHighlights(fromX, fromY, toX, toY);
9696             }
9697         }
9698         currentMove = forwardMostMove;
9699     }
9700
9701     if (instant) return;
9702
9703     DisplayMove(currentMove - 1);
9704     DrawPosition(FALSE, boards[currentMove]);
9705     DisplayBothClocks();
9706     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9707 }
9708
9709 void
9710 SendEgtPath (ChessProgramState *cps)
9711 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9712         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9713
9714         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9715
9716         while(*p) {
9717             char c, *q = name+1, *r, *s;
9718
9719             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9720             while(*p && *p != ',') *q++ = *p++;
9721             *q++ = ':'; *q = 0;
9722             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9723                 strcmp(name, ",nalimov:") == 0 ) {
9724                 // take nalimov path from the menu-changeable option first, if it is defined
9725               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9726                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9727             } else
9728             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9729                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9730                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9731                 s = r = StrStr(s, ":") + 1; // beginning of path info
9732                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9733                 c = *r; *r = 0;             // temporarily null-terminate path info
9734                     *--q = 0;               // strip of trailig ':' from name
9735                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9736                 *r = c;
9737                 SendToProgram(buf,cps);     // send egtbpath command for this format
9738             }
9739             if(*p == ',') p++; // read away comma to position for next format name
9740         }
9741 }
9742
9743 void
9744 InitChessProgram (ChessProgramState *cps, int setup)
9745 /* setup needed to setup FRC opening position */
9746 {
9747     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9748     if (appData.noChessProgram) return;
9749     hintRequested = FALSE;
9750     bookRequested = FALSE;
9751
9752     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9753     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9754     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9755     if(cps->memSize) { /* [HGM] memory */
9756       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9757         SendToProgram(buf, cps);
9758     }
9759     SendEgtPath(cps); /* [HGM] EGT */
9760     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9761       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9762         SendToProgram(buf, cps);
9763     }
9764
9765     SendToProgram(cps->initString, cps);
9766     if (gameInfo.variant != VariantNormal &&
9767         gameInfo.variant != VariantLoadable
9768         /* [HGM] also send variant if board size non-standard */
9769         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9770                                             ) {
9771       char *v = VariantName(gameInfo.variant);
9772       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9773         /* [HGM] in protocol 1 we have to assume all variants valid */
9774         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9775         DisplayFatalError(buf, 0, 1);
9776         return;
9777       }
9778
9779       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9780       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9781       if( gameInfo.variant == VariantXiangqi )
9782            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9783       if( gameInfo.variant == VariantShogi )
9784            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9785       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9786            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9787       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9788           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9789            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9790       if( gameInfo.variant == VariantCourier )
9791            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9792       if( gameInfo.variant == VariantSuper )
9793            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9794       if( gameInfo.variant == VariantGreat )
9795            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9796       if( gameInfo.variant == VariantSChess )
9797            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9798       if( gameInfo.variant == VariantGrand )
9799            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9800
9801       if(overruled) {
9802         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9803                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9804            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9805            if(StrStr(cps->variants, b) == NULL) {
9806                // specific sized variant not known, check if general sizing allowed
9807                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9808                    if(StrStr(cps->variants, "boardsize") == NULL) {
9809                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9810                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9811                        DisplayFatalError(buf, 0, 1);
9812                        return;
9813                    }
9814                    /* [HGM] here we really should compare with the maximum supported board size */
9815                }
9816            }
9817       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9818       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9819       SendToProgram(buf, cps);
9820     }
9821     currentlyInitializedVariant = gameInfo.variant;
9822
9823     /* [HGM] send opening position in FRC to first engine */
9824     if(setup) {
9825           SendToProgram("force\n", cps);
9826           SendBoard(cps, 0);
9827           /* engine is now in force mode! Set flag to wake it up after first move. */
9828           setboardSpoiledMachineBlack = 1;
9829     }
9830
9831     if (cps->sendICS) {
9832       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9833       SendToProgram(buf, cps);
9834     }
9835     cps->maybeThinking = FALSE;
9836     cps->offeredDraw = 0;
9837     if (!appData.icsActive) {
9838         SendTimeControl(cps, movesPerSession, timeControl,
9839                         timeIncrement, appData.searchDepth,
9840                         searchTime);
9841     }
9842     if (appData.showThinking
9843         // [HGM] thinking: four options require thinking output to be sent
9844         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9845                                 ) {
9846         SendToProgram("post\n", cps);
9847     }
9848     SendToProgram("hard\n", cps);
9849     if (!appData.ponderNextMove) {
9850         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9851            it without being sure what state we are in first.  "hard"
9852            is not a toggle, so that one is OK.
9853          */
9854         SendToProgram("easy\n", cps);
9855     }
9856     if (cps->usePing) {
9857       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9858       SendToProgram(buf, cps);
9859     }
9860     cps->initDone = TRUE;
9861     ClearEngineOutputPane(cps == &second);
9862 }
9863
9864
9865 void
9866 StartChessProgram (ChessProgramState *cps)
9867 {
9868     char buf[MSG_SIZ];
9869     int err;
9870
9871     if (appData.noChessProgram) return;
9872     cps->initDone = FALSE;
9873
9874     if (strcmp(cps->host, "localhost") == 0) {
9875         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9876     } else if (*appData.remoteShell == NULLCHAR) {
9877         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9878     } else {
9879         if (*appData.remoteUser == NULLCHAR) {
9880           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9881                     cps->program);
9882         } else {
9883           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9884                     cps->host, appData.remoteUser, cps->program);
9885         }
9886         err = StartChildProcess(buf, "", &cps->pr);
9887     }
9888
9889     if (err != 0) {
9890       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9891         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9892         if(cps != &first) return;
9893         appData.noChessProgram = TRUE;
9894         ThawUI();
9895         SetNCPMode();
9896 //      DisplayFatalError(buf, err, 1);
9897 //      cps->pr = NoProc;
9898 //      cps->isr = NULL;
9899         return;
9900     }
9901
9902     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9903     if (cps->protocolVersion > 1) {
9904       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9905       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9906       cps->comboCnt = 0;  //                and values of combo boxes
9907       SendToProgram(buf, cps);
9908     } else {
9909       SendToProgram("xboard\n", cps);
9910     }
9911 }
9912
9913 void
9914 TwoMachinesEventIfReady P((void))
9915 {
9916   static int curMess = 0;
9917   if (first.lastPing != first.lastPong) {
9918     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9919     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9920     return;
9921   }
9922   if (second.lastPing != second.lastPong) {
9923     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9924     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9925     return;
9926   }
9927   DisplayMessage("", ""); curMess = 0;
9928   ThawUI();
9929   TwoMachinesEvent();
9930 }
9931
9932 char *
9933 MakeName (char *template)
9934 {
9935     time_t clock;
9936     struct tm *tm;
9937     static char buf[MSG_SIZ];
9938     char *p = buf;
9939     int i;
9940
9941     clock = time((time_t *)NULL);
9942     tm = localtime(&clock);
9943
9944     while(*p++ = *template++) if(p[-1] == '%') {
9945         switch(*template++) {
9946           case 0:   *p = 0; return buf;
9947           case 'Y': i = tm->tm_year+1900; break;
9948           case 'y': i = tm->tm_year-100; break;
9949           case 'M': i = tm->tm_mon+1; break;
9950           case 'd': i = tm->tm_mday; break;
9951           case 'h': i = tm->tm_hour; break;
9952           case 'm': i = tm->tm_min; break;
9953           case 's': i = tm->tm_sec; break;
9954           default:  i = 0;
9955         }
9956         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9957     }
9958     return buf;
9959 }
9960
9961 int
9962 CountPlayers (char *p)
9963 {
9964     int n = 0;
9965     while(p = strchr(p, '\n')) p++, n++; // count participants
9966     return n;
9967 }
9968
9969 FILE *
9970 WriteTourneyFile (char *results, FILE *f)
9971 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9972     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9973     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9974         // create a file with tournament description
9975         fprintf(f, "-participants {%s}\n", appData.participants);
9976         fprintf(f, "-seedBase %d\n", appData.seedBase);
9977         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9978         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9979         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9980         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9981         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9982         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9983         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9984         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9985         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9986         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9987         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9988         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9989         if(searchTime > 0)
9990                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9991         else {
9992                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9993                 fprintf(f, "-tc %s\n", appData.timeControl);
9994                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9995         }
9996         fprintf(f, "-results \"%s\"\n", results);
9997     }
9998     return f;
9999 }
10000
10001 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10002
10003 void
10004 Substitute (char *participants, int expunge)
10005 {
10006     int i, changed, changes=0, nPlayers=0;
10007     char *p, *q, *r, buf[MSG_SIZ];
10008     if(participants == NULL) return;
10009     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10010     r = p = participants; q = appData.participants;
10011     while(*p && *p == *q) {
10012         if(*p == '\n') r = p+1, nPlayers++;
10013         p++; q++;
10014     }
10015     if(*p) { // difference
10016         while(*p && *p++ != '\n');
10017         while(*q && *q++ != '\n');
10018       changed = nPlayers;
10019         changes = 1 + (strcmp(p, q) != 0);
10020     }
10021     if(changes == 1) { // a single engine mnemonic was changed
10022         q = r; while(*q) nPlayers += (*q++ == '\n');
10023         p = buf; while(*r && (*p = *r++) != '\n') p++;
10024         *p = NULLCHAR;
10025         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10026         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10027         if(mnemonic[i]) { // The substitute is valid
10028             FILE *f;
10029             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10030                 flock(fileno(f), LOCK_EX);
10031                 ParseArgsFromFile(f);
10032                 fseek(f, 0, SEEK_SET);
10033                 FREE(appData.participants); appData.participants = participants;
10034                 if(expunge) { // erase results of replaced engine
10035                     int len = strlen(appData.results), w, b, dummy;
10036                     for(i=0; i<len; i++) {
10037                         Pairing(i, nPlayers, &w, &b, &dummy);
10038                         if((w == changed || b == changed) && appData.results[i] == '*') {
10039                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10040                             fclose(f);
10041                             return;
10042                         }
10043                     }
10044                     for(i=0; i<len; i++) {
10045                         Pairing(i, nPlayers, &w, &b, &dummy);
10046                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10047                     }
10048                 }
10049                 WriteTourneyFile(appData.results, f);
10050                 fclose(f); // release lock
10051                 return;
10052             }
10053         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10054     }
10055     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10056     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10057     free(participants);
10058     return;
10059 }
10060
10061 int
10062 CheckPlayers (char *participants)
10063 {
10064         int i;
10065         char buf[MSG_SIZ], *p;
10066         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10067         while(p = strchr(participants, '\n')) {
10068             *p = NULLCHAR;
10069             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10070             if(!mnemonic[i]) {
10071                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10072                 *p = '\n';
10073                 DisplayError(buf, 0);
10074                 return 1;
10075             }
10076             *p = '\n';
10077             participants = p + 1;
10078         }
10079         return 0;
10080 }
10081
10082 int
10083 CreateTourney (char *name)
10084 {
10085         FILE *f;
10086         if(matchMode && strcmp(name, appData.tourneyFile)) {
10087              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10088         }
10089         if(name[0] == NULLCHAR) {
10090             if(appData.participants[0])
10091                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10092             return 0;
10093         }
10094         f = fopen(name, "r");
10095         if(f) { // file exists
10096             ASSIGN(appData.tourneyFile, name);
10097             ParseArgsFromFile(f); // parse it
10098         } else {
10099             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10100             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10101                 DisplayError(_("Not enough participants"), 0);
10102                 return 0;
10103             }
10104             if(CheckPlayers(appData.participants)) return 0;
10105             ASSIGN(appData.tourneyFile, name);
10106             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10107             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10108         }
10109         fclose(f);
10110         appData.noChessProgram = FALSE;
10111         appData.clockMode = TRUE;
10112         SetGNUMode();
10113         return 1;
10114 }
10115
10116 int
10117 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10118 {
10119     char buf[MSG_SIZ], *p, *q;
10120     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10121     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10122     skip = !all && group[0]; // if group requested, we start in skip mode
10123     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10124         p = names; q = buf; header = 0;
10125         while(*p && *p != '\n') *q++ = *p++;
10126         *q = 0;
10127         if(*p == '\n') p++;
10128         if(buf[0] == '#') {
10129             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10130             depth++; // we must be entering a new group
10131             if(all) continue; // suppress printing group headers when complete list requested
10132             header = 1;
10133             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10134         }
10135         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10136         if(engineList[i]) free(engineList[i]);
10137         engineList[i] = strdup(buf);
10138         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10139         if(engineMnemonic[i]) free(engineMnemonic[i]);
10140         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10141             strcat(buf, " (");
10142             sscanf(q + 8, "%s", buf + strlen(buf));
10143             strcat(buf, ")");
10144         }
10145         engineMnemonic[i] = strdup(buf);
10146         i++;
10147     }
10148     engineList[i] = engineMnemonic[i] = NULL;
10149     return i;
10150 }
10151
10152 // following implemented as macro to avoid type limitations
10153 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10154
10155 void
10156 SwapEngines (int n)
10157 {   // swap settings for first engine and other engine (so far only some selected options)
10158     int h;
10159     char *p;
10160     if(n == 0) return;
10161     SWAP(directory, p)
10162     SWAP(chessProgram, p)
10163     SWAP(isUCI, h)
10164     SWAP(hasOwnBookUCI, h)
10165     SWAP(protocolVersion, h)
10166     SWAP(reuse, h)
10167     SWAP(scoreIsAbsolute, h)
10168     SWAP(timeOdds, h)
10169     SWAP(logo, p)
10170     SWAP(pgnName, p)
10171     SWAP(pvSAN, h)
10172     SWAP(engOptions, p)
10173     SWAP(engInitString, p)
10174     SWAP(computerString, p)
10175     SWAP(features, p)
10176     SWAP(fenOverride, p)
10177     SWAP(NPS, h)
10178     SWAP(accumulateTC, h)
10179     SWAP(host, p)
10180 }
10181
10182 int
10183 GetEngineLine (char *s, int n)
10184 {
10185     int i;
10186     char buf[MSG_SIZ];
10187     extern char *icsNames;
10188     if(!s || !*s) return 0;
10189     NamesToList(n == 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10190     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10191     if(!mnemonic[i]) return 0;
10192     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10193     if(n == 1) SwapEngines(n);
10194     ParseArgsFromString(buf);
10195     if(n == 1) SwapEngines(n);
10196     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10197         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10198         ParseArgsFromString(buf);
10199     }
10200     return 1;
10201 }
10202
10203 int
10204 SetPlayer (int player, char *p)
10205 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10206     int i;
10207     char buf[MSG_SIZ], *engineName;
10208     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10209     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10210     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10211     if(mnemonic[i]) {
10212         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10213         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10214         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10215         ParseArgsFromString(buf);
10216     }
10217     free(engineName);
10218     return i;
10219 }
10220
10221 char *recentEngines;
10222
10223 void
10224 RecentEngineEvent (int nr)
10225 {
10226     int n;
10227 //    SwapEngines(1); // bump first to second
10228 //    ReplaceEngine(&second, 1); // and load it there
10229     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10230     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10231     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10232         ReplaceEngine(&first, 0);
10233         FloatToFront(&appData.recentEngineList, command[n]);
10234     }
10235 }
10236
10237 int
10238 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10239 {   // determine players from game number
10240     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10241
10242     if(appData.tourneyType == 0) {
10243         roundsPerCycle = (nPlayers - 1) | 1;
10244         pairingsPerRound = nPlayers / 2;
10245     } else if(appData.tourneyType > 0) {
10246         roundsPerCycle = nPlayers - appData.tourneyType;
10247         pairingsPerRound = appData.tourneyType;
10248     }
10249     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10250     gamesPerCycle = gamesPerRound * roundsPerCycle;
10251     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10252     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10253     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10254     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10255     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10256     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10257
10258     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10259     if(appData.roundSync) *syncInterval = gamesPerRound;
10260
10261     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10262
10263     if(appData.tourneyType == 0) {
10264         if(curPairing == (nPlayers-1)/2 ) {
10265             *whitePlayer = curRound;
10266             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10267         } else {
10268             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10269             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10270             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10271             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10272         }
10273     } else if(appData.tourneyType > 1) {
10274         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10275         *whitePlayer = curRound + appData.tourneyType;
10276     } else if(appData.tourneyType > 0) {
10277         *whitePlayer = curPairing;
10278         *blackPlayer = curRound + appData.tourneyType;
10279     }
10280
10281     // take care of white/black alternation per round. 
10282     // For cycles and games this is already taken care of by default, derived from matchGame!
10283     return curRound & 1;
10284 }
10285
10286 int
10287 NextTourneyGame (int nr, int *swapColors)
10288 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10289     char *p, *q;
10290     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10291     FILE *tf;
10292     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10293     tf = fopen(appData.tourneyFile, "r");
10294     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10295     ParseArgsFromFile(tf); fclose(tf);
10296     InitTimeControls(); // TC might be altered from tourney file
10297
10298     nPlayers = CountPlayers(appData.participants); // count participants
10299     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10300     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10301
10302     if(syncInterval) {
10303         p = q = appData.results;
10304         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10305         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10306             DisplayMessage(_("Waiting for other game(s)"),"");
10307             waitingForGame = TRUE;
10308             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10309             return 0;
10310         }
10311         waitingForGame = FALSE;
10312     }
10313
10314     if(appData.tourneyType < 0) {
10315         if(nr>=0 && !pairingReceived) {
10316             char buf[1<<16];
10317             if(pairing.pr == NoProc) {
10318                 if(!appData.pairingEngine[0]) {
10319                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10320                     return 0;
10321                 }
10322                 StartChessProgram(&pairing); // starts the pairing engine
10323             }
10324             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10325             SendToProgram(buf, &pairing);
10326             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10327             SendToProgram(buf, &pairing);
10328             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10329         }
10330         pairingReceived = 0;                              // ... so we continue here 
10331         *swapColors = 0;
10332         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10333         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10334         matchGame = 1; roundNr = nr / syncInterval + 1;
10335     }
10336
10337     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10338
10339     // redefine engines, engine dir, etc.
10340     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10341     if(first.pr == NoProc) {
10342       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10343       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10344     }
10345     if(second.pr == NoProc) {
10346       SwapEngines(1);
10347       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10348       SwapEngines(1);         // and make that valid for second engine by swapping
10349       InitEngine(&second, 1);
10350     }
10351     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10352     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10353     return 1;
10354 }
10355
10356 void
10357 NextMatchGame ()
10358 {   // performs game initialization that does not invoke engines, and then tries to start the game
10359     int res, firstWhite, swapColors = 0;
10360     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10361     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
10362         char buf[MSG_SIZ];
10363         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10364         if(strcmp(buf, currentDebugFile)) { // name has changed
10365             FILE *f = fopen(buf, "w");
10366             if(f) { // if opening the new file failed, just keep using the old one
10367                 ASSIGN(currentDebugFile, buf);
10368                 fclose(debugFP);
10369                 debugFP = f;
10370             }
10371             if(appData.serverFileName) {
10372                 if(serverFP) fclose(serverFP);
10373                 serverFP = fopen(appData.serverFileName, "w");
10374                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10375                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10376             }
10377         }
10378     }
10379     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10380     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10381     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10382     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10383     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10384     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10385     Reset(FALSE, first.pr != NoProc);
10386     res = LoadGameOrPosition(matchGame); // setup game
10387     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10388     if(!res) return; // abort when bad game/pos file
10389     TwoMachinesEvent();
10390 }
10391
10392 void
10393 UserAdjudicationEvent (int result)
10394 {
10395     ChessMove gameResult = GameIsDrawn;
10396
10397     if( result > 0 ) {
10398         gameResult = WhiteWins;
10399     }
10400     else if( result < 0 ) {
10401         gameResult = BlackWins;
10402     }
10403
10404     if( gameMode == TwoMachinesPlay ) {
10405         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10406     }
10407 }
10408
10409
10410 // [HGM] save: calculate checksum of game to make games easily identifiable
10411 int
10412 StringCheckSum (char *s)
10413 {
10414         int i = 0;
10415         if(s==NULL) return 0;
10416         while(*s) i = i*259 + *s++;
10417         return i;
10418 }
10419
10420 int
10421 GameCheckSum ()
10422 {
10423         int i, sum=0;
10424         for(i=backwardMostMove; i<forwardMostMove; i++) {
10425                 sum += pvInfoList[i].depth;
10426                 sum += StringCheckSum(parseList[i]);
10427                 sum += StringCheckSum(commentList[i]);
10428                 sum *= 261;
10429         }
10430         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10431         return sum + StringCheckSum(commentList[i]);
10432 } // end of save patch
10433
10434 void
10435 GameEnds (ChessMove result, char *resultDetails, int whosays)
10436 {
10437     GameMode nextGameMode;
10438     int isIcsGame;
10439     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10440
10441     if(endingGame) return; /* [HGM] crash: forbid recursion */
10442     endingGame = 1;
10443     if(twoBoards) { // [HGM] dual: switch back to one board
10444         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10445         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10446     }
10447     if (appData.debugMode) {
10448       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10449               result, resultDetails ? resultDetails : "(null)", whosays);
10450     }
10451
10452     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10453
10454     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10455         /* If we are playing on ICS, the server decides when the
10456            game is over, but the engine can offer to draw, claim
10457            a draw, or resign.
10458          */
10459 #if ZIPPY
10460         if (appData.zippyPlay && first.initDone) {
10461             if (result == GameIsDrawn) {
10462                 /* In case draw still needs to be claimed */
10463                 SendToICS(ics_prefix);
10464                 SendToICS("draw\n");
10465             } else if (StrCaseStr(resultDetails, "resign")) {
10466                 SendToICS(ics_prefix);
10467                 SendToICS("resign\n");
10468             }
10469         }
10470 #endif
10471         endingGame = 0; /* [HGM] crash */
10472         return;
10473     }
10474
10475     /* If we're loading the game from a file, stop */
10476     if (whosays == GE_FILE) {
10477       (void) StopLoadGameTimer();
10478       gameFileFP = NULL;
10479     }
10480
10481     /* Cancel draw offers */
10482     first.offeredDraw = second.offeredDraw = 0;
10483
10484     /* If this is an ICS game, only ICS can really say it's done;
10485        if not, anyone can. */
10486     isIcsGame = (gameMode == IcsPlayingWhite ||
10487                  gameMode == IcsPlayingBlack ||
10488                  gameMode == IcsObserving    ||
10489                  gameMode == IcsExamining);
10490
10491     if (!isIcsGame || whosays == GE_ICS) {
10492         /* OK -- not an ICS game, or ICS said it was done */
10493         StopClocks();
10494         if (!isIcsGame && !appData.noChessProgram)
10495           SetUserThinkingEnables();
10496
10497         /* [HGM] if a machine claims the game end we verify this claim */
10498         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10499             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10500                 char claimer;
10501                 ChessMove trueResult = (ChessMove) -1;
10502
10503                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10504                                             first.twoMachinesColor[0] :
10505                                             second.twoMachinesColor[0] ;
10506
10507                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10508                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10509                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10510                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10511                 } else
10512                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10513                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10514                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10515                 } else
10516                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10517                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10518                 }
10519
10520                 // now verify win claims, but not in drop games, as we don't understand those yet
10521                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10522                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10523                     (result == WhiteWins && claimer == 'w' ||
10524                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10525                       if (appData.debugMode) {
10526                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10527                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10528                       }
10529                       if(result != trueResult) {
10530                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10531                               result = claimer == 'w' ? BlackWins : WhiteWins;
10532                               resultDetails = buf;
10533                       }
10534                 } else
10535                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10536                     && (forwardMostMove <= backwardMostMove ||
10537                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10538                         (claimer=='b')==(forwardMostMove&1))
10539                                                                                   ) {
10540                       /* [HGM] verify: draws that were not flagged are false claims */
10541                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10542                       result = claimer == 'w' ? BlackWins : WhiteWins;
10543                       resultDetails = buf;
10544                 }
10545                 /* (Claiming a loss is accepted no questions asked!) */
10546             }
10547             /* [HGM] bare: don't allow bare King to win */
10548             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10549                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10550                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10551                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10552                && result != GameIsDrawn)
10553             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10554                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10555                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10556                         if(p >= 0 && p <= (int)WhiteKing) k++;
10557                 }
10558                 if (appData.debugMode) {
10559                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10560                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10561                 }
10562                 if(k <= 1) {
10563                         result = GameIsDrawn;
10564                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10565                         resultDetails = buf;
10566                 }
10567             }
10568         }
10569
10570
10571         if(serverMoves != NULL && !loadFlag) { char c = '=';
10572             if(result==WhiteWins) c = '+';
10573             if(result==BlackWins) c = '-';
10574             if(resultDetails != NULL)
10575                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10576         }
10577         if (resultDetails != NULL) {
10578             gameInfo.result = result;
10579             gameInfo.resultDetails = StrSave(resultDetails);
10580
10581             /* display last move only if game was not loaded from file */
10582             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10583                 DisplayMove(currentMove - 1);
10584
10585             if (forwardMostMove != 0) {
10586                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10587                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10588                                                                 ) {
10589                     if (*appData.saveGameFile != NULLCHAR) {
10590                         SaveGameToFile(appData.saveGameFile, TRUE);
10591                     } else if (appData.autoSaveGames) {
10592                         AutoSaveGame();
10593                     }
10594                     if (*appData.savePositionFile != NULLCHAR) {
10595                         SavePositionToFile(appData.savePositionFile);
10596                     }
10597                 }
10598             }
10599
10600             /* Tell program how game ended in case it is learning */
10601             /* [HGM] Moved this to after saving the PGN, just in case */
10602             /* engine died and we got here through time loss. In that */
10603             /* case we will get a fatal error writing the pipe, which */
10604             /* would otherwise lose us the PGN.                       */
10605             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10606             /* output during GameEnds should never be fatal anymore   */
10607             if (gameMode == MachinePlaysWhite ||
10608                 gameMode == MachinePlaysBlack ||
10609                 gameMode == TwoMachinesPlay ||
10610                 gameMode == IcsPlayingWhite ||
10611                 gameMode == IcsPlayingBlack ||
10612                 gameMode == BeginningOfGame) {
10613                 char buf[MSG_SIZ];
10614                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10615                         resultDetails);
10616                 if (first.pr != NoProc) {
10617                     SendToProgram(buf, &first);
10618                 }
10619                 if (second.pr != NoProc &&
10620                     gameMode == TwoMachinesPlay) {
10621                     SendToProgram(buf, &second);
10622                 }
10623             }
10624         }
10625
10626         if (appData.icsActive) {
10627             if (appData.quietPlay &&
10628                 (gameMode == IcsPlayingWhite ||
10629                  gameMode == IcsPlayingBlack)) {
10630                 SendToICS(ics_prefix);
10631                 SendToICS("set shout 1\n");
10632             }
10633             nextGameMode = IcsIdle;
10634             ics_user_moved = FALSE;
10635             /* clean up premove.  It's ugly when the game has ended and the
10636              * premove highlights are still on the board.
10637              */
10638             if (gotPremove) {
10639               gotPremove = FALSE;
10640               ClearPremoveHighlights();
10641               DrawPosition(FALSE, boards[currentMove]);
10642             }
10643             if (whosays == GE_ICS) {
10644                 switch (result) {
10645                 case WhiteWins:
10646                     if (gameMode == IcsPlayingWhite)
10647                         PlayIcsWinSound();
10648                     else if(gameMode == IcsPlayingBlack)
10649                         PlayIcsLossSound();
10650                     break;
10651                 case BlackWins:
10652                     if (gameMode == IcsPlayingBlack)
10653                         PlayIcsWinSound();
10654                     else if(gameMode == IcsPlayingWhite)
10655                         PlayIcsLossSound();
10656                     break;
10657                 case GameIsDrawn:
10658                     PlayIcsDrawSound();
10659                     break;
10660                 default:
10661                     PlayIcsUnfinishedSound();
10662                 }
10663             }
10664         } else if (gameMode == EditGame ||
10665                    gameMode == PlayFromGameFile ||
10666                    gameMode == AnalyzeMode ||
10667                    gameMode == AnalyzeFile) {
10668             nextGameMode = gameMode;
10669         } else {
10670             nextGameMode = EndOfGame;
10671         }
10672         pausing = FALSE;
10673         ModeHighlight();
10674     } else {
10675         nextGameMode = gameMode;
10676     }
10677
10678     if (appData.noChessProgram) {
10679         gameMode = nextGameMode;
10680         ModeHighlight();
10681         endingGame = 0; /* [HGM] crash */
10682         return;
10683     }
10684
10685     if (first.reuse) {
10686         /* Put first chess program into idle state */
10687         if (first.pr != NoProc &&
10688             (gameMode == MachinePlaysWhite ||
10689              gameMode == MachinePlaysBlack ||
10690              gameMode == TwoMachinesPlay ||
10691              gameMode == IcsPlayingWhite ||
10692              gameMode == IcsPlayingBlack ||
10693              gameMode == BeginningOfGame)) {
10694             SendToProgram("force\n", &first);
10695             if (first.usePing) {
10696               char buf[MSG_SIZ];
10697               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10698               SendToProgram(buf, &first);
10699             }
10700         }
10701     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10702         /* Kill off first chess program */
10703         if (first.isr != NULL)
10704           RemoveInputSource(first.isr);
10705         first.isr = NULL;
10706
10707         if (first.pr != NoProc) {
10708             ExitAnalyzeMode();
10709             DoSleep( appData.delayBeforeQuit );
10710             SendToProgram("quit\n", &first);
10711             DoSleep( appData.delayAfterQuit );
10712             DestroyChildProcess(first.pr, first.useSigterm);
10713         }
10714         first.pr = NoProc;
10715     }
10716     if (second.reuse) {
10717         /* Put second chess program into idle state */
10718         if (second.pr != NoProc &&
10719             gameMode == TwoMachinesPlay) {
10720             SendToProgram("force\n", &second);
10721             if (second.usePing) {
10722               char buf[MSG_SIZ];
10723               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10724               SendToProgram(buf, &second);
10725             }
10726         }
10727     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10728         /* Kill off second chess program */
10729         if (second.isr != NULL)
10730           RemoveInputSource(second.isr);
10731         second.isr = NULL;
10732
10733         if (second.pr != NoProc) {
10734             DoSleep( appData.delayBeforeQuit );
10735             SendToProgram("quit\n", &second);
10736             DoSleep( appData.delayAfterQuit );
10737             DestroyChildProcess(second.pr, second.useSigterm);
10738         }
10739         second.pr = NoProc;
10740     }
10741
10742     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10743         char resChar = '=';
10744         switch (result) {
10745         case WhiteWins:
10746           resChar = '+';
10747           if (first.twoMachinesColor[0] == 'w') {
10748             first.matchWins++;
10749           } else {
10750             second.matchWins++;
10751           }
10752           break;
10753         case BlackWins:
10754           resChar = '-';
10755           if (first.twoMachinesColor[0] == 'b') {
10756             first.matchWins++;
10757           } else {
10758             second.matchWins++;
10759           }
10760           break;
10761         case GameUnfinished:
10762           resChar = ' ';
10763         default:
10764           break;
10765         }
10766
10767         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10768         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10769             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10770             ReserveGame(nextGame, resChar); // sets nextGame
10771             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10772             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10773         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10774
10775         if (nextGame <= appData.matchGames && !abortMatch) {
10776             gameMode = nextGameMode;
10777             matchGame = nextGame; // this will be overruled in tourney mode!
10778             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10779             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10780             endingGame = 0; /* [HGM] crash */
10781             return;
10782         } else {
10783             gameMode = nextGameMode;
10784             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10785                      first.tidy, second.tidy,
10786                      first.matchWins, second.matchWins,
10787                      appData.matchGames - (first.matchWins + second.matchWins));
10788             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10789             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10790             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10791             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10792                 first.twoMachinesColor = "black\n";
10793                 second.twoMachinesColor = "white\n";
10794             } else {
10795                 first.twoMachinesColor = "white\n";
10796                 second.twoMachinesColor = "black\n";
10797             }
10798         }
10799     }
10800     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10801         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10802       ExitAnalyzeMode();
10803     gameMode = nextGameMode;
10804     ModeHighlight();
10805     endingGame = 0;  /* [HGM] crash */
10806     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10807         if(matchMode == TRUE) { // match through command line: exit with or without popup
10808             if(ranking) {
10809                 ToNrEvent(forwardMostMove);
10810                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10811                 else ExitEvent(0);
10812             } else DisplayFatalError(buf, 0, 0);
10813         } else { // match through menu; just stop, with or without popup
10814             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10815             ModeHighlight();
10816             if(ranking){
10817                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10818             } else DisplayNote(buf);
10819       }
10820       if(ranking) free(ranking);
10821     }
10822 }
10823
10824 /* Assumes program was just initialized (initString sent).
10825    Leaves program in force mode. */
10826 void
10827 FeedMovesToProgram (ChessProgramState *cps, int upto)
10828 {
10829     int i;
10830
10831     if (appData.debugMode)
10832       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10833               startedFromSetupPosition ? "position and " : "",
10834               backwardMostMove, upto, cps->which);
10835     if(currentlyInitializedVariant != gameInfo.variant) {
10836       char buf[MSG_SIZ];
10837         // [HGM] variantswitch: make engine aware of new variant
10838         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10839                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10840         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10841         SendToProgram(buf, cps);
10842         currentlyInitializedVariant = gameInfo.variant;
10843     }
10844     SendToProgram("force\n", cps);
10845     if (startedFromSetupPosition) {
10846         SendBoard(cps, backwardMostMove);
10847     if (appData.debugMode) {
10848         fprintf(debugFP, "feedMoves\n");
10849     }
10850     }
10851     for (i = backwardMostMove; i < upto; i++) {
10852         SendMoveToProgram(i, cps);
10853     }
10854 }
10855
10856
10857 int
10858 ResurrectChessProgram ()
10859 {
10860      /* The chess program may have exited.
10861         If so, restart it and feed it all the moves made so far. */
10862     static int doInit = 0;
10863
10864     if (appData.noChessProgram) return 1;
10865
10866     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10867         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10868         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10869         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10870     } else {
10871         if (first.pr != NoProc) return 1;
10872         StartChessProgram(&first);
10873     }
10874     InitChessProgram(&first, FALSE);
10875     FeedMovesToProgram(&first, currentMove);
10876
10877     if (!first.sendTime) {
10878         /* can't tell gnuchess what its clock should read,
10879            so we bow to its notion. */
10880         ResetClocks();
10881         timeRemaining[0][currentMove] = whiteTimeRemaining;
10882         timeRemaining[1][currentMove] = blackTimeRemaining;
10883     }
10884
10885     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10886                 appData.icsEngineAnalyze) && first.analysisSupport) {
10887       SendToProgram("analyze\n", &first);
10888       first.analyzing = TRUE;
10889     }
10890     return 1;
10891 }
10892
10893 /*
10894  * Button procedures
10895  */
10896 void
10897 Reset (int redraw, int init)
10898 {
10899     int i;
10900
10901     if (appData.debugMode) {
10902         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10903                 redraw, init, gameMode);
10904     }
10905     CleanupTail(); // [HGM] vari: delete any stored variations
10906     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10907     pausing = pauseExamInvalid = FALSE;
10908     startedFromSetupPosition = blackPlaysFirst = FALSE;
10909     firstMove = TRUE;
10910     whiteFlag = blackFlag = FALSE;
10911     userOfferedDraw = FALSE;
10912     hintRequested = bookRequested = FALSE;
10913     first.maybeThinking = FALSE;
10914     second.maybeThinking = FALSE;
10915     first.bookSuspend = FALSE; // [HGM] book
10916     second.bookSuspend = FALSE;
10917     thinkOutput[0] = NULLCHAR;
10918     lastHint[0] = NULLCHAR;
10919     ClearGameInfo(&gameInfo);
10920     gameInfo.variant = StringToVariant(appData.variant);
10921     ics_user_moved = ics_clock_paused = FALSE;
10922     ics_getting_history = H_FALSE;
10923     ics_gamenum = -1;
10924     white_holding[0] = black_holding[0] = NULLCHAR;
10925     ClearProgramStats();
10926     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10927
10928     ResetFrontEnd();
10929     ClearHighlights();
10930     flipView = appData.flipView;
10931     ClearPremoveHighlights();
10932     gotPremove = FALSE;
10933     alarmSounded = FALSE;
10934
10935     GameEnds(EndOfFile, NULL, GE_PLAYER);
10936     if(appData.serverMovesName != NULL) {
10937         /* [HGM] prepare to make moves file for broadcasting */
10938         clock_t t = clock();
10939         if(serverMoves != NULL) fclose(serverMoves);
10940         serverMoves = fopen(appData.serverMovesName, "r");
10941         if(serverMoves != NULL) {
10942             fclose(serverMoves);
10943             /* delay 15 sec before overwriting, so all clients can see end */
10944             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10945         }
10946         serverMoves = fopen(appData.serverMovesName, "w");
10947     }
10948
10949     ExitAnalyzeMode();
10950     gameMode = BeginningOfGame;
10951     ModeHighlight();
10952     if(appData.icsActive) gameInfo.variant = VariantNormal;
10953     currentMove = forwardMostMove = backwardMostMove = 0;
10954     MarkTargetSquares(1);
10955     InitPosition(redraw);
10956     for (i = 0; i < MAX_MOVES; i++) {
10957         if (commentList[i] != NULL) {
10958             free(commentList[i]);
10959             commentList[i] = NULL;
10960         }
10961     }
10962     ResetClocks();
10963     timeRemaining[0][0] = whiteTimeRemaining;
10964     timeRemaining[1][0] = blackTimeRemaining;
10965
10966     if (first.pr == NoProc) {
10967         StartChessProgram(&first);
10968     }
10969     if (init) {
10970             InitChessProgram(&first, startedFromSetupPosition);
10971     }
10972     DisplayTitle("");
10973     DisplayMessage("", "");
10974     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10975     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10976     ClearMap();        // [HGM] exclude: invalidate map
10977 }
10978
10979 void
10980 AutoPlayGameLoop ()
10981 {
10982     for (;;) {
10983         if (!AutoPlayOneMove())
10984           return;
10985         if (matchMode || appData.timeDelay == 0)
10986           continue;
10987         if (appData.timeDelay < 0)
10988           return;
10989         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
10990         break;
10991     }
10992 }
10993
10994
10995 int
10996 AutoPlayOneMove ()
10997 {
10998     int fromX, fromY, toX, toY;
10999
11000     if (appData.debugMode) {
11001       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11002     }
11003
11004     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11005       return FALSE;
11006
11007     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11008       pvInfoList[currentMove].depth = programStats.depth;
11009       pvInfoList[currentMove].score = programStats.score;
11010       pvInfoList[currentMove].time  = 0;
11011       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11012     }
11013
11014     if (currentMove >= forwardMostMove) {
11015       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
11016 //      gameMode = EndOfGame;
11017 //      ModeHighlight();
11018
11019       /* [AS] Clear current move marker at the end of a game */
11020       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11021
11022       return FALSE;
11023     }
11024
11025     toX = moveList[currentMove][2] - AAA;
11026     toY = moveList[currentMove][3] - ONE;
11027
11028     if (moveList[currentMove][1] == '@') {
11029         if (appData.highlightLastMove) {
11030             SetHighlights(-1, -1, toX, toY);
11031         }
11032     } else {
11033         fromX = moveList[currentMove][0] - AAA;
11034         fromY = moveList[currentMove][1] - ONE;
11035
11036         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11037
11038         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11039
11040         if (appData.highlightLastMove) {
11041             SetHighlights(fromX, fromY, toX, toY);
11042         }
11043     }
11044     DisplayMove(currentMove);
11045     SendMoveToProgram(currentMove++, &first);
11046     DisplayBothClocks();
11047     DrawPosition(FALSE, boards[currentMove]);
11048     // [HGM] PV info: always display, routine tests if empty
11049     DisplayComment(currentMove - 1, commentList[currentMove]);
11050     return TRUE;
11051 }
11052
11053
11054 int
11055 LoadGameOneMove (ChessMove readAhead)
11056 {
11057     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11058     char promoChar = NULLCHAR;
11059     ChessMove moveType;
11060     char move[MSG_SIZ];
11061     char *p, *q;
11062
11063     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11064         gameMode != AnalyzeMode && gameMode != Training) {
11065         gameFileFP = NULL;
11066         return FALSE;
11067     }
11068
11069     yyboardindex = forwardMostMove;
11070     if (readAhead != EndOfFile) {
11071       moveType = readAhead;
11072     } else {
11073       if (gameFileFP == NULL)
11074           return FALSE;
11075       moveType = (ChessMove) Myylex();
11076     }
11077
11078     done = FALSE;
11079     switch (moveType) {
11080       case Comment:
11081         if (appData.debugMode)
11082           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11083         p = yy_text;
11084
11085         /* append the comment but don't display it */
11086         AppendComment(currentMove, p, FALSE);
11087         return TRUE;
11088
11089       case WhiteCapturesEnPassant:
11090       case BlackCapturesEnPassant:
11091       case WhitePromotion:
11092       case BlackPromotion:
11093       case WhiteNonPromotion:
11094       case BlackNonPromotion:
11095       case NormalMove:
11096       case WhiteKingSideCastle:
11097       case WhiteQueenSideCastle:
11098       case BlackKingSideCastle:
11099       case BlackQueenSideCastle:
11100       case WhiteKingSideCastleWild:
11101       case WhiteQueenSideCastleWild:
11102       case BlackKingSideCastleWild:
11103       case BlackQueenSideCastleWild:
11104       /* PUSH Fabien */
11105       case WhiteHSideCastleFR:
11106       case WhiteASideCastleFR:
11107       case BlackHSideCastleFR:
11108       case BlackASideCastleFR:
11109       /* POP Fabien */
11110         if (appData.debugMode)
11111           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11112         fromX = currentMoveString[0] - AAA;
11113         fromY = currentMoveString[1] - ONE;
11114         toX = currentMoveString[2] - AAA;
11115         toY = currentMoveString[3] - ONE;
11116         promoChar = currentMoveString[4];
11117         break;
11118
11119       case WhiteDrop:
11120       case BlackDrop:
11121         if (appData.debugMode)
11122           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11123         fromX = moveType == WhiteDrop ?
11124           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11125         (int) CharToPiece(ToLower(currentMoveString[0]));
11126         fromY = DROP_RANK;
11127         toX = currentMoveString[2] - AAA;
11128         toY = currentMoveString[3] - ONE;
11129         break;
11130
11131       case WhiteWins:
11132       case BlackWins:
11133       case GameIsDrawn:
11134       case GameUnfinished:
11135         if (appData.debugMode)
11136           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11137         p = strchr(yy_text, '{');
11138         if (p == NULL) p = strchr(yy_text, '(');
11139         if (p == NULL) {
11140             p = yy_text;
11141             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11142         } else {
11143             q = strchr(p, *p == '{' ? '}' : ')');
11144             if (q != NULL) *q = NULLCHAR;
11145             p++;
11146         }
11147         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11148         GameEnds(moveType, p, GE_FILE);
11149         done = TRUE;
11150         if (cmailMsgLoaded) {
11151             ClearHighlights();
11152             flipView = WhiteOnMove(currentMove);
11153             if (moveType == GameUnfinished) flipView = !flipView;
11154             if (appData.debugMode)
11155               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11156         }
11157         break;
11158
11159       case EndOfFile:
11160         if (appData.debugMode)
11161           fprintf(debugFP, "Parser hit end of file\n");
11162         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11163           case MT_NONE:
11164           case MT_CHECK:
11165             break;
11166           case MT_CHECKMATE:
11167           case MT_STAINMATE:
11168             if (WhiteOnMove(currentMove)) {
11169                 GameEnds(BlackWins, "Black mates", GE_FILE);
11170             } else {
11171                 GameEnds(WhiteWins, "White mates", GE_FILE);
11172             }
11173             break;
11174           case MT_STALEMATE:
11175             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11176             break;
11177         }
11178         done = TRUE;
11179         break;
11180
11181       case MoveNumberOne:
11182         if (lastLoadGameStart == GNUChessGame) {
11183             /* GNUChessGames have numbers, but they aren't move numbers */
11184             if (appData.debugMode)
11185               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11186                       yy_text, (int) moveType);
11187             return LoadGameOneMove(EndOfFile); /* tail recursion */
11188         }
11189         /* else fall thru */
11190
11191       case XBoardGame:
11192       case GNUChessGame:
11193       case PGNTag:
11194         /* Reached start of next game in file */
11195         if (appData.debugMode)
11196           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11197         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11198           case MT_NONE:
11199           case MT_CHECK:
11200             break;
11201           case MT_CHECKMATE:
11202           case MT_STAINMATE:
11203             if (WhiteOnMove(currentMove)) {
11204                 GameEnds(BlackWins, "Black mates", GE_FILE);
11205             } else {
11206                 GameEnds(WhiteWins, "White mates", GE_FILE);
11207             }
11208             break;
11209           case MT_STALEMATE:
11210             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11211             break;
11212         }
11213         done = TRUE;
11214         break;
11215
11216       case PositionDiagram:     /* should not happen; ignore */
11217       case ElapsedTime:         /* ignore */
11218       case NAG:                 /* ignore */
11219         if (appData.debugMode)
11220           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11221                   yy_text, (int) moveType);
11222         return LoadGameOneMove(EndOfFile); /* tail recursion */
11223
11224       case IllegalMove:
11225         if (appData.testLegality) {
11226             if (appData.debugMode)
11227               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11228             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11229                     (forwardMostMove / 2) + 1,
11230                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11231             DisplayError(move, 0);
11232             done = TRUE;
11233         } else {
11234             if (appData.debugMode)
11235               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11236                       yy_text, currentMoveString);
11237             fromX = currentMoveString[0] - AAA;
11238             fromY = currentMoveString[1] - ONE;
11239             toX = currentMoveString[2] - AAA;
11240             toY = currentMoveString[3] - ONE;
11241             promoChar = currentMoveString[4];
11242         }
11243         break;
11244
11245       case AmbiguousMove:
11246         if (appData.debugMode)
11247           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11248         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11249                 (forwardMostMove / 2) + 1,
11250                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11251         DisplayError(move, 0);
11252         done = TRUE;
11253         break;
11254
11255       default:
11256       case ImpossibleMove:
11257         if (appData.debugMode)
11258           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11259         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11260                 (forwardMostMove / 2) + 1,
11261                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11262         DisplayError(move, 0);
11263         done = TRUE;
11264         break;
11265     }
11266
11267     if (done) {
11268         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11269             DrawPosition(FALSE, boards[currentMove]);
11270             DisplayBothClocks();
11271             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11272               DisplayComment(currentMove - 1, commentList[currentMove]);
11273         }
11274         (void) StopLoadGameTimer();
11275         gameFileFP = NULL;
11276         cmailOldMove = forwardMostMove;
11277         return FALSE;
11278     } else {
11279         /* currentMoveString is set as a side-effect of yylex */
11280
11281         thinkOutput[0] = NULLCHAR;
11282         MakeMove(fromX, fromY, toX, toY, promoChar);
11283         currentMove = forwardMostMove;
11284         return TRUE;
11285     }
11286 }
11287
11288 /* Load the nth game from the given file */
11289 int
11290 LoadGameFromFile (char *filename, int n, char *title, int useList)
11291 {
11292     FILE *f;
11293     char buf[MSG_SIZ];
11294
11295     if (strcmp(filename, "-") == 0) {
11296         f = stdin;
11297         title = "stdin";
11298     } else {
11299         f = fopen(filename, "rb");
11300         if (f == NULL) {
11301           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11302             DisplayError(buf, errno);
11303             return FALSE;
11304         }
11305     }
11306     if (fseek(f, 0, 0) == -1) {
11307         /* f is not seekable; probably a pipe */
11308         useList = FALSE;
11309     }
11310     if (useList && n == 0) {
11311         int error = GameListBuild(f);
11312         if (error) {
11313             DisplayError(_("Cannot build game list"), error);
11314         } else if (!ListEmpty(&gameList) &&
11315                    ((ListGame *) gameList.tailPred)->number > 1) {
11316             GameListPopUp(f, title);
11317             return TRUE;
11318         }
11319         GameListDestroy();
11320         n = 1;
11321     }
11322     if (n == 0) n = 1;
11323     return LoadGame(f, n, title, FALSE);
11324 }
11325
11326
11327 void
11328 MakeRegisteredMove ()
11329 {
11330     int fromX, fromY, toX, toY;
11331     char promoChar;
11332     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11333         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11334           case CMAIL_MOVE:
11335           case CMAIL_DRAW:
11336             if (appData.debugMode)
11337               fprintf(debugFP, "Restoring %s for game %d\n",
11338                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11339
11340             thinkOutput[0] = NULLCHAR;
11341             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11342             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11343             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11344             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11345             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11346             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11347             MakeMove(fromX, fromY, toX, toY, promoChar);
11348             ShowMove(fromX, fromY, toX, toY);
11349
11350             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11351               case MT_NONE:
11352               case MT_CHECK:
11353                 break;
11354
11355               case MT_CHECKMATE:
11356               case MT_STAINMATE:
11357                 if (WhiteOnMove(currentMove)) {
11358                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11359                 } else {
11360                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11361                 }
11362                 break;
11363
11364               case MT_STALEMATE:
11365                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11366                 break;
11367             }
11368
11369             break;
11370
11371           case CMAIL_RESIGN:
11372             if (WhiteOnMove(currentMove)) {
11373                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11374             } else {
11375                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11376             }
11377             break;
11378
11379           case CMAIL_ACCEPT:
11380             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11381             break;
11382
11383           default:
11384             break;
11385         }
11386     }
11387
11388     return;
11389 }
11390
11391 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11392 int
11393 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11394 {
11395     int retVal;
11396
11397     if (gameNumber > nCmailGames) {
11398         DisplayError(_("No more games in this message"), 0);
11399         return FALSE;
11400     }
11401     if (f == lastLoadGameFP) {
11402         int offset = gameNumber - lastLoadGameNumber;
11403         if (offset == 0) {
11404             cmailMsg[0] = NULLCHAR;
11405             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11406                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11407                 nCmailMovesRegistered--;
11408             }
11409             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11410             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11411                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11412             }
11413         } else {
11414             if (! RegisterMove()) return FALSE;
11415         }
11416     }
11417
11418     retVal = LoadGame(f, gameNumber, title, useList);
11419
11420     /* Make move registered during previous look at this game, if any */
11421     MakeRegisteredMove();
11422
11423     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11424         commentList[currentMove]
11425           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11426         DisplayComment(currentMove - 1, commentList[currentMove]);
11427     }
11428
11429     return retVal;
11430 }
11431
11432 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11433 int
11434 ReloadGame (int offset)
11435 {
11436     int gameNumber = lastLoadGameNumber + offset;
11437     if (lastLoadGameFP == NULL) {
11438         DisplayError(_("No game has been loaded yet"), 0);
11439         return FALSE;
11440     }
11441     if (gameNumber <= 0) {
11442         DisplayError(_("Can't back up any further"), 0);
11443         return FALSE;
11444     }
11445     if (cmailMsgLoaded) {
11446         return CmailLoadGame(lastLoadGameFP, gameNumber,
11447                              lastLoadGameTitle, lastLoadGameUseList);
11448     } else {
11449         return LoadGame(lastLoadGameFP, gameNumber,
11450                         lastLoadGameTitle, lastLoadGameUseList);
11451     }
11452 }
11453
11454 int keys[EmptySquare+1];
11455
11456 int
11457 PositionMatches (Board b1, Board b2)
11458 {
11459     int r, f, sum=0;
11460     switch(appData.searchMode) {
11461         case 1: return CompareWithRights(b1, b2);
11462         case 2:
11463             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11464                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11465             }
11466             return TRUE;
11467         case 3:
11468             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11469               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11470                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11471             }
11472             return sum==0;
11473         case 4:
11474             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11475                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11476             }
11477             return sum==0;
11478     }
11479     return TRUE;
11480 }
11481
11482 #define Q_PROMO  4
11483 #define Q_EP     3
11484 #define Q_BCASTL 2
11485 #define Q_WCASTL 1
11486
11487 int pieceList[256], quickBoard[256];
11488 ChessSquare pieceType[256] = { EmptySquare };
11489 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11490 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11491 int soughtTotal, turn;
11492 Boolean epOK, flipSearch;
11493
11494 typedef struct {
11495     unsigned char piece, to;
11496 } Move;
11497
11498 #define DSIZE (250000)
11499
11500 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11501 Move *moveDatabase = initialSpace;
11502 unsigned int movePtr, dataSize = DSIZE;
11503
11504 int
11505 MakePieceList (Board board, int *counts)
11506 {
11507     int r, f, n=Q_PROMO, total=0;
11508     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11509     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11510         int sq = f + (r<<4);
11511         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11512             quickBoard[sq] = ++n;
11513             pieceList[n] = sq;
11514             pieceType[n] = board[r][f];
11515             counts[board[r][f]]++;
11516             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11517             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11518             total++;
11519         }
11520     }
11521     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11522     return total;
11523 }
11524
11525 void
11526 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11527 {
11528     int sq = fromX + (fromY<<4);
11529     int piece = quickBoard[sq];
11530     quickBoard[sq] = 0;
11531     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11532     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11533         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11534         moveDatabase[movePtr++].piece = Q_WCASTL;
11535         quickBoard[sq] = piece;
11536         piece = quickBoard[from]; quickBoard[from] = 0;
11537         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11538     } else
11539     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11540         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11541         moveDatabase[movePtr++].piece = Q_BCASTL;
11542         quickBoard[sq] = piece;
11543         piece = quickBoard[from]; quickBoard[from] = 0;
11544         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11545     } else
11546     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11547         quickBoard[(fromY<<4)+toX] = 0;
11548         moveDatabase[movePtr].piece = Q_EP;
11549         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11550         moveDatabase[movePtr].to = sq;
11551     } else
11552     if(promoPiece != pieceType[piece]) {
11553         moveDatabase[movePtr++].piece = Q_PROMO;
11554         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11555     }
11556     moveDatabase[movePtr].piece = piece;
11557     quickBoard[sq] = piece;
11558     movePtr++;
11559 }
11560
11561 int
11562 PackGame (Board board)
11563 {
11564     Move *newSpace = NULL;
11565     moveDatabase[movePtr].piece = 0; // terminate previous game
11566     if(movePtr > dataSize) {
11567         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11568         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11569         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11570         if(newSpace) {
11571             int i;
11572             Move *p = moveDatabase, *q = newSpace;
11573             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11574             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11575             moveDatabase = newSpace;
11576         } else { // calloc failed, we must be out of memory. Too bad...
11577             dataSize = 0; // prevent calloc events for all subsequent games
11578             return 0;     // and signal this one isn't cached
11579         }
11580     }
11581     movePtr++;
11582     MakePieceList(board, counts);
11583     return movePtr;
11584 }
11585
11586 int
11587 QuickCompare (Board board, int *minCounts, int *maxCounts)
11588 {   // compare according to search mode
11589     int r, f;
11590     switch(appData.searchMode)
11591     {
11592       case 1: // exact position match
11593         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11594         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11595             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11596         }
11597         break;
11598       case 2: // can have extra material on empty squares
11599         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11600             if(board[r][f] == EmptySquare) continue;
11601             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11602         }
11603         break;
11604       case 3: // material with exact Pawn structure
11605         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11606             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11607             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11608         } // fall through to material comparison
11609       case 4: // exact material
11610         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11611         break;
11612       case 6: // material range with given imbalance
11613         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11614         // fall through to range comparison
11615       case 5: // material range
11616         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11617     }
11618     return TRUE;
11619 }
11620
11621 int
11622 QuickScan (Board board, Move *move)
11623 {   // reconstruct game,and compare all positions in it
11624     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11625     do {
11626         int piece = move->piece;
11627         int to = move->to, from = pieceList[piece];
11628         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11629           if(!piece) return -1;
11630           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11631             piece = (++move)->piece;
11632             from = pieceList[piece];
11633             counts[pieceType[piece]]--;
11634             pieceType[piece] = (ChessSquare) move->to;
11635             counts[move->to]++;
11636           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11637             counts[pieceType[quickBoard[to]]]--;
11638             quickBoard[to] = 0; total--;
11639             move++;
11640             continue;
11641           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11642             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11643             from  = pieceList[piece]; // so this must be King
11644             quickBoard[from] = 0;
11645             pieceList[piece] = to;
11646             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11647             quickBoard[from] = 0; // rook
11648             quickBoard[to] = piece;
11649             to = move->to; piece = move->piece;
11650             goto aftercastle;
11651           }
11652         }
11653         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11654         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11655         quickBoard[from] = 0;
11656       aftercastle:
11657         quickBoard[to] = piece;
11658         pieceList[piece] = to;
11659         cnt++; turn ^= 3;
11660         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11661            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11662            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11663                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11664           ) {
11665             static int lastCounts[EmptySquare+1];
11666             int i;
11667             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11668             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11669         } else stretch = 0;
11670         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11671         move++;
11672     } while(1);
11673 }
11674
11675 void
11676 InitSearch ()
11677 {
11678     int r, f;
11679     flipSearch = FALSE;
11680     CopyBoard(soughtBoard, boards[currentMove]);
11681     soughtTotal = MakePieceList(soughtBoard, maxSought);
11682     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11683     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11684     CopyBoard(reverseBoard, boards[currentMove]);
11685     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11686         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11687         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11688         reverseBoard[r][f] = piece;
11689     }
11690     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11691     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11692     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11693                  || (boards[currentMove][CASTLING][2] == NoRights || 
11694                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11695                  && (boards[currentMove][CASTLING][5] == NoRights || 
11696                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11697       ) {
11698         flipSearch = TRUE;
11699         CopyBoard(flipBoard, soughtBoard);
11700         CopyBoard(rotateBoard, reverseBoard);
11701         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11702             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11703             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11704         }
11705     }
11706     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11707     if(appData.searchMode >= 5) {
11708         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11709         MakePieceList(soughtBoard, minSought);
11710         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11711     }
11712     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11713         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11714 }
11715
11716 GameInfo dummyInfo;
11717
11718 int
11719 GameContainsPosition (FILE *f, ListGame *lg)
11720 {
11721     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11722     int fromX, fromY, toX, toY;
11723     char promoChar;
11724     static int initDone=FALSE;
11725
11726     // weed out games based on numerical tag comparison
11727     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11728     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11729     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11730     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11731     if(!initDone) {
11732         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11733         initDone = TRUE;
11734     }
11735     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11736     else CopyBoard(boards[scratch], initialPosition); // default start position
11737     if(lg->moves) {
11738         turn = btm + 1;
11739         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11740         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11741     }
11742     if(btm) plyNr++;
11743     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11744     fseek(f, lg->offset, 0);
11745     yynewfile(f);
11746     while(1) {
11747         yyboardindex = scratch;
11748         quickFlag = plyNr+1;
11749         next = Myylex();
11750         quickFlag = 0;
11751         switch(next) {
11752             case PGNTag:
11753                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11754             default:
11755                 continue;
11756
11757             case XBoardGame:
11758             case GNUChessGame:
11759                 if(plyNr) return -1; // after we have seen moves, this is for new game
11760               continue;
11761
11762             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11763             case ImpossibleMove:
11764             case WhiteWins: // game ends here with these four
11765             case BlackWins:
11766             case GameIsDrawn:
11767             case GameUnfinished:
11768                 return -1;
11769
11770             case IllegalMove:
11771                 if(appData.testLegality) return -1;
11772             case WhiteCapturesEnPassant:
11773             case BlackCapturesEnPassant:
11774             case WhitePromotion:
11775             case BlackPromotion:
11776             case WhiteNonPromotion:
11777             case BlackNonPromotion:
11778             case NormalMove:
11779             case WhiteKingSideCastle:
11780             case WhiteQueenSideCastle:
11781             case BlackKingSideCastle:
11782             case BlackQueenSideCastle:
11783             case WhiteKingSideCastleWild:
11784             case WhiteQueenSideCastleWild:
11785             case BlackKingSideCastleWild:
11786             case BlackQueenSideCastleWild:
11787             case WhiteHSideCastleFR:
11788             case WhiteASideCastleFR:
11789             case BlackHSideCastleFR:
11790             case BlackASideCastleFR:
11791                 fromX = currentMoveString[0] - AAA;
11792                 fromY = currentMoveString[1] - ONE;
11793                 toX = currentMoveString[2] - AAA;
11794                 toY = currentMoveString[3] - ONE;
11795                 promoChar = currentMoveString[4];
11796                 break;
11797             case WhiteDrop:
11798             case BlackDrop:
11799                 fromX = next == WhiteDrop ?
11800                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11801                   (int) CharToPiece(ToLower(currentMoveString[0]));
11802                 fromY = DROP_RANK;
11803                 toX = currentMoveString[2] - AAA;
11804                 toY = currentMoveString[3] - ONE;
11805                 promoChar = 0;
11806                 break;
11807         }
11808         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11809         plyNr++;
11810         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11811         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11812         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11813         if(appData.findMirror) {
11814             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11815             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11816         }
11817     }
11818 }
11819
11820 /* Load the nth game from open file f */
11821 int
11822 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11823 {
11824     ChessMove cm;
11825     char buf[MSG_SIZ];
11826     int gn = gameNumber;
11827     ListGame *lg = NULL;
11828     int numPGNTags = 0;
11829     int err, pos = -1;
11830     GameMode oldGameMode;
11831     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11832
11833     if (appData.debugMode)
11834         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11835
11836     if (gameMode == Training )
11837         SetTrainingModeOff();
11838
11839     oldGameMode = gameMode;
11840     if (gameMode != BeginningOfGame) {
11841       Reset(FALSE, TRUE);
11842     }
11843
11844     gameFileFP = f;
11845     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11846         fclose(lastLoadGameFP);
11847     }
11848
11849     if (useList) {
11850         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11851
11852         if (lg) {
11853             fseek(f, lg->offset, 0);
11854             GameListHighlight(gameNumber);
11855             pos = lg->position;
11856             gn = 1;
11857         }
11858         else {
11859             DisplayError(_("Game number out of range"), 0);
11860             return FALSE;
11861         }
11862     } else {
11863         GameListDestroy();
11864         if (fseek(f, 0, 0) == -1) {
11865             if (f == lastLoadGameFP ?
11866                 gameNumber == lastLoadGameNumber + 1 :
11867                 gameNumber == 1) {
11868                 gn = 1;
11869             } else {
11870                 DisplayError(_("Can't seek on game file"), 0);
11871                 return FALSE;
11872             }
11873         }
11874     }
11875     lastLoadGameFP = f;
11876     lastLoadGameNumber = gameNumber;
11877     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11878     lastLoadGameUseList = useList;
11879
11880     yynewfile(f);
11881
11882     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11883       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11884                 lg->gameInfo.black);
11885             DisplayTitle(buf);
11886     } else if (*title != NULLCHAR) {
11887         if (gameNumber > 1) {
11888           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11889             DisplayTitle(buf);
11890         } else {
11891             DisplayTitle(title);
11892         }
11893     }
11894
11895     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11896         gameMode = PlayFromGameFile;
11897         ModeHighlight();
11898     }
11899
11900     currentMove = forwardMostMove = backwardMostMove = 0;
11901     CopyBoard(boards[0], initialPosition);
11902     StopClocks();
11903
11904     /*
11905      * Skip the first gn-1 games in the file.
11906      * Also skip over anything that precedes an identifiable
11907      * start of game marker, to avoid being confused by
11908      * garbage at the start of the file.  Currently
11909      * recognized start of game markers are the move number "1",
11910      * the pattern "gnuchess .* game", the pattern
11911      * "^[#;%] [^ ]* game file", and a PGN tag block.
11912      * A game that starts with one of the latter two patterns
11913      * will also have a move number 1, possibly
11914      * following a position diagram.
11915      * 5-4-02: Let's try being more lenient and allowing a game to
11916      * start with an unnumbered move.  Does that break anything?
11917      */
11918     cm = lastLoadGameStart = EndOfFile;
11919     while (gn > 0) {
11920         yyboardindex = forwardMostMove;
11921         cm = (ChessMove) Myylex();
11922         switch (cm) {
11923           case EndOfFile:
11924             if (cmailMsgLoaded) {
11925                 nCmailGames = CMAIL_MAX_GAMES - gn;
11926             } else {
11927                 Reset(TRUE, TRUE);
11928                 DisplayError(_("Game not found in file"), 0);
11929             }
11930             return FALSE;
11931
11932           case GNUChessGame:
11933           case XBoardGame:
11934             gn--;
11935             lastLoadGameStart = cm;
11936             break;
11937
11938           case MoveNumberOne:
11939             switch (lastLoadGameStart) {
11940               case GNUChessGame:
11941               case XBoardGame:
11942               case PGNTag:
11943                 break;
11944               case MoveNumberOne:
11945               case EndOfFile:
11946                 gn--;           /* count this game */
11947                 lastLoadGameStart = cm;
11948                 break;
11949               default:
11950                 /* impossible */
11951                 break;
11952             }
11953             break;
11954
11955           case PGNTag:
11956             switch (lastLoadGameStart) {
11957               case GNUChessGame:
11958               case PGNTag:
11959               case MoveNumberOne:
11960               case EndOfFile:
11961                 gn--;           /* count this game */
11962                 lastLoadGameStart = cm;
11963                 break;
11964               case XBoardGame:
11965                 lastLoadGameStart = cm; /* game counted already */
11966                 break;
11967               default:
11968                 /* impossible */
11969                 break;
11970             }
11971             if (gn > 0) {
11972                 do {
11973                     yyboardindex = forwardMostMove;
11974                     cm = (ChessMove) Myylex();
11975                 } while (cm == PGNTag || cm == Comment);
11976             }
11977             break;
11978
11979           case WhiteWins:
11980           case BlackWins:
11981           case GameIsDrawn:
11982             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11983                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11984                     != CMAIL_OLD_RESULT) {
11985                     nCmailResults ++ ;
11986                     cmailResult[  CMAIL_MAX_GAMES
11987                                 - gn - 1] = CMAIL_OLD_RESULT;
11988                 }
11989             }
11990             break;
11991
11992           case NormalMove:
11993             /* Only a NormalMove can be at the start of a game
11994              * without a position diagram. */
11995             if (lastLoadGameStart == EndOfFile ) {
11996               gn--;
11997               lastLoadGameStart = MoveNumberOne;
11998             }
11999             break;
12000
12001           default:
12002             break;
12003         }
12004     }
12005
12006     if (appData.debugMode)
12007       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12008
12009     if (cm == XBoardGame) {
12010         /* Skip any header junk before position diagram and/or move 1 */
12011         for (;;) {
12012             yyboardindex = forwardMostMove;
12013             cm = (ChessMove) Myylex();
12014
12015             if (cm == EndOfFile ||
12016                 cm == GNUChessGame || cm == XBoardGame) {
12017                 /* Empty game; pretend end-of-file and handle later */
12018                 cm = EndOfFile;
12019                 break;
12020             }
12021
12022             if (cm == MoveNumberOne || cm == PositionDiagram ||
12023                 cm == PGNTag || cm == Comment)
12024               break;
12025         }
12026     } else if (cm == GNUChessGame) {
12027         if (gameInfo.event != NULL) {
12028             free(gameInfo.event);
12029         }
12030         gameInfo.event = StrSave(yy_text);
12031     }
12032
12033     startedFromSetupPosition = FALSE;
12034     while (cm == PGNTag) {
12035         if (appData.debugMode)
12036           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12037         err = ParsePGNTag(yy_text, &gameInfo);
12038         if (!err) numPGNTags++;
12039
12040         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12041         if(gameInfo.variant != oldVariant) {
12042             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12043             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12044             InitPosition(TRUE);
12045             oldVariant = gameInfo.variant;
12046             if (appData.debugMode)
12047               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12048         }
12049
12050
12051         if (gameInfo.fen != NULL) {
12052           Board initial_position;
12053           startedFromSetupPosition = TRUE;
12054           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12055             Reset(TRUE, TRUE);
12056             DisplayError(_("Bad FEN position in file"), 0);
12057             return FALSE;
12058           }
12059           CopyBoard(boards[0], initial_position);
12060           if (blackPlaysFirst) {
12061             currentMove = forwardMostMove = backwardMostMove = 1;
12062             CopyBoard(boards[1], initial_position);
12063             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12064             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12065             timeRemaining[0][1] = whiteTimeRemaining;
12066             timeRemaining[1][1] = blackTimeRemaining;
12067             if (commentList[0] != NULL) {
12068               commentList[1] = commentList[0];
12069               commentList[0] = NULL;
12070             }
12071           } else {
12072             currentMove = forwardMostMove = backwardMostMove = 0;
12073           }
12074           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12075           {   int i;
12076               initialRulePlies = FENrulePlies;
12077               for( i=0; i< nrCastlingRights; i++ )
12078                   initialRights[i] = initial_position[CASTLING][i];
12079           }
12080           yyboardindex = forwardMostMove;
12081           free(gameInfo.fen);
12082           gameInfo.fen = NULL;
12083         }
12084
12085         yyboardindex = forwardMostMove;
12086         cm = (ChessMove) Myylex();
12087
12088         /* Handle comments interspersed among the tags */
12089         while (cm == Comment) {
12090             char *p;
12091             if (appData.debugMode)
12092               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12093             p = yy_text;
12094             AppendComment(currentMove, p, FALSE);
12095             yyboardindex = forwardMostMove;
12096             cm = (ChessMove) Myylex();
12097         }
12098     }
12099
12100     /* don't rely on existence of Event tag since if game was
12101      * pasted from clipboard the Event tag may not exist
12102      */
12103     if (numPGNTags > 0){
12104         char *tags;
12105         if (gameInfo.variant == VariantNormal) {
12106           VariantClass v = StringToVariant(gameInfo.event);
12107           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12108           if(v < VariantShogi) gameInfo.variant = v;
12109         }
12110         if (!matchMode) {
12111           if( appData.autoDisplayTags ) {
12112             tags = PGNTags(&gameInfo);
12113             TagsPopUp(tags, CmailMsg());
12114             free(tags);
12115           }
12116         }
12117     } else {
12118         /* Make something up, but don't display it now */
12119         SetGameInfo();
12120         TagsPopDown();
12121     }
12122
12123     if (cm == PositionDiagram) {
12124         int i, j;
12125         char *p;
12126         Board initial_position;
12127
12128         if (appData.debugMode)
12129           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12130
12131         if (!startedFromSetupPosition) {
12132             p = yy_text;
12133             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12134               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12135                 switch (*p) {
12136                   case '{':
12137                   case '[':
12138                   case '-':
12139                   case ' ':
12140                   case '\t':
12141                   case '\n':
12142                   case '\r':
12143                     break;
12144                   default:
12145                     initial_position[i][j++] = CharToPiece(*p);
12146                     break;
12147                 }
12148             while (*p == ' ' || *p == '\t' ||
12149                    *p == '\n' || *p == '\r') p++;
12150
12151             if (strncmp(p, "black", strlen("black"))==0)
12152               blackPlaysFirst = TRUE;
12153             else
12154               blackPlaysFirst = FALSE;
12155             startedFromSetupPosition = TRUE;
12156
12157             CopyBoard(boards[0], initial_position);
12158             if (blackPlaysFirst) {
12159                 currentMove = forwardMostMove = backwardMostMove = 1;
12160                 CopyBoard(boards[1], initial_position);
12161                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12162                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12163                 timeRemaining[0][1] = whiteTimeRemaining;
12164                 timeRemaining[1][1] = blackTimeRemaining;
12165                 if (commentList[0] != NULL) {
12166                     commentList[1] = commentList[0];
12167                     commentList[0] = NULL;
12168                 }
12169             } else {
12170                 currentMove = forwardMostMove = backwardMostMove = 0;
12171             }
12172         }
12173         yyboardindex = forwardMostMove;
12174         cm = (ChessMove) Myylex();
12175     }
12176
12177     if (first.pr == NoProc) {
12178         StartChessProgram(&first);
12179     }
12180     InitChessProgram(&first, FALSE);
12181     SendToProgram("force\n", &first);
12182     if (startedFromSetupPosition) {
12183         SendBoard(&first, forwardMostMove);
12184     if (appData.debugMode) {
12185         fprintf(debugFP, "Load Game\n");
12186     }
12187         DisplayBothClocks();
12188     }
12189
12190     /* [HGM] server: flag to write setup moves in broadcast file as one */
12191     loadFlag = appData.suppressLoadMoves;
12192
12193     while (cm == Comment) {
12194         char *p;
12195         if (appData.debugMode)
12196           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12197         p = yy_text;
12198         AppendComment(currentMove, p, FALSE);
12199         yyboardindex = forwardMostMove;
12200         cm = (ChessMove) Myylex();
12201     }
12202
12203     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12204         cm == WhiteWins || cm == BlackWins ||
12205         cm == GameIsDrawn || cm == GameUnfinished) {
12206         DisplayMessage("", _("No moves in game"));
12207         if (cmailMsgLoaded) {
12208             if (appData.debugMode)
12209               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12210             ClearHighlights();
12211             flipView = FALSE;
12212         }
12213         DrawPosition(FALSE, boards[currentMove]);
12214         DisplayBothClocks();
12215         gameMode = EditGame;
12216         ModeHighlight();
12217         gameFileFP = NULL;
12218         cmailOldMove = 0;
12219         return TRUE;
12220     }
12221
12222     // [HGM] PV info: routine tests if comment empty
12223     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12224         DisplayComment(currentMove - 1, commentList[currentMove]);
12225     }
12226     if (!matchMode && appData.timeDelay != 0)
12227       DrawPosition(FALSE, boards[currentMove]);
12228
12229     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12230       programStats.ok_to_send = 1;
12231     }
12232
12233     /* if the first token after the PGN tags is a move
12234      * and not move number 1, retrieve it from the parser
12235      */
12236     if (cm != MoveNumberOne)
12237         LoadGameOneMove(cm);
12238
12239     /* load the remaining moves from the file */
12240     while (LoadGameOneMove(EndOfFile)) {
12241       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12242       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12243     }
12244
12245     /* rewind to the start of the game */
12246     currentMove = backwardMostMove;
12247
12248     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12249
12250     if (oldGameMode == AnalyzeFile ||
12251         oldGameMode == AnalyzeMode) {
12252       AnalyzeFileEvent();
12253     }
12254
12255     if (!matchMode && pos > 0) {
12256         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12257     } else
12258     if (matchMode || appData.timeDelay == 0) {
12259       ToEndEvent();
12260     } else if (appData.timeDelay > 0) {
12261       AutoPlayGameLoop();
12262     }
12263
12264     if (appData.debugMode)
12265         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12266
12267     loadFlag = 0; /* [HGM] true game starts */
12268     return TRUE;
12269 }
12270
12271 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12272 int
12273 ReloadPosition (int offset)
12274 {
12275     int positionNumber = lastLoadPositionNumber + offset;
12276     if (lastLoadPositionFP == NULL) {
12277         DisplayError(_("No position has been loaded yet"), 0);
12278         return FALSE;
12279     }
12280     if (positionNumber <= 0) {
12281         DisplayError(_("Can't back up any further"), 0);
12282         return FALSE;
12283     }
12284     return LoadPosition(lastLoadPositionFP, positionNumber,
12285                         lastLoadPositionTitle);
12286 }
12287
12288 /* Load the nth position from the given file */
12289 int
12290 LoadPositionFromFile (char *filename, int n, char *title)
12291 {
12292     FILE *f;
12293     char buf[MSG_SIZ];
12294
12295     if (strcmp(filename, "-") == 0) {
12296         return LoadPosition(stdin, n, "stdin");
12297     } else {
12298         f = fopen(filename, "rb");
12299         if (f == NULL) {
12300             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12301             DisplayError(buf, errno);
12302             return FALSE;
12303         } else {
12304             return LoadPosition(f, n, title);
12305         }
12306     }
12307 }
12308
12309 /* Load the nth position from the given open file, and close it */
12310 int
12311 LoadPosition (FILE *f, int positionNumber, char *title)
12312 {
12313     char *p, line[MSG_SIZ];
12314     Board initial_position;
12315     int i, j, fenMode, pn;
12316
12317     if (gameMode == Training )
12318         SetTrainingModeOff();
12319
12320     if (gameMode != BeginningOfGame) {
12321         Reset(FALSE, TRUE);
12322     }
12323     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12324         fclose(lastLoadPositionFP);
12325     }
12326     if (positionNumber == 0) positionNumber = 1;
12327     lastLoadPositionFP = f;
12328     lastLoadPositionNumber = positionNumber;
12329     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12330     if (first.pr == NoProc && !appData.noChessProgram) {
12331       StartChessProgram(&first);
12332       InitChessProgram(&first, FALSE);
12333     }
12334     pn = positionNumber;
12335     if (positionNumber < 0) {
12336         /* Negative position number means to seek to that byte offset */
12337         if (fseek(f, -positionNumber, 0) == -1) {
12338             DisplayError(_("Can't seek on position file"), 0);
12339             return FALSE;
12340         };
12341         pn = 1;
12342     } else {
12343         if (fseek(f, 0, 0) == -1) {
12344             if (f == lastLoadPositionFP ?
12345                 positionNumber == lastLoadPositionNumber + 1 :
12346                 positionNumber == 1) {
12347                 pn = 1;
12348             } else {
12349                 DisplayError(_("Can't seek on position file"), 0);
12350                 return FALSE;
12351             }
12352         }
12353     }
12354     /* See if this file is FEN or old-style xboard */
12355     if (fgets(line, MSG_SIZ, f) == NULL) {
12356         DisplayError(_("Position not found in file"), 0);
12357         return FALSE;
12358     }
12359     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12360     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12361
12362     if (pn >= 2) {
12363         if (fenMode || line[0] == '#') pn--;
12364         while (pn > 0) {
12365             /* skip positions before number pn */
12366             if (fgets(line, MSG_SIZ, f) == NULL) {
12367                 Reset(TRUE, TRUE);
12368                 DisplayError(_("Position not found in file"), 0);
12369                 return FALSE;
12370             }
12371             if (fenMode || line[0] == '#') pn--;
12372         }
12373     }
12374
12375     if (fenMode) {
12376         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12377             DisplayError(_("Bad FEN position in file"), 0);
12378             return FALSE;
12379         }
12380     } else {
12381         (void) fgets(line, MSG_SIZ, f);
12382         (void) fgets(line, MSG_SIZ, f);
12383
12384         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12385             (void) fgets(line, MSG_SIZ, f);
12386             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12387                 if (*p == ' ')
12388                   continue;
12389                 initial_position[i][j++] = CharToPiece(*p);
12390             }
12391         }
12392
12393         blackPlaysFirst = FALSE;
12394         if (!feof(f)) {
12395             (void) fgets(line, MSG_SIZ, f);
12396             if (strncmp(line, "black", strlen("black"))==0)
12397               blackPlaysFirst = TRUE;
12398         }
12399     }
12400     startedFromSetupPosition = TRUE;
12401
12402     CopyBoard(boards[0], initial_position);
12403     if (blackPlaysFirst) {
12404         currentMove = forwardMostMove = backwardMostMove = 1;
12405         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12406         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12407         CopyBoard(boards[1], initial_position);
12408         DisplayMessage("", _("Black to play"));
12409     } else {
12410         currentMove = forwardMostMove = backwardMostMove = 0;
12411         DisplayMessage("", _("White to play"));
12412     }
12413     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12414     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12415         SendToProgram("force\n", &first);
12416         SendBoard(&first, forwardMostMove);
12417     }
12418     if (appData.debugMode) {
12419 int i, j;
12420   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12421   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12422         fprintf(debugFP, "Load Position\n");
12423     }
12424
12425     if (positionNumber > 1) {
12426       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12427         DisplayTitle(line);
12428     } else {
12429         DisplayTitle(title);
12430     }
12431     gameMode = EditGame;
12432     ModeHighlight();
12433     ResetClocks();
12434     timeRemaining[0][1] = whiteTimeRemaining;
12435     timeRemaining[1][1] = blackTimeRemaining;
12436     DrawPosition(FALSE, boards[currentMove]);
12437
12438     return TRUE;
12439 }
12440
12441
12442 void
12443 CopyPlayerNameIntoFileName (char **dest, char *src)
12444 {
12445     while (*src != NULLCHAR && *src != ',') {
12446         if (*src == ' ') {
12447             *(*dest)++ = '_';
12448             src++;
12449         } else {
12450             *(*dest)++ = *src++;
12451         }
12452     }
12453 }
12454
12455 char *
12456 DefaultFileName (char *ext)
12457 {
12458     static char def[MSG_SIZ];
12459     char *p;
12460
12461     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12462         p = def;
12463         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12464         *p++ = '-';
12465         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12466         *p++ = '.';
12467         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12468     } else {
12469         def[0] = NULLCHAR;
12470     }
12471     return def;
12472 }
12473
12474 /* Save the current game to the given file */
12475 int
12476 SaveGameToFile (char *filename, int append)
12477 {
12478     FILE *f;
12479     char buf[MSG_SIZ];
12480     int result, i, t,tot=0;
12481
12482     if (strcmp(filename, "-") == 0) {
12483         return SaveGame(stdout, 0, NULL);
12484     } else {
12485         for(i=0; i<10; i++) { // upto 10 tries
12486              f = fopen(filename, append ? "a" : "w");
12487              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12488              if(f || errno != 13) break;
12489              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12490              tot += t;
12491         }
12492         if (f == NULL) {
12493             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12494             DisplayError(buf, errno);
12495             return FALSE;
12496         } else {
12497             safeStrCpy(buf, lastMsg, MSG_SIZ);
12498             DisplayMessage(_("Waiting for access to save file"), "");
12499             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12500             DisplayMessage(_("Saving game"), "");
12501             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12502             result = SaveGame(f, 0, NULL);
12503             DisplayMessage(buf, "");
12504             return result;
12505         }
12506     }
12507 }
12508
12509 char *
12510 SavePart (char *str)
12511 {
12512     static char buf[MSG_SIZ];
12513     char *p;
12514
12515     p = strchr(str, ' ');
12516     if (p == NULL) return str;
12517     strncpy(buf, str, p - str);
12518     buf[p - str] = NULLCHAR;
12519     return buf;
12520 }
12521
12522 #define PGN_MAX_LINE 75
12523
12524 #define PGN_SIDE_WHITE  0
12525 #define PGN_SIDE_BLACK  1
12526
12527 static int
12528 FindFirstMoveOutOfBook (int side)
12529 {
12530     int result = -1;
12531
12532     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12533         int index = backwardMostMove;
12534         int has_book_hit = 0;
12535
12536         if( (index % 2) != side ) {
12537             index++;
12538         }
12539
12540         while( index < forwardMostMove ) {
12541             /* Check to see if engine is in book */
12542             int depth = pvInfoList[index].depth;
12543             int score = pvInfoList[index].score;
12544             int in_book = 0;
12545
12546             if( depth <= 2 ) {
12547                 in_book = 1;
12548             }
12549             else if( score == 0 && depth == 63 ) {
12550                 in_book = 1; /* Zappa */
12551             }
12552             else if( score == 2 && depth == 99 ) {
12553                 in_book = 1; /* Abrok */
12554             }
12555
12556             has_book_hit += in_book;
12557
12558             if( ! in_book ) {
12559                 result = index;
12560
12561                 break;
12562             }
12563
12564             index += 2;
12565         }
12566     }
12567
12568     return result;
12569 }
12570
12571 void
12572 GetOutOfBookInfo (char * buf)
12573 {
12574     int oob[2];
12575     int i;
12576     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12577
12578     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12579     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12580
12581     *buf = '\0';
12582
12583     if( oob[0] >= 0 || oob[1] >= 0 ) {
12584         for( i=0; i<2; i++ ) {
12585             int idx = oob[i];
12586
12587             if( idx >= 0 ) {
12588                 if( i > 0 && oob[0] >= 0 ) {
12589                     strcat( buf, "   " );
12590                 }
12591
12592                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12593                 sprintf( buf+strlen(buf), "%s%.2f",
12594                     pvInfoList[idx].score >= 0 ? "+" : "",
12595                     pvInfoList[idx].score / 100.0 );
12596             }
12597         }
12598     }
12599 }
12600
12601 /* Save game in PGN style and close the file */
12602 int
12603 SaveGamePGN (FILE *f)
12604 {
12605     int i, offset, linelen, newblock;
12606 //    char *movetext;
12607     char numtext[32];
12608     int movelen, numlen, blank;
12609     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12610
12611     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12612
12613     PrintPGNTags(f, &gameInfo);
12614
12615     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12616
12617     if (backwardMostMove > 0 || startedFromSetupPosition) {
12618         char *fen = PositionToFEN(backwardMostMove, NULL);
12619         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12620         fprintf(f, "\n{--------------\n");
12621         PrintPosition(f, backwardMostMove);
12622         fprintf(f, "--------------}\n");
12623         free(fen);
12624     }
12625     else {
12626         /* [AS] Out of book annotation */
12627         if( appData.saveOutOfBookInfo ) {
12628             char buf[64];
12629
12630             GetOutOfBookInfo( buf );
12631
12632             if( buf[0] != '\0' ) {
12633                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12634             }
12635         }
12636
12637         fprintf(f, "\n");
12638     }
12639
12640     i = backwardMostMove;
12641     linelen = 0;
12642     newblock = TRUE;
12643
12644     while (i < forwardMostMove) {
12645         /* Print comments preceding this move */
12646         if (commentList[i] != NULL) {
12647             if (linelen > 0) fprintf(f, "\n");
12648             fprintf(f, "%s", commentList[i]);
12649             linelen = 0;
12650             newblock = TRUE;
12651         }
12652
12653         /* Format move number */
12654         if ((i % 2) == 0)
12655           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12656         else
12657           if (newblock)
12658             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12659           else
12660             numtext[0] = NULLCHAR;
12661
12662         numlen = strlen(numtext);
12663         newblock = FALSE;
12664
12665         /* Print move number */
12666         blank = linelen > 0 && numlen > 0;
12667         if (linelen + (blank ? 1 : 0) + numlen > 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", numtext);
12677         linelen += numlen;
12678
12679         /* Get move */
12680         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12681         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12682
12683         /* Print move */
12684         blank = linelen > 0 && movelen > 0;
12685         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12686             fprintf(f, "\n");
12687             linelen = 0;
12688             blank = 0;
12689         }
12690         if (blank) {
12691             fprintf(f, " ");
12692             linelen++;
12693         }
12694         fprintf(f, "%s", move_buffer);
12695         linelen += movelen;
12696
12697         /* [AS] Add PV info if present */
12698         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12699             /* [HGM] add time */
12700             char buf[MSG_SIZ]; int seconds;
12701
12702             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12703
12704             if( seconds <= 0)
12705               buf[0] = 0;
12706             else
12707               if( seconds < 30 )
12708                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12709               else
12710                 {
12711                   seconds = (seconds + 4)/10; // round to full seconds
12712                   if( seconds < 60 )
12713                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12714                   else
12715                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12716                 }
12717
12718             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12719                       pvInfoList[i].score >= 0 ? "+" : "",
12720                       pvInfoList[i].score / 100.0,
12721                       pvInfoList[i].depth,
12722                       buf );
12723
12724             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12725
12726             /* Print score/depth */
12727             blank = linelen > 0 && movelen > 0;
12728             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12729                 fprintf(f, "\n");
12730                 linelen = 0;
12731                 blank = 0;
12732             }
12733             if (blank) {
12734                 fprintf(f, " ");
12735                 linelen++;
12736             }
12737             fprintf(f, "%s", move_buffer);
12738             linelen += movelen;
12739         }
12740
12741         i++;
12742     }
12743
12744     /* Start a new line */
12745     if (linelen > 0) fprintf(f, "\n");
12746
12747     /* Print comments after last move */
12748     if (commentList[i] != NULL) {
12749         fprintf(f, "%s\n", commentList[i]);
12750     }
12751
12752     /* Print result */
12753     if (gameInfo.resultDetails != NULL &&
12754         gameInfo.resultDetails[0] != NULLCHAR) {
12755         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12756                 PGNResult(gameInfo.result));
12757     } else {
12758         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12759     }
12760
12761     fclose(f);
12762     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12763     return TRUE;
12764 }
12765
12766 /* Save game in old style and close the file */
12767 int
12768 SaveGameOldStyle (FILE *f)
12769 {
12770     int i, offset;
12771     time_t tm;
12772
12773     tm = time((time_t *) NULL);
12774
12775     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12776     PrintOpponents(f);
12777
12778     if (backwardMostMove > 0 || startedFromSetupPosition) {
12779         fprintf(f, "\n[--------------\n");
12780         PrintPosition(f, backwardMostMove);
12781         fprintf(f, "--------------]\n");
12782     } else {
12783         fprintf(f, "\n");
12784     }
12785
12786     i = backwardMostMove;
12787     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12788
12789     while (i < forwardMostMove) {
12790         if (commentList[i] != NULL) {
12791             fprintf(f, "[%s]\n", commentList[i]);
12792         }
12793
12794         if ((i % 2) == 1) {
12795             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12796             i++;
12797         } else {
12798             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12799             i++;
12800             if (commentList[i] != NULL) {
12801                 fprintf(f, "\n");
12802                 continue;
12803             }
12804             if (i >= forwardMostMove) {
12805                 fprintf(f, "\n");
12806                 break;
12807             }
12808             fprintf(f, "%s\n", parseList[i]);
12809             i++;
12810         }
12811     }
12812
12813     if (commentList[i] != NULL) {
12814         fprintf(f, "[%s]\n", commentList[i]);
12815     }
12816
12817     /* This isn't really the old style, but it's close enough */
12818     if (gameInfo.resultDetails != NULL &&
12819         gameInfo.resultDetails[0] != NULLCHAR) {
12820         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12821                 gameInfo.resultDetails);
12822     } else {
12823         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12824     }
12825
12826     fclose(f);
12827     return TRUE;
12828 }
12829
12830 /* Save the current game to open file f and close the file */
12831 int
12832 SaveGame (FILE *f, int dummy, char *dummy2)
12833 {
12834     if (gameMode == EditPosition) EditPositionDone(TRUE);
12835     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12836     if (appData.oldSaveStyle)
12837       return SaveGameOldStyle(f);
12838     else
12839       return SaveGamePGN(f);
12840 }
12841
12842 /* Save the current position to the given file */
12843 int
12844 SavePositionToFile (char *filename)
12845 {
12846     FILE *f;
12847     char buf[MSG_SIZ];
12848
12849     if (strcmp(filename, "-") == 0) {
12850         return SavePosition(stdout, 0, NULL);
12851     } else {
12852         f = fopen(filename, "a");
12853         if (f == NULL) {
12854             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12855             DisplayError(buf, errno);
12856             return FALSE;
12857         } else {
12858             safeStrCpy(buf, lastMsg, MSG_SIZ);
12859             DisplayMessage(_("Waiting for access to save file"), "");
12860             flock(fileno(f), LOCK_EX); // [HGM] lock
12861             DisplayMessage(_("Saving position"), "");
12862             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12863             SavePosition(f, 0, NULL);
12864             DisplayMessage(buf, "");
12865             return TRUE;
12866         }
12867     }
12868 }
12869
12870 /* Save the current position to the given open file and close the file */
12871 int
12872 SavePosition (FILE *f, int dummy, char *dummy2)
12873 {
12874     time_t tm;
12875     char *fen;
12876
12877     if (gameMode == EditPosition) EditPositionDone(TRUE);
12878     if (appData.oldSaveStyle) {
12879         tm = time((time_t *) NULL);
12880
12881         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12882         PrintOpponents(f);
12883         fprintf(f, "[--------------\n");
12884         PrintPosition(f, currentMove);
12885         fprintf(f, "--------------]\n");
12886     } else {
12887         fen = PositionToFEN(currentMove, NULL);
12888         fprintf(f, "%s\n", fen);
12889         free(fen);
12890     }
12891     fclose(f);
12892     return TRUE;
12893 }
12894
12895 void
12896 ReloadCmailMsgEvent (int unregister)
12897 {
12898 #if !WIN32
12899     static char *inFilename = NULL;
12900     static char *outFilename;
12901     int i;
12902     struct stat inbuf, outbuf;
12903     int status;
12904
12905     /* Any registered moves are unregistered if unregister is set, */
12906     /* i.e. invoked by the signal handler */
12907     if (unregister) {
12908         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12909             cmailMoveRegistered[i] = FALSE;
12910             if (cmailCommentList[i] != NULL) {
12911                 free(cmailCommentList[i]);
12912                 cmailCommentList[i] = NULL;
12913             }
12914         }
12915         nCmailMovesRegistered = 0;
12916     }
12917
12918     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12919         cmailResult[i] = CMAIL_NOT_RESULT;
12920     }
12921     nCmailResults = 0;
12922
12923     if (inFilename == NULL) {
12924         /* Because the filenames are static they only get malloced once  */
12925         /* and they never get freed                                      */
12926         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12927         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12928
12929         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12930         sprintf(outFilename, "%s.out", appData.cmailGameName);
12931     }
12932
12933     status = stat(outFilename, &outbuf);
12934     if (status < 0) {
12935         cmailMailedMove = FALSE;
12936     } else {
12937         status = stat(inFilename, &inbuf);
12938         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12939     }
12940
12941     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12942        counts the games, notes how each one terminated, etc.
12943
12944        It would be nice to remove this kludge and instead gather all
12945        the information while building the game list.  (And to keep it
12946        in the game list nodes instead of having a bunch of fixed-size
12947        parallel arrays.)  Note this will require getting each game's
12948        termination from the PGN tags, as the game list builder does
12949        not process the game moves.  --mann
12950        */
12951     cmailMsgLoaded = TRUE;
12952     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12953
12954     /* Load first game in the file or popup game menu */
12955     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12956
12957 #endif /* !WIN32 */
12958     return;
12959 }
12960
12961 int
12962 RegisterMove ()
12963 {
12964     FILE *f;
12965     char string[MSG_SIZ];
12966
12967     if (   cmailMailedMove
12968         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12969         return TRUE;            /* Allow free viewing  */
12970     }
12971
12972     /* Unregister move to ensure that we don't leave RegisterMove        */
12973     /* with the move registered when the conditions for registering no   */
12974     /* longer hold                                                       */
12975     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12976         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12977         nCmailMovesRegistered --;
12978
12979         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12980           {
12981               free(cmailCommentList[lastLoadGameNumber - 1]);
12982               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12983           }
12984     }
12985
12986     if (cmailOldMove == -1) {
12987         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12988         return FALSE;
12989     }
12990
12991     if (currentMove > cmailOldMove + 1) {
12992         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12993         return FALSE;
12994     }
12995
12996     if (currentMove < cmailOldMove) {
12997         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12998         return FALSE;
12999     }
13000
13001     if (forwardMostMove > currentMove) {
13002         /* Silently truncate extra moves */
13003         TruncateGame();
13004     }
13005
13006     if (   (currentMove == cmailOldMove + 1)
13007         || (   (currentMove == cmailOldMove)
13008             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13009                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13010         if (gameInfo.result != GameUnfinished) {
13011             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13012         }
13013
13014         if (commentList[currentMove] != NULL) {
13015             cmailCommentList[lastLoadGameNumber - 1]
13016               = StrSave(commentList[currentMove]);
13017         }
13018         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13019
13020         if (appData.debugMode)
13021           fprintf(debugFP, "Saving %s for game %d\n",
13022                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13023
13024         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13025
13026         f = fopen(string, "w");
13027         if (appData.oldSaveStyle) {
13028             SaveGameOldStyle(f); /* also closes the file */
13029
13030             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13031             f = fopen(string, "w");
13032             SavePosition(f, 0, NULL); /* also closes the file */
13033         } else {
13034             fprintf(f, "{--------------\n");
13035             PrintPosition(f, currentMove);
13036             fprintf(f, "--------------}\n\n");
13037
13038             SaveGame(f, 0, NULL); /* also closes the file*/
13039         }
13040
13041         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13042         nCmailMovesRegistered ++;
13043     } else if (nCmailGames == 1) {
13044         DisplayError(_("You have not made a move yet"), 0);
13045         return FALSE;
13046     }
13047
13048     return TRUE;
13049 }
13050
13051 void
13052 MailMoveEvent ()
13053 {
13054 #if !WIN32
13055     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13056     FILE *commandOutput;
13057     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13058     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13059     int nBuffers;
13060     int i;
13061     int archived;
13062     char *arcDir;
13063
13064     if (! cmailMsgLoaded) {
13065         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13066         return;
13067     }
13068
13069     if (nCmailGames == nCmailResults) {
13070         DisplayError(_("No unfinished games"), 0);
13071         return;
13072     }
13073
13074 #if CMAIL_PROHIBIT_REMAIL
13075     if (cmailMailedMove) {
13076       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);
13077         DisplayError(msg, 0);
13078         return;
13079     }
13080 #endif
13081
13082     if (! (cmailMailedMove || RegisterMove())) return;
13083
13084     if (   cmailMailedMove
13085         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13086       snprintf(string, MSG_SIZ, partCommandString,
13087                appData.debugMode ? " -v" : "", appData.cmailGameName);
13088         commandOutput = popen(string, "r");
13089
13090         if (commandOutput == NULL) {
13091             DisplayError(_("Failed to invoke cmail"), 0);
13092         } else {
13093             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13094                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13095             }
13096             if (nBuffers > 1) {
13097                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13098                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13099                 nBytes = MSG_SIZ - 1;
13100             } else {
13101                 (void) memcpy(msg, buffer, nBytes);
13102             }
13103             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13104
13105             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13106                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13107
13108                 archived = TRUE;
13109                 for (i = 0; i < nCmailGames; i ++) {
13110                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13111                         archived = FALSE;
13112                     }
13113                 }
13114                 if (   archived
13115                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13116                         != NULL)) {
13117                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13118                            arcDir,
13119                            appData.cmailGameName,
13120                            gameInfo.date);
13121                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13122                     cmailMsgLoaded = FALSE;
13123                 }
13124             }
13125
13126             DisplayInformation(msg);
13127             pclose(commandOutput);
13128         }
13129     } else {
13130         if ((*cmailMsg) != '\0') {
13131             DisplayInformation(cmailMsg);
13132         }
13133     }
13134
13135     return;
13136 #endif /* !WIN32 */
13137 }
13138
13139 char *
13140 CmailMsg ()
13141 {
13142 #if WIN32
13143     return NULL;
13144 #else
13145     int  prependComma = 0;
13146     char number[5];
13147     char string[MSG_SIZ];       /* Space for game-list */
13148     int  i;
13149
13150     if (!cmailMsgLoaded) return "";
13151
13152     if (cmailMailedMove) {
13153       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13154     } else {
13155         /* Create a list of games left */
13156       snprintf(string, MSG_SIZ, "[");
13157         for (i = 0; i < nCmailGames; i ++) {
13158             if (! (   cmailMoveRegistered[i]
13159                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13160                 if (prependComma) {
13161                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13162                 } else {
13163                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13164                     prependComma = 1;
13165                 }
13166
13167                 strcat(string, number);
13168             }
13169         }
13170         strcat(string, "]");
13171
13172         if (nCmailMovesRegistered + nCmailResults == 0) {
13173             switch (nCmailGames) {
13174               case 1:
13175                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13176                 break;
13177
13178               case 2:
13179                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13180                 break;
13181
13182               default:
13183                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13184                          nCmailGames);
13185                 break;
13186             }
13187         } else {
13188             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13189               case 1:
13190                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13191                          string);
13192                 break;
13193
13194               case 0:
13195                 if (nCmailResults == nCmailGames) {
13196                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13197                 } else {
13198                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13199                 }
13200                 break;
13201
13202               default:
13203                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13204                          string);
13205             }
13206         }
13207     }
13208     return cmailMsg;
13209 #endif /* WIN32 */
13210 }
13211
13212 void
13213 ResetGameEvent ()
13214 {
13215     if (gameMode == Training)
13216       SetTrainingModeOff();
13217
13218     Reset(TRUE, TRUE);
13219     cmailMsgLoaded = FALSE;
13220     if (appData.icsActive) {
13221       SendToICS(ics_prefix);
13222       SendToICS("refresh\n");
13223     }
13224 }
13225
13226 void
13227 ExitEvent (int status)
13228 {
13229     exiting++;
13230     if (exiting > 2) {
13231       /* Give up on clean exit */
13232       exit(status);
13233     }
13234     if (exiting > 1) {
13235       /* Keep trying for clean exit */
13236       return;
13237     }
13238
13239     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13240
13241     if (telnetISR != NULL) {
13242       RemoveInputSource(telnetISR);
13243     }
13244     if (icsPR != NoProc) {
13245       DestroyChildProcess(icsPR, TRUE);
13246     }
13247
13248     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13249     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13250
13251     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13252     /* make sure this other one finishes before killing it!                  */
13253     if(endingGame) { int count = 0;
13254         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13255         while(endingGame && count++ < 10) DoSleep(1);
13256         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13257     }
13258
13259     /* Kill off chess programs */
13260     if (first.pr != NoProc) {
13261         ExitAnalyzeMode();
13262
13263         DoSleep( appData.delayBeforeQuit );
13264         SendToProgram("quit\n", &first);
13265         DoSleep( appData.delayAfterQuit );
13266         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13267     }
13268     if (second.pr != NoProc) {
13269         DoSleep( appData.delayBeforeQuit );
13270         SendToProgram("quit\n", &second);
13271         DoSleep( appData.delayAfterQuit );
13272         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13273     }
13274     if (first.isr != NULL) {
13275         RemoveInputSource(first.isr);
13276     }
13277     if (second.isr != NULL) {
13278         RemoveInputSource(second.isr);
13279     }
13280
13281     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13282     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13283
13284     ShutDownFrontEnd();
13285     exit(status);
13286 }
13287
13288 void
13289 PauseEvent ()
13290 {
13291     if (appData.debugMode)
13292         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13293     if (pausing) {
13294         pausing = FALSE;
13295         ModeHighlight();
13296         if (gameMode == MachinePlaysWhite ||
13297             gameMode == MachinePlaysBlack) {
13298             StartClocks();
13299         } else {
13300             DisplayBothClocks();
13301         }
13302         if (gameMode == PlayFromGameFile) {
13303             if (appData.timeDelay >= 0)
13304                 AutoPlayGameLoop();
13305         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13306             Reset(FALSE, TRUE);
13307             SendToICS(ics_prefix);
13308             SendToICS("refresh\n");
13309         } else if (currentMove < forwardMostMove) {
13310             ForwardInner(forwardMostMove);
13311         }
13312         pauseExamInvalid = FALSE;
13313     } else {
13314         switch (gameMode) {
13315           default:
13316             return;
13317           case IcsExamining:
13318             pauseExamForwardMostMove = forwardMostMove;
13319             pauseExamInvalid = FALSE;
13320             /* fall through */
13321           case IcsObserving:
13322           case IcsPlayingWhite:
13323           case IcsPlayingBlack:
13324             pausing = TRUE;
13325             ModeHighlight();
13326             return;
13327           case PlayFromGameFile:
13328             (void) StopLoadGameTimer();
13329             pausing = TRUE;
13330             ModeHighlight();
13331             break;
13332           case BeginningOfGame:
13333             if (appData.icsActive) return;
13334             /* else fall through */
13335           case MachinePlaysWhite:
13336           case MachinePlaysBlack:
13337           case TwoMachinesPlay:
13338             if (forwardMostMove == 0)
13339               return;           /* don't pause if no one has moved */
13340             if ((gameMode == MachinePlaysWhite &&
13341                  !WhiteOnMove(forwardMostMove)) ||
13342                 (gameMode == MachinePlaysBlack &&
13343                  WhiteOnMove(forwardMostMove))) {
13344                 StopClocks();
13345             }
13346             pausing = TRUE;
13347             ModeHighlight();
13348             break;
13349         }
13350     }
13351 }
13352
13353 void
13354 EditCommentEvent ()
13355 {
13356     char title[MSG_SIZ];
13357
13358     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13359       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13360     } else {
13361       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13362                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13363                parseList[currentMove - 1]);
13364     }
13365
13366     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13367 }
13368
13369
13370 void
13371 EditTagsEvent ()
13372 {
13373     char *tags = PGNTags(&gameInfo);
13374     bookUp = FALSE;
13375     EditTagsPopUp(tags, NULL);
13376     free(tags);
13377 }
13378
13379 void
13380 AnalyzeModeEvent ()
13381 {
13382     if (appData.noChessProgram || gameMode == AnalyzeMode)
13383       return;
13384
13385     if (gameMode != AnalyzeFile) {
13386         if (!appData.icsEngineAnalyze) {
13387                EditGameEvent();
13388                if (gameMode != EditGame) return;
13389         }
13390         ResurrectChessProgram();
13391         SendToProgram("analyze\n", &first);
13392         first.analyzing = TRUE;
13393         /*first.maybeThinking = TRUE;*/
13394         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13395         EngineOutputPopUp();
13396     }
13397     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13398     pausing = FALSE;
13399     ModeHighlight();
13400     SetGameInfo();
13401
13402     StartAnalysisClock();
13403     GetTimeMark(&lastNodeCountTime);
13404     lastNodeCount = 0;
13405 }
13406
13407 void
13408 AnalyzeFileEvent ()
13409 {
13410     if (appData.noChessProgram || gameMode == AnalyzeFile)
13411       return;
13412
13413     if (gameMode != AnalyzeMode) {
13414         EditGameEvent();
13415         if (gameMode != EditGame) return;
13416         ResurrectChessProgram();
13417         SendToProgram("analyze\n", &first);
13418         first.analyzing = TRUE;
13419         /*first.maybeThinking = TRUE;*/
13420         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13421         EngineOutputPopUp();
13422     }
13423     gameMode = AnalyzeFile;
13424     pausing = FALSE;
13425     ModeHighlight();
13426     SetGameInfo();
13427
13428     StartAnalysisClock();
13429     GetTimeMark(&lastNodeCountTime);
13430     lastNodeCount = 0;
13431     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13432 }
13433
13434 void
13435 MachineWhiteEvent ()
13436 {
13437     char buf[MSG_SIZ];
13438     char *bookHit = NULL;
13439
13440     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13441       return;
13442
13443
13444     if (gameMode == PlayFromGameFile ||
13445         gameMode == TwoMachinesPlay  ||
13446         gameMode == Training         ||
13447         gameMode == AnalyzeMode      ||
13448         gameMode == EndOfGame)
13449         EditGameEvent();
13450
13451     if (gameMode == EditPosition)
13452         EditPositionDone(TRUE);
13453
13454     if (!WhiteOnMove(currentMove)) {
13455         DisplayError(_("It is not White's turn"), 0);
13456         return;
13457     }
13458
13459     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13460       ExitAnalyzeMode();
13461
13462     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13463         gameMode == AnalyzeFile)
13464         TruncateGame();
13465
13466     ResurrectChessProgram();    /* in case it isn't running */
13467     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13468         gameMode = MachinePlaysWhite;
13469         ResetClocks();
13470     } else
13471     gameMode = MachinePlaysWhite;
13472     pausing = FALSE;
13473     ModeHighlight();
13474     SetGameInfo();
13475     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13476     DisplayTitle(buf);
13477     if (first.sendName) {
13478       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13479       SendToProgram(buf, &first);
13480     }
13481     if (first.sendTime) {
13482       if (first.useColors) {
13483         SendToProgram("black\n", &first); /*gnu kludge*/
13484       }
13485       SendTimeRemaining(&first, TRUE);
13486     }
13487     if (first.useColors) {
13488       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13489     }
13490     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13491     SetMachineThinkingEnables();
13492     first.maybeThinking = TRUE;
13493     StartClocks();
13494     firstMove = FALSE;
13495
13496     if (appData.autoFlipView && !flipView) {
13497       flipView = !flipView;
13498       DrawPosition(FALSE, NULL);
13499       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13500     }
13501
13502     if(bookHit) { // [HGM] book: simulate book reply
13503         static char bookMove[MSG_SIZ]; // a bit generous?
13504
13505         programStats.nodes = programStats.depth = programStats.time =
13506         programStats.score = programStats.got_only_move = 0;
13507         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13508
13509         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13510         strcat(bookMove, bookHit);
13511         HandleMachineMove(bookMove, &first);
13512     }
13513 }
13514
13515 void
13516 MachineBlackEvent ()
13517 {
13518   char buf[MSG_SIZ];
13519   char *bookHit = NULL;
13520
13521     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13522         return;
13523
13524
13525     if (gameMode == PlayFromGameFile ||
13526         gameMode == TwoMachinesPlay  ||
13527         gameMode == Training         ||
13528         gameMode == AnalyzeMode      ||
13529         gameMode == EndOfGame)
13530         EditGameEvent();
13531
13532     if (gameMode == EditPosition)
13533         EditPositionDone(TRUE);
13534
13535     if (WhiteOnMove(currentMove)) {
13536         DisplayError(_("It is not Black's turn"), 0);
13537         return;
13538     }
13539
13540     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13541       ExitAnalyzeMode();
13542
13543     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13544         gameMode == AnalyzeFile)
13545         TruncateGame();
13546
13547     ResurrectChessProgram();    /* in case it isn't running */
13548     gameMode = MachinePlaysBlack;
13549     pausing = FALSE;
13550     ModeHighlight();
13551     SetGameInfo();
13552     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13553     DisplayTitle(buf);
13554     if (first.sendName) {
13555       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13556       SendToProgram(buf, &first);
13557     }
13558     if (first.sendTime) {
13559       if (first.useColors) {
13560         SendToProgram("white\n", &first); /*gnu kludge*/
13561       }
13562       SendTimeRemaining(&first, FALSE);
13563     }
13564     if (first.useColors) {
13565       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13566     }
13567     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13568     SetMachineThinkingEnables();
13569     first.maybeThinking = TRUE;
13570     StartClocks();
13571
13572     if (appData.autoFlipView && flipView) {
13573       flipView = !flipView;
13574       DrawPosition(FALSE, NULL);
13575       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13576     }
13577     if(bookHit) { // [HGM] book: simulate book reply
13578         static char bookMove[MSG_SIZ]; // a bit generous?
13579
13580         programStats.nodes = programStats.depth = programStats.time =
13581         programStats.score = programStats.got_only_move = 0;
13582         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13583
13584         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13585         strcat(bookMove, bookHit);
13586         HandleMachineMove(bookMove, &first);
13587     }
13588 }
13589
13590
13591 void
13592 DisplayTwoMachinesTitle ()
13593 {
13594     char buf[MSG_SIZ];
13595     if (appData.matchGames > 0) {
13596         if(appData.tourneyFile[0]) {
13597           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13598                    gameInfo.white, _("vs."), gameInfo.black,
13599                    nextGame+1, appData.matchGames+1,
13600                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13601         } else 
13602         if (first.twoMachinesColor[0] == 'w') {
13603           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13604                    gameInfo.white, _("vs."),  gameInfo.black,
13605                    first.matchWins, second.matchWins,
13606                    matchGame - 1 - (first.matchWins + second.matchWins));
13607         } else {
13608           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13609                    gameInfo.white, _("vs."), gameInfo.black,
13610                    second.matchWins, first.matchWins,
13611                    matchGame - 1 - (first.matchWins + second.matchWins));
13612         }
13613     } else {
13614       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13615     }
13616     DisplayTitle(buf);
13617 }
13618
13619 void
13620 SettingsMenuIfReady ()
13621 {
13622   if (second.lastPing != second.lastPong) {
13623     DisplayMessage("", _("Waiting for second chess program"));
13624     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13625     return;
13626   }
13627   ThawUI();
13628   DisplayMessage("", "");
13629   SettingsPopUp(&second);
13630 }
13631
13632 int
13633 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13634 {
13635     char buf[MSG_SIZ];
13636     if (cps->pr == NoProc) {
13637         StartChessProgram(cps);
13638         if (cps->protocolVersion == 1) {
13639           retry();
13640         } else {
13641           /* kludge: allow timeout for initial "feature" command */
13642           FreezeUI();
13643           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13644           DisplayMessage("", buf);
13645           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13646         }
13647         return 1;
13648     }
13649     return 0;
13650 }
13651
13652 void
13653 TwoMachinesEvent P((void))
13654 {
13655     int i;
13656     char buf[MSG_SIZ];
13657     ChessProgramState *onmove;
13658     char *bookHit = NULL;
13659     static int stalling = 0;
13660     TimeMark now;
13661     long wait;
13662
13663     if (appData.noChessProgram) return;
13664
13665     switch (gameMode) {
13666       case TwoMachinesPlay:
13667         return;
13668       case MachinePlaysWhite:
13669       case MachinePlaysBlack:
13670         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13671             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13672             return;
13673         }
13674         /* fall through */
13675       case BeginningOfGame:
13676       case PlayFromGameFile:
13677       case EndOfGame:
13678         EditGameEvent();
13679         if (gameMode != EditGame) return;
13680         break;
13681       case EditPosition:
13682         EditPositionDone(TRUE);
13683         break;
13684       case AnalyzeMode:
13685       case AnalyzeFile:
13686         ExitAnalyzeMode();
13687         break;
13688       case EditGame:
13689       default:
13690         break;
13691     }
13692
13693 //    forwardMostMove = currentMove;
13694     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13695
13696     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13697
13698     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13699     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13700       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13701       return;
13702     }
13703
13704     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13705         DisplayError("second engine does not play this", 0);
13706         return;
13707     }
13708
13709     if(!stalling) {
13710       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13711       SendToProgram("force\n", &second);
13712       stalling = 1;
13713       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13714       return;
13715     }
13716     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13717     if(appData.matchPause>10000 || appData.matchPause<10)
13718                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13719     wait = SubtractTimeMarks(&now, &pauseStart);
13720     if(wait < appData.matchPause) {
13721         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13722         return;
13723     }
13724     // we are now committed to starting the game
13725     stalling = 0;
13726     DisplayMessage("", "");
13727     if (startedFromSetupPosition) {
13728         SendBoard(&second, backwardMostMove);
13729     if (appData.debugMode) {
13730         fprintf(debugFP, "Two Machines\n");
13731     }
13732     }
13733     for (i = backwardMostMove; i < forwardMostMove; i++) {
13734         SendMoveToProgram(i, &second);
13735     }
13736
13737     gameMode = TwoMachinesPlay;
13738     pausing = FALSE;
13739     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13740     SetGameInfo();
13741     DisplayTwoMachinesTitle();
13742     firstMove = TRUE;
13743     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13744         onmove = &first;
13745     } else {
13746         onmove = &second;
13747     }
13748     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13749     SendToProgram(first.computerString, &first);
13750     if (first.sendName) {
13751       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13752       SendToProgram(buf, &first);
13753     }
13754     SendToProgram(second.computerString, &second);
13755     if (second.sendName) {
13756       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13757       SendToProgram(buf, &second);
13758     }
13759
13760     ResetClocks();
13761     if (!first.sendTime || !second.sendTime) {
13762         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13763         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13764     }
13765     if (onmove->sendTime) {
13766       if (onmove->useColors) {
13767         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13768       }
13769       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13770     }
13771     if (onmove->useColors) {
13772       SendToProgram(onmove->twoMachinesColor, onmove);
13773     }
13774     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13775 //    SendToProgram("go\n", onmove);
13776     onmove->maybeThinking = TRUE;
13777     SetMachineThinkingEnables();
13778
13779     StartClocks();
13780
13781     if(bookHit) { // [HGM] book: simulate book reply
13782         static char bookMove[MSG_SIZ]; // a bit generous?
13783
13784         programStats.nodes = programStats.depth = programStats.time =
13785         programStats.score = programStats.got_only_move = 0;
13786         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13787
13788         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13789         strcat(bookMove, bookHit);
13790         savedMessage = bookMove; // args for deferred call
13791         savedState = onmove;
13792         ScheduleDelayedEvent(DeferredBookMove, 1);
13793     }
13794 }
13795
13796 void
13797 TrainingEvent ()
13798 {
13799     if (gameMode == Training) {
13800       SetTrainingModeOff();
13801       gameMode = PlayFromGameFile;
13802       DisplayMessage("", _("Training mode off"));
13803     } else {
13804       gameMode = Training;
13805       animateTraining = appData.animate;
13806
13807       /* make sure we are not already at the end of the game */
13808       if (currentMove < forwardMostMove) {
13809         SetTrainingModeOn();
13810         DisplayMessage("", _("Training mode on"));
13811       } else {
13812         gameMode = PlayFromGameFile;
13813         DisplayError(_("Already at end of game"), 0);
13814       }
13815     }
13816     ModeHighlight();
13817 }
13818
13819 void
13820 IcsClientEvent ()
13821 {
13822     if (!appData.icsActive) return;
13823     switch (gameMode) {
13824       case IcsPlayingWhite:
13825       case IcsPlayingBlack:
13826       case IcsObserving:
13827       case IcsIdle:
13828       case BeginningOfGame:
13829       case IcsExamining:
13830         return;
13831
13832       case EditGame:
13833         break;
13834
13835       case EditPosition:
13836         EditPositionDone(TRUE);
13837         break;
13838
13839       case AnalyzeMode:
13840       case AnalyzeFile:
13841         ExitAnalyzeMode();
13842         break;
13843
13844       default:
13845         EditGameEvent();
13846         break;
13847     }
13848
13849     gameMode = IcsIdle;
13850     ModeHighlight();
13851     return;
13852 }
13853
13854 void
13855 EditGameEvent ()
13856 {
13857     int i;
13858
13859     switch (gameMode) {
13860       case Training:
13861         SetTrainingModeOff();
13862         break;
13863       case MachinePlaysWhite:
13864       case MachinePlaysBlack:
13865       case BeginningOfGame:
13866         SendToProgram("force\n", &first);
13867         SetUserThinkingEnables();
13868         break;
13869       case PlayFromGameFile:
13870         (void) StopLoadGameTimer();
13871         if (gameFileFP != NULL) {
13872             gameFileFP = NULL;
13873         }
13874         break;
13875       case EditPosition:
13876         EditPositionDone(TRUE);
13877         break;
13878       case AnalyzeMode:
13879       case AnalyzeFile:
13880         ExitAnalyzeMode();
13881         SendToProgram("force\n", &first);
13882         break;
13883       case TwoMachinesPlay:
13884         GameEnds(EndOfFile, NULL, GE_PLAYER);
13885         ResurrectChessProgram();
13886         SetUserThinkingEnables();
13887         break;
13888       case EndOfGame:
13889         ResurrectChessProgram();
13890         break;
13891       case IcsPlayingBlack:
13892       case IcsPlayingWhite:
13893         DisplayError(_("Warning: You are still playing a game"), 0);
13894         break;
13895       case IcsObserving:
13896         DisplayError(_("Warning: You are still observing a game"), 0);
13897         break;
13898       case IcsExamining:
13899         DisplayError(_("Warning: You are still examining a game"), 0);
13900         break;
13901       case IcsIdle:
13902         break;
13903       case EditGame:
13904       default:
13905         return;
13906     }
13907
13908     pausing = FALSE;
13909     StopClocks();
13910     first.offeredDraw = second.offeredDraw = 0;
13911
13912     if (gameMode == PlayFromGameFile) {
13913         whiteTimeRemaining = timeRemaining[0][currentMove];
13914         blackTimeRemaining = timeRemaining[1][currentMove];
13915         DisplayTitle("");
13916     }
13917
13918     if (gameMode == MachinePlaysWhite ||
13919         gameMode == MachinePlaysBlack ||
13920         gameMode == TwoMachinesPlay ||
13921         gameMode == EndOfGame) {
13922         i = forwardMostMove;
13923         while (i > currentMove) {
13924             SendToProgram("undo\n", &first);
13925             i--;
13926         }
13927         if(!adjustedClock) {
13928         whiteTimeRemaining = timeRemaining[0][currentMove];
13929         blackTimeRemaining = timeRemaining[1][currentMove];
13930         DisplayBothClocks();
13931         }
13932         if (whiteFlag || blackFlag) {
13933             whiteFlag = blackFlag = 0;
13934         }
13935         DisplayTitle("");
13936     }
13937
13938     gameMode = EditGame;
13939     ModeHighlight();
13940     SetGameInfo();
13941 }
13942
13943
13944 void
13945 EditPositionEvent ()
13946 {
13947     if (gameMode == EditPosition) {
13948         EditGameEvent();
13949         return;
13950     }
13951
13952     EditGameEvent();
13953     if (gameMode != EditGame) return;
13954
13955     gameMode = EditPosition;
13956     ModeHighlight();
13957     SetGameInfo();
13958     if (currentMove > 0)
13959       CopyBoard(boards[0], boards[currentMove]);
13960
13961     blackPlaysFirst = !WhiteOnMove(currentMove);
13962     ResetClocks();
13963     currentMove = forwardMostMove = backwardMostMove = 0;
13964     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13965     DisplayMove(-1);
13966     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13967 }
13968
13969 void
13970 ExitAnalyzeMode ()
13971 {
13972     /* [DM] icsEngineAnalyze - possible call from other functions */
13973     if (appData.icsEngineAnalyze) {
13974         appData.icsEngineAnalyze = FALSE;
13975
13976         DisplayMessage("",_("Close ICS engine analyze..."));
13977     }
13978     if (first.analysisSupport && first.analyzing) {
13979       SendToProgram("exit\n", &first);
13980       first.analyzing = FALSE;
13981     }
13982     thinkOutput[0] = NULLCHAR;
13983 }
13984
13985 void
13986 EditPositionDone (Boolean fakeRights)
13987 {
13988     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13989
13990     startedFromSetupPosition = TRUE;
13991     InitChessProgram(&first, FALSE);
13992     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13993       boards[0][EP_STATUS] = EP_NONE;
13994       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13995       if(boards[0][0][BOARD_WIDTH>>1] == king) {
13996         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
13997         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13998       } else boards[0][CASTLING][2] = NoRights;
13999       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14000         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14001         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14002       } else boards[0][CASTLING][5] = NoRights;
14003       if(gameInfo.variant == VariantSChess) {
14004         int i;
14005         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14006           boards[0][VIRGIN][i] = 0;
14007           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14008           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14009         }
14010       }
14011     }
14012     SendToProgram("force\n", &first);
14013     if (blackPlaysFirst) {
14014         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14015         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14016         currentMove = forwardMostMove = backwardMostMove = 1;
14017         CopyBoard(boards[1], boards[0]);
14018     } else {
14019         currentMove = forwardMostMove = backwardMostMove = 0;
14020     }
14021     SendBoard(&first, forwardMostMove);
14022     if (appData.debugMode) {
14023         fprintf(debugFP, "EditPosDone\n");
14024     }
14025     DisplayTitle("");
14026     DisplayMessage("", "");
14027     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14028     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14029     gameMode = EditGame;
14030     ModeHighlight();
14031     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14032     ClearHighlights(); /* [AS] */
14033 }
14034
14035 /* Pause for `ms' milliseconds */
14036 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14037 void
14038 TimeDelay (long ms)
14039 {
14040     TimeMark m1, m2;
14041
14042     GetTimeMark(&m1);
14043     do {
14044         GetTimeMark(&m2);
14045     } while (SubtractTimeMarks(&m2, &m1) < ms);
14046 }
14047
14048 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14049 void
14050 SendMultiLineToICS (char *buf)
14051 {
14052     char temp[MSG_SIZ+1], *p;
14053     int len;
14054
14055     len = strlen(buf);
14056     if (len > MSG_SIZ)
14057       len = MSG_SIZ;
14058
14059     strncpy(temp, buf, len);
14060     temp[len] = 0;
14061
14062     p = temp;
14063     while (*p) {
14064         if (*p == '\n' || *p == '\r')
14065           *p = ' ';
14066         ++p;
14067     }
14068
14069     strcat(temp, "\n");
14070     SendToICS(temp);
14071     SendToPlayer(temp, strlen(temp));
14072 }
14073
14074 void
14075 SetWhiteToPlayEvent ()
14076 {
14077     if (gameMode == EditPosition) {
14078         blackPlaysFirst = FALSE;
14079         DisplayBothClocks();    /* works because currentMove is 0 */
14080     } else if (gameMode == IcsExamining) {
14081         SendToICS(ics_prefix);
14082         SendToICS("tomove white\n");
14083     }
14084 }
14085
14086 void
14087 SetBlackToPlayEvent ()
14088 {
14089     if (gameMode == EditPosition) {
14090         blackPlaysFirst = TRUE;
14091         currentMove = 1;        /* kludge */
14092         DisplayBothClocks();
14093         currentMove = 0;
14094     } else if (gameMode == IcsExamining) {
14095         SendToICS(ics_prefix);
14096         SendToICS("tomove black\n");
14097     }
14098 }
14099
14100 void
14101 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14102 {
14103     char buf[MSG_SIZ];
14104     ChessSquare piece = boards[0][y][x];
14105
14106     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14107
14108     switch (selection) {
14109       case ClearBoard:
14110         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14111             SendToICS(ics_prefix);
14112             SendToICS("bsetup clear\n");
14113         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14114             SendToICS(ics_prefix);
14115             SendToICS("clearboard\n");
14116         } else {
14117             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14118                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14119                 for (y = 0; y < BOARD_HEIGHT; y++) {
14120                     if (gameMode == IcsExamining) {
14121                         if (boards[currentMove][y][x] != EmptySquare) {
14122                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14123                                     AAA + x, ONE + y);
14124                             SendToICS(buf);
14125                         }
14126                     } else {
14127                         boards[0][y][x] = p;
14128                     }
14129                 }
14130             }
14131         }
14132         if (gameMode == EditPosition) {
14133             DrawPosition(FALSE, boards[0]);
14134         }
14135         break;
14136
14137       case WhitePlay:
14138         SetWhiteToPlayEvent();
14139         break;
14140
14141       case BlackPlay:
14142         SetBlackToPlayEvent();
14143         break;
14144
14145       case EmptySquare:
14146         if (gameMode == IcsExamining) {
14147             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14148             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14149             SendToICS(buf);
14150         } else {
14151             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14152                 if(x == BOARD_LEFT-2) {
14153                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14154                     boards[0][y][1] = 0;
14155                 } else
14156                 if(x == BOARD_RGHT+1) {
14157                     if(y >= gameInfo.holdingsSize) break;
14158                     boards[0][y][BOARD_WIDTH-2] = 0;
14159                 } else break;
14160             }
14161             boards[0][y][x] = EmptySquare;
14162             DrawPosition(FALSE, boards[0]);
14163         }
14164         break;
14165
14166       case PromotePiece:
14167         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14168            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14169             selection = (ChessSquare) (PROMOTED piece);
14170         } else if(piece == EmptySquare) selection = WhiteSilver;
14171         else selection = (ChessSquare)((int)piece - 1);
14172         goto defaultlabel;
14173
14174       case DemotePiece:
14175         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14176            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14177             selection = (ChessSquare) (DEMOTED piece);
14178         } else if(piece == EmptySquare) selection = BlackSilver;
14179         else selection = (ChessSquare)((int)piece + 1);
14180         goto defaultlabel;
14181
14182       case WhiteQueen:
14183       case BlackQueen:
14184         if(gameInfo.variant == VariantShatranj ||
14185            gameInfo.variant == VariantXiangqi  ||
14186            gameInfo.variant == VariantCourier  ||
14187            gameInfo.variant == VariantMakruk     )
14188             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14189         goto defaultlabel;
14190
14191       case WhiteKing:
14192       case BlackKing:
14193         if(gameInfo.variant == VariantXiangqi)
14194             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14195         if(gameInfo.variant == VariantKnightmate)
14196             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14197       default:
14198         defaultlabel:
14199         if (gameMode == IcsExamining) {
14200             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14201             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14202                      PieceToChar(selection), AAA + x, ONE + y);
14203             SendToICS(buf);
14204         } else {
14205             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14206                 int n;
14207                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14208                     n = PieceToNumber(selection - BlackPawn);
14209                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14210                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14211                     boards[0][BOARD_HEIGHT-1-n][1]++;
14212                 } else
14213                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14214                     n = PieceToNumber(selection);
14215                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14216                     boards[0][n][BOARD_WIDTH-1] = selection;
14217                     boards[0][n][BOARD_WIDTH-2]++;
14218                 }
14219             } else
14220             boards[0][y][x] = selection;
14221             DrawPosition(TRUE, boards[0]);
14222             ClearHighlights();
14223             fromX = fromY = -1;
14224         }
14225         break;
14226     }
14227 }
14228
14229
14230 void
14231 DropMenuEvent (ChessSquare selection, int x, int y)
14232 {
14233     ChessMove moveType;
14234
14235     switch (gameMode) {
14236       case IcsPlayingWhite:
14237       case MachinePlaysBlack:
14238         if (!WhiteOnMove(currentMove)) {
14239             DisplayMoveError(_("It is Black's turn"));
14240             return;
14241         }
14242         moveType = WhiteDrop;
14243         break;
14244       case IcsPlayingBlack:
14245       case MachinePlaysWhite:
14246         if (WhiteOnMove(currentMove)) {
14247             DisplayMoveError(_("It is White's turn"));
14248             return;
14249         }
14250         moveType = BlackDrop;
14251         break;
14252       case EditGame:
14253         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14254         break;
14255       default:
14256         return;
14257     }
14258
14259     if (moveType == BlackDrop && selection < BlackPawn) {
14260       selection = (ChessSquare) ((int) selection
14261                                  + (int) BlackPawn - (int) WhitePawn);
14262     }
14263     if (boards[currentMove][y][x] != EmptySquare) {
14264         DisplayMoveError(_("That square is occupied"));
14265         return;
14266     }
14267
14268     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14269 }
14270
14271 void
14272 AcceptEvent ()
14273 {
14274     /* Accept a pending offer of any kind from opponent */
14275
14276     if (appData.icsActive) {
14277         SendToICS(ics_prefix);
14278         SendToICS("accept\n");
14279     } else if (cmailMsgLoaded) {
14280         if (currentMove == cmailOldMove &&
14281             commentList[cmailOldMove] != NULL &&
14282             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14283                    "Black offers a draw" : "White offers a draw")) {
14284             TruncateGame();
14285             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14286             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14287         } else {
14288             DisplayError(_("There is no pending offer on this move"), 0);
14289             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14290         }
14291     } else {
14292         /* Not used for offers from chess program */
14293     }
14294 }
14295
14296 void
14297 DeclineEvent ()
14298 {
14299     /* Decline a pending offer of any kind from opponent */
14300
14301     if (appData.icsActive) {
14302         SendToICS(ics_prefix);
14303         SendToICS("decline\n");
14304     } else if (cmailMsgLoaded) {
14305         if (currentMove == cmailOldMove &&
14306             commentList[cmailOldMove] != NULL &&
14307             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14308                    "Black offers a draw" : "White offers a draw")) {
14309 #ifdef NOTDEF
14310             AppendComment(cmailOldMove, "Draw declined", TRUE);
14311             DisplayComment(cmailOldMove - 1, "Draw declined");
14312 #endif /*NOTDEF*/
14313         } else {
14314             DisplayError(_("There is no pending offer on this move"), 0);
14315         }
14316     } else {
14317         /* Not used for offers from chess program */
14318     }
14319 }
14320
14321 void
14322 RematchEvent ()
14323 {
14324     /* Issue ICS rematch command */
14325     if (appData.icsActive) {
14326         SendToICS(ics_prefix);
14327         SendToICS("rematch\n");
14328     }
14329 }
14330
14331 void
14332 CallFlagEvent ()
14333 {
14334     /* Call your opponent's flag (claim a win on time) */
14335     if (appData.icsActive) {
14336         SendToICS(ics_prefix);
14337         SendToICS("flag\n");
14338     } else {
14339         switch (gameMode) {
14340           default:
14341             return;
14342           case MachinePlaysWhite:
14343             if (whiteFlag) {
14344                 if (blackFlag)
14345                   GameEnds(GameIsDrawn, "Both players ran out of time",
14346                            GE_PLAYER);
14347                 else
14348                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14349             } else {
14350                 DisplayError(_("Your opponent is not out of time"), 0);
14351             }
14352             break;
14353           case MachinePlaysBlack:
14354             if (blackFlag) {
14355                 if (whiteFlag)
14356                   GameEnds(GameIsDrawn, "Both players ran out of time",
14357                            GE_PLAYER);
14358                 else
14359                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14360             } else {
14361                 DisplayError(_("Your opponent is not out of time"), 0);
14362             }
14363             break;
14364         }
14365     }
14366 }
14367
14368 void
14369 ClockClick (int which)
14370 {       // [HGM] code moved to back-end from winboard.c
14371         if(which) { // black clock
14372           if (gameMode == EditPosition || gameMode == IcsExamining) {
14373             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14374             SetBlackToPlayEvent();
14375           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14376           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14377           } else if (shiftKey) {
14378             AdjustClock(which, -1);
14379           } else if (gameMode == IcsPlayingWhite ||
14380                      gameMode == MachinePlaysBlack) {
14381             CallFlagEvent();
14382           }
14383         } else { // white clock
14384           if (gameMode == EditPosition || gameMode == IcsExamining) {
14385             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14386             SetWhiteToPlayEvent();
14387           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14388           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14389           } else if (shiftKey) {
14390             AdjustClock(which, -1);
14391           } else if (gameMode == IcsPlayingBlack ||
14392                    gameMode == MachinePlaysWhite) {
14393             CallFlagEvent();
14394           }
14395         }
14396 }
14397
14398 void
14399 DrawEvent ()
14400 {
14401     /* Offer draw or accept pending draw offer from opponent */
14402
14403     if (appData.icsActive) {
14404         /* Note: tournament rules require draw offers to be
14405            made after you make your move but before you punch
14406            your clock.  Currently ICS doesn't let you do that;
14407            instead, you immediately punch your clock after making
14408            a move, but you can offer a draw at any time. */
14409
14410         SendToICS(ics_prefix);
14411         SendToICS("draw\n");
14412         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14413     } else if (cmailMsgLoaded) {
14414         if (currentMove == cmailOldMove &&
14415             commentList[cmailOldMove] != NULL &&
14416             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14417                    "Black offers a draw" : "White offers a draw")) {
14418             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14419             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14420         } else if (currentMove == cmailOldMove + 1) {
14421             char *offer = WhiteOnMove(cmailOldMove) ?
14422               "White offers a draw" : "Black offers a draw";
14423             AppendComment(currentMove, offer, TRUE);
14424             DisplayComment(currentMove - 1, offer);
14425             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14426         } else {
14427             DisplayError(_("You must make your move before offering a draw"), 0);
14428             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14429         }
14430     } else if (first.offeredDraw) {
14431         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14432     } else {
14433         if (first.sendDrawOffers) {
14434             SendToProgram("draw\n", &first);
14435             userOfferedDraw = TRUE;
14436         }
14437     }
14438 }
14439
14440 void
14441 AdjournEvent ()
14442 {
14443     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14444
14445     if (appData.icsActive) {
14446         SendToICS(ics_prefix);
14447         SendToICS("adjourn\n");
14448     } else {
14449         /* Currently GNU Chess doesn't offer or accept Adjourns */
14450     }
14451 }
14452
14453
14454 void
14455 AbortEvent ()
14456 {
14457     /* Offer Abort or accept pending Abort offer from opponent */
14458
14459     if (appData.icsActive) {
14460         SendToICS(ics_prefix);
14461         SendToICS("abort\n");
14462     } else {
14463         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14464     }
14465 }
14466
14467 void
14468 ResignEvent ()
14469 {
14470     /* Resign.  You can do this even if it's not your turn. */
14471
14472     if (appData.icsActive) {
14473         SendToICS(ics_prefix);
14474         SendToICS("resign\n");
14475     } else {
14476         switch (gameMode) {
14477           case MachinePlaysWhite:
14478             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14479             break;
14480           case MachinePlaysBlack:
14481             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14482             break;
14483           case EditGame:
14484             if (cmailMsgLoaded) {
14485                 TruncateGame();
14486                 if (WhiteOnMove(cmailOldMove)) {
14487                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14488                 } else {
14489                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14490                 }
14491                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14492             }
14493             break;
14494           default:
14495             break;
14496         }
14497     }
14498 }
14499
14500
14501 void
14502 StopObservingEvent ()
14503 {
14504     /* Stop observing current games */
14505     SendToICS(ics_prefix);
14506     SendToICS("unobserve\n");
14507 }
14508
14509 void
14510 StopExaminingEvent ()
14511 {
14512     /* Stop observing current game */
14513     SendToICS(ics_prefix);
14514     SendToICS("unexamine\n");
14515 }
14516
14517 void
14518 ForwardInner (int target)
14519 {
14520     int limit; int oldSeekGraphUp = seekGraphUp;
14521
14522     if (appData.debugMode)
14523         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14524                 target, currentMove, forwardMostMove);
14525
14526     if (gameMode == EditPosition)
14527       return;
14528
14529     seekGraphUp = FALSE;
14530     MarkTargetSquares(1);
14531
14532     if (gameMode == PlayFromGameFile && !pausing)
14533       PauseEvent();
14534
14535     if (gameMode == IcsExamining && pausing)
14536       limit = pauseExamForwardMostMove;
14537     else
14538       limit = forwardMostMove;
14539
14540     if (target > limit) target = limit;
14541
14542     if (target > 0 && moveList[target - 1][0]) {
14543         int fromX, fromY, toX, toY;
14544         toX = moveList[target - 1][2] - AAA;
14545         toY = moveList[target - 1][3] - ONE;
14546         if (moveList[target - 1][1] == '@') {
14547             if (appData.highlightLastMove) {
14548                 SetHighlights(-1, -1, toX, toY);
14549             }
14550         } else {
14551             fromX = moveList[target - 1][0] - AAA;
14552             fromY = moveList[target - 1][1] - ONE;
14553             if (target == currentMove + 1) {
14554                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14555             }
14556             if (appData.highlightLastMove) {
14557                 SetHighlights(fromX, fromY, toX, toY);
14558             }
14559         }
14560     }
14561     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14562         gameMode == Training || gameMode == PlayFromGameFile ||
14563         gameMode == AnalyzeFile) {
14564         while (currentMove < target) {
14565             SendMoveToProgram(currentMove++, &first);
14566         }
14567     } else {
14568         currentMove = target;
14569     }
14570
14571     if (gameMode == EditGame || gameMode == EndOfGame) {
14572         whiteTimeRemaining = timeRemaining[0][currentMove];
14573         blackTimeRemaining = timeRemaining[1][currentMove];
14574     }
14575     DisplayBothClocks();
14576     DisplayMove(currentMove - 1);
14577     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14578     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14579     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14580         DisplayComment(currentMove - 1, commentList[currentMove]);
14581     }
14582     ClearMap(); // [HGM] exclude: invalidate map
14583 }
14584
14585
14586 void
14587 ForwardEvent ()
14588 {
14589     if (gameMode == IcsExamining && !pausing) {
14590         SendToICS(ics_prefix);
14591         SendToICS("forward\n");
14592     } else {
14593         ForwardInner(currentMove + 1);
14594     }
14595 }
14596
14597 void
14598 ToEndEvent ()
14599 {
14600     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14601         /* to optimze, we temporarily turn off analysis mode while we feed
14602          * the remaining moves to the engine. Otherwise we get analysis output
14603          * after each move.
14604          */
14605         if (first.analysisSupport) {
14606           SendToProgram("exit\nforce\n", &first);
14607           first.analyzing = FALSE;
14608         }
14609     }
14610
14611     if (gameMode == IcsExamining && !pausing) {
14612         SendToICS(ics_prefix);
14613         SendToICS("forward 999999\n");
14614     } else {
14615         ForwardInner(forwardMostMove);
14616     }
14617
14618     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14619         /* we have fed all the moves, so reactivate analysis mode */
14620         SendToProgram("analyze\n", &first);
14621         first.analyzing = TRUE;
14622         /*first.maybeThinking = TRUE;*/
14623         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14624     }
14625 }
14626
14627 void
14628 BackwardInner (int target)
14629 {
14630     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14631
14632     if (appData.debugMode)
14633         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14634                 target, currentMove, forwardMostMove);
14635
14636     if (gameMode == EditPosition) return;
14637     seekGraphUp = FALSE;
14638     MarkTargetSquares(1);
14639     if (currentMove <= backwardMostMove) {
14640         ClearHighlights();
14641         DrawPosition(full_redraw, boards[currentMove]);
14642         return;
14643     }
14644     if (gameMode == PlayFromGameFile && !pausing)
14645       PauseEvent();
14646
14647     if (moveList[target][0]) {
14648         int fromX, fromY, toX, toY;
14649         toX = moveList[target][2] - AAA;
14650         toY = moveList[target][3] - ONE;
14651         if (moveList[target][1] == '@') {
14652             if (appData.highlightLastMove) {
14653                 SetHighlights(-1, -1, toX, toY);
14654             }
14655         } else {
14656             fromX = moveList[target][0] - AAA;
14657             fromY = moveList[target][1] - ONE;
14658             if (target == currentMove - 1) {
14659                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14660             }
14661             if (appData.highlightLastMove) {
14662                 SetHighlights(fromX, fromY, toX, toY);
14663             }
14664         }
14665     }
14666     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14667         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14668         while (currentMove > target) {
14669             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14670                 // null move cannot be undone. Reload program with move history before it.
14671                 int i;
14672                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14673                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14674                 }
14675                 SendBoard(&first, i); 
14676                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14677                 break;
14678             }
14679             SendToProgram("undo\n", &first);
14680             currentMove--;
14681         }
14682     } else {
14683         currentMove = target;
14684     }
14685
14686     if (gameMode == EditGame || gameMode == EndOfGame) {
14687         whiteTimeRemaining = timeRemaining[0][currentMove];
14688         blackTimeRemaining = timeRemaining[1][currentMove];
14689     }
14690     DisplayBothClocks();
14691     DisplayMove(currentMove - 1);
14692     DrawPosition(full_redraw, boards[currentMove]);
14693     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14694     // [HGM] PV info: routine tests if comment empty
14695     DisplayComment(currentMove - 1, commentList[currentMove]);
14696     ClearMap(); // [HGM] exclude: invalidate map
14697 }
14698
14699 void
14700 BackwardEvent ()
14701 {
14702     if (gameMode == IcsExamining && !pausing) {
14703         SendToICS(ics_prefix);
14704         SendToICS("backward\n");
14705     } else {
14706         BackwardInner(currentMove - 1);
14707     }
14708 }
14709
14710 void
14711 ToStartEvent ()
14712 {
14713     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14714         /* to optimize, we temporarily turn off analysis mode while we undo
14715          * all the moves. Otherwise we get analysis output after each undo.
14716          */
14717         if (first.analysisSupport) {
14718           SendToProgram("exit\nforce\n", &first);
14719           first.analyzing = FALSE;
14720         }
14721     }
14722
14723     if (gameMode == IcsExamining && !pausing) {
14724         SendToICS(ics_prefix);
14725         SendToICS("backward 999999\n");
14726     } else {
14727         BackwardInner(backwardMostMove);
14728     }
14729
14730     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14731         /* we have fed all the moves, so reactivate analysis mode */
14732         SendToProgram("analyze\n", &first);
14733         first.analyzing = TRUE;
14734         /*first.maybeThinking = TRUE;*/
14735         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14736     }
14737 }
14738
14739 void
14740 ToNrEvent (int to)
14741 {
14742   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14743   if (to >= forwardMostMove) to = forwardMostMove;
14744   if (to <= backwardMostMove) to = backwardMostMove;
14745   if (to < currentMove) {
14746     BackwardInner(to);
14747   } else {
14748     ForwardInner(to);
14749   }
14750 }
14751
14752 void
14753 RevertEvent (Boolean annotate)
14754 {
14755     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14756         return;
14757     }
14758     if (gameMode != IcsExamining) {
14759         DisplayError(_("You are not examining a game"), 0);
14760         return;
14761     }
14762     if (pausing) {
14763         DisplayError(_("You can't revert while pausing"), 0);
14764         return;
14765     }
14766     SendToICS(ics_prefix);
14767     SendToICS("revert\n");
14768 }
14769
14770 void
14771 RetractMoveEvent ()
14772 {
14773     switch (gameMode) {
14774       case MachinePlaysWhite:
14775       case MachinePlaysBlack:
14776         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14777             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14778             return;
14779         }
14780         if (forwardMostMove < 2) return;
14781         currentMove = forwardMostMove = forwardMostMove - 2;
14782         whiteTimeRemaining = timeRemaining[0][currentMove];
14783         blackTimeRemaining = timeRemaining[1][currentMove];
14784         DisplayBothClocks();
14785         DisplayMove(currentMove - 1);
14786         ClearHighlights();/*!! could figure this out*/
14787         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14788         SendToProgram("remove\n", &first);
14789         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14790         break;
14791
14792       case BeginningOfGame:
14793       default:
14794         break;
14795
14796       case IcsPlayingWhite:
14797       case IcsPlayingBlack:
14798         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14799             SendToICS(ics_prefix);
14800             SendToICS("takeback 2\n");
14801         } else {
14802             SendToICS(ics_prefix);
14803             SendToICS("takeback 1\n");
14804         }
14805         break;
14806     }
14807 }
14808
14809 void
14810 MoveNowEvent ()
14811 {
14812     ChessProgramState *cps;
14813
14814     switch (gameMode) {
14815       case MachinePlaysWhite:
14816         if (!WhiteOnMove(forwardMostMove)) {
14817             DisplayError(_("It is your turn"), 0);
14818             return;
14819         }
14820         cps = &first;
14821         break;
14822       case MachinePlaysBlack:
14823         if (WhiteOnMove(forwardMostMove)) {
14824             DisplayError(_("It is your turn"), 0);
14825             return;
14826         }
14827         cps = &first;
14828         break;
14829       case TwoMachinesPlay:
14830         if (WhiteOnMove(forwardMostMove) ==
14831             (first.twoMachinesColor[0] == 'w')) {
14832             cps = &first;
14833         } else {
14834             cps = &second;
14835         }
14836         break;
14837       case BeginningOfGame:
14838       default:
14839         return;
14840     }
14841     SendToProgram("?\n", cps);
14842 }
14843
14844 void
14845 TruncateGameEvent ()
14846 {
14847     EditGameEvent();
14848     if (gameMode != EditGame) return;
14849     TruncateGame();
14850 }
14851
14852 void
14853 TruncateGame ()
14854 {
14855     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14856     if (forwardMostMove > currentMove) {
14857         if (gameInfo.resultDetails != NULL) {
14858             free(gameInfo.resultDetails);
14859             gameInfo.resultDetails = NULL;
14860             gameInfo.result = GameUnfinished;
14861         }
14862         forwardMostMove = currentMove;
14863         HistorySet(parseList, backwardMostMove, forwardMostMove,
14864                    currentMove-1);
14865     }
14866 }
14867
14868 void
14869 HintEvent ()
14870 {
14871     if (appData.noChessProgram) return;
14872     switch (gameMode) {
14873       case MachinePlaysWhite:
14874         if (WhiteOnMove(forwardMostMove)) {
14875             DisplayError(_("Wait until your turn"), 0);
14876             return;
14877         }
14878         break;
14879       case BeginningOfGame:
14880       case MachinePlaysBlack:
14881         if (!WhiteOnMove(forwardMostMove)) {
14882             DisplayError(_("Wait until your turn"), 0);
14883             return;
14884         }
14885         break;
14886       default:
14887         DisplayError(_("No hint available"), 0);
14888         return;
14889     }
14890     SendToProgram("hint\n", &first);
14891     hintRequested = TRUE;
14892 }
14893
14894 void
14895 BookEvent ()
14896 {
14897     if (appData.noChessProgram) return;
14898     switch (gameMode) {
14899       case MachinePlaysWhite:
14900         if (WhiteOnMove(forwardMostMove)) {
14901             DisplayError(_("Wait until your turn"), 0);
14902             return;
14903         }
14904         break;
14905       case BeginningOfGame:
14906       case MachinePlaysBlack:
14907         if (!WhiteOnMove(forwardMostMove)) {
14908             DisplayError(_("Wait until your turn"), 0);
14909             return;
14910         }
14911         break;
14912       case EditPosition:
14913         EditPositionDone(TRUE);
14914         break;
14915       case TwoMachinesPlay:
14916         return;
14917       default:
14918         break;
14919     }
14920     SendToProgram("bk\n", &first);
14921     bookOutput[0] = NULLCHAR;
14922     bookRequested = TRUE;
14923 }
14924
14925 void
14926 AboutGameEvent ()
14927 {
14928     char *tags = PGNTags(&gameInfo);
14929     TagsPopUp(tags, CmailMsg());
14930     free(tags);
14931 }
14932
14933 /* end button procedures */
14934
14935 void
14936 PrintPosition (FILE *fp, int move)
14937 {
14938     int i, j;
14939
14940     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14941         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14942             char c = PieceToChar(boards[move][i][j]);
14943             fputc(c == 'x' ? '.' : c, fp);
14944             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14945         }
14946     }
14947     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14948       fprintf(fp, "white to play\n");
14949     else
14950       fprintf(fp, "black to play\n");
14951 }
14952
14953 void
14954 PrintOpponents (FILE *fp)
14955 {
14956     if (gameInfo.white != NULL) {
14957         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14958     } else {
14959         fprintf(fp, "\n");
14960     }
14961 }
14962
14963 /* Find last component of program's own name, using some heuristics */
14964 void
14965 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14966 {
14967     char *p, *q, c;
14968     int local = (strcmp(host, "localhost") == 0);
14969     while (!local && (p = strchr(prog, ';')) != NULL) {
14970         p++;
14971         while (*p == ' ') p++;
14972         prog = p;
14973     }
14974     if (*prog == '"' || *prog == '\'') {
14975         q = strchr(prog + 1, *prog);
14976     } else {
14977         q = strchr(prog, ' ');
14978     }
14979     if (q == NULL) q = prog + strlen(prog);
14980     p = q;
14981     while (p >= prog && *p != '/' && *p != '\\') p--;
14982     p++;
14983     if(p == prog && *p == '"') p++;
14984     c = *q; *q = 0;
14985     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14986     memcpy(buf, p, q - p);
14987     buf[q - p] = NULLCHAR;
14988     if (!local) {
14989         strcat(buf, "@");
14990         strcat(buf, host);
14991     }
14992 }
14993
14994 char *
14995 TimeControlTagValue ()
14996 {
14997     char buf[MSG_SIZ];
14998     if (!appData.clockMode) {
14999       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15000     } else if (movesPerSession > 0) {
15001       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15002     } else if (timeIncrement == 0) {
15003       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15004     } else {
15005       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15006     }
15007     return StrSave(buf);
15008 }
15009
15010 void
15011 SetGameInfo ()
15012 {
15013     /* This routine is used only for certain modes */
15014     VariantClass v = gameInfo.variant;
15015     ChessMove r = GameUnfinished;
15016     char *p = NULL;
15017
15018     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15019         r = gameInfo.result;
15020         p = gameInfo.resultDetails;
15021         gameInfo.resultDetails = NULL;
15022     }
15023     ClearGameInfo(&gameInfo);
15024     gameInfo.variant = v;
15025
15026     switch (gameMode) {
15027       case MachinePlaysWhite:
15028         gameInfo.event = StrSave( appData.pgnEventHeader );
15029         gameInfo.site = StrSave(HostName());
15030         gameInfo.date = PGNDate();
15031         gameInfo.round = StrSave("-");
15032         gameInfo.white = StrSave(first.tidy);
15033         gameInfo.black = StrSave(UserName());
15034         gameInfo.timeControl = TimeControlTagValue();
15035         break;
15036
15037       case MachinePlaysBlack:
15038         gameInfo.event = StrSave( appData.pgnEventHeader );
15039         gameInfo.site = StrSave(HostName());
15040         gameInfo.date = PGNDate();
15041         gameInfo.round = StrSave("-");
15042         gameInfo.white = StrSave(UserName());
15043         gameInfo.black = StrSave(first.tidy);
15044         gameInfo.timeControl = TimeControlTagValue();
15045         break;
15046
15047       case TwoMachinesPlay:
15048         gameInfo.event = StrSave( appData.pgnEventHeader );
15049         gameInfo.site = StrSave(HostName());
15050         gameInfo.date = PGNDate();
15051         if (roundNr > 0) {
15052             char buf[MSG_SIZ];
15053             snprintf(buf, MSG_SIZ, "%d", roundNr);
15054             gameInfo.round = StrSave(buf);
15055         } else {
15056             gameInfo.round = StrSave("-");
15057         }
15058         if (first.twoMachinesColor[0] == 'w') {
15059             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15060             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15061         } else {
15062             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15063             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15064         }
15065         gameInfo.timeControl = TimeControlTagValue();
15066         break;
15067
15068       case EditGame:
15069         gameInfo.event = StrSave("Edited game");
15070         gameInfo.site = StrSave(HostName());
15071         gameInfo.date = PGNDate();
15072         gameInfo.round = StrSave("-");
15073         gameInfo.white = StrSave("-");
15074         gameInfo.black = StrSave("-");
15075         gameInfo.result = r;
15076         gameInfo.resultDetails = p;
15077         break;
15078
15079       case EditPosition:
15080         gameInfo.event = StrSave("Edited position");
15081         gameInfo.site = StrSave(HostName());
15082         gameInfo.date = PGNDate();
15083         gameInfo.round = StrSave("-");
15084         gameInfo.white = StrSave("-");
15085         gameInfo.black = StrSave("-");
15086         break;
15087
15088       case IcsPlayingWhite:
15089       case IcsPlayingBlack:
15090       case IcsObserving:
15091       case IcsExamining:
15092         break;
15093
15094       case PlayFromGameFile:
15095         gameInfo.event = StrSave("Game from non-PGN file");
15096         gameInfo.site = StrSave(HostName());
15097         gameInfo.date = PGNDate();
15098         gameInfo.round = StrSave("-");
15099         gameInfo.white = StrSave("?");
15100         gameInfo.black = StrSave("?");
15101         break;
15102
15103       default:
15104         break;
15105     }
15106 }
15107
15108 void
15109 ReplaceComment (int index, char *text)
15110 {
15111     int len;
15112     char *p;
15113     float score;
15114
15115     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15116        pvInfoList[index-1].depth == len &&
15117        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15118        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15119     while (*text == '\n') text++;
15120     len = strlen(text);
15121     while (len > 0 && text[len - 1] == '\n') len--;
15122
15123     if (commentList[index] != NULL)
15124       free(commentList[index]);
15125
15126     if (len == 0) {
15127         commentList[index] = NULL;
15128         return;
15129     }
15130   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15131       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15132       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15133     commentList[index] = (char *) malloc(len + 2);
15134     strncpy(commentList[index], text, len);
15135     commentList[index][len] = '\n';
15136     commentList[index][len + 1] = NULLCHAR;
15137   } else {
15138     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15139     char *p;
15140     commentList[index] = (char *) malloc(len + 7);
15141     safeStrCpy(commentList[index], "{\n", 3);
15142     safeStrCpy(commentList[index]+2, text, len+1);
15143     commentList[index][len+2] = NULLCHAR;
15144     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15145     strcat(commentList[index], "\n}\n");
15146   }
15147 }
15148
15149 void
15150 CrushCRs (char *text)
15151 {
15152   char *p = text;
15153   char *q = text;
15154   char ch;
15155
15156   do {
15157     ch = *p++;
15158     if (ch == '\r') continue;
15159     *q++ = ch;
15160   } while (ch != '\0');
15161 }
15162
15163 void
15164 AppendComment (int index, char *text, Boolean addBraces)
15165 /* addBraces  tells if we should add {} */
15166 {
15167     int oldlen, len;
15168     char *old;
15169
15170 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15171     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15172
15173     CrushCRs(text);
15174     while (*text == '\n') text++;
15175     len = strlen(text);
15176     while (len > 0 && text[len - 1] == '\n') len--;
15177     text[len] = NULLCHAR;
15178
15179     if (len == 0) return;
15180
15181     if (commentList[index] != NULL) {
15182       Boolean addClosingBrace = addBraces;
15183         old = commentList[index];
15184         oldlen = strlen(old);
15185         while(commentList[index][oldlen-1] ==  '\n')
15186           commentList[index][--oldlen] = NULLCHAR;
15187         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15188         safeStrCpy(commentList[index], old, oldlen + len + 6);
15189         free(old);
15190         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15191         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15192           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15193           while (*text == '\n') { text++; len--; }
15194           commentList[index][--oldlen] = NULLCHAR;
15195       }
15196         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15197         else          strcat(commentList[index], "\n");
15198         strcat(commentList[index], text);
15199         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15200         else          strcat(commentList[index], "\n");
15201     } else {
15202         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15203         if(addBraces)
15204           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15205         else commentList[index][0] = NULLCHAR;
15206         strcat(commentList[index], text);
15207         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15208         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15209     }
15210 }
15211
15212 static char *
15213 FindStr (char * text, char * sub_text)
15214 {
15215     char * result = strstr( text, sub_text );
15216
15217     if( result != NULL ) {
15218         result += strlen( sub_text );
15219     }
15220
15221     return result;
15222 }
15223
15224 /* [AS] Try to extract PV info from PGN comment */
15225 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15226 char *
15227 GetInfoFromComment (int index, char * text)
15228 {
15229     char * sep = text, *p;
15230
15231     if( text != NULL && index > 0 ) {
15232         int score = 0;
15233         int depth = 0;
15234         int time = -1, sec = 0, deci;
15235         char * s_eval = FindStr( text, "[%eval " );
15236         char * s_emt = FindStr( text, "[%emt " );
15237
15238         if( s_eval != NULL || s_emt != NULL ) {
15239             /* New style */
15240             char delim;
15241
15242             if( s_eval != NULL ) {
15243                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15244                     return text;
15245                 }
15246
15247                 if( delim != ']' ) {
15248                     return text;
15249                 }
15250             }
15251
15252             if( s_emt != NULL ) {
15253             }
15254                 return text;
15255         }
15256         else {
15257             /* We expect something like: [+|-]nnn.nn/dd */
15258             int score_lo = 0;
15259
15260             if(*text != '{') return text; // [HGM] braces: must be normal comment
15261
15262             sep = strchr( text, '/' );
15263             if( sep == NULL || sep < (text+4) ) {
15264                 return text;
15265             }
15266
15267             p = text;
15268             if(p[1] == '(') { // comment starts with PV
15269                p = strchr(p, ')'); // locate end of PV
15270                if(p == NULL || sep < p+5) return text;
15271                // at this point we have something like "{(.*) +0.23/6 ..."
15272                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15273                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15274                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15275             }
15276             time = -1; sec = -1; deci = -1;
15277             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15278                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15279                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15280                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15281                 return text;
15282             }
15283
15284             if( score_lo < 0 || score_lo >= 100 ) {
15285                 return text;
15286             }
15287
15288             if(sec >= 0) time = 600*time + 10*sec; else
15289             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15290
15291             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15292
15293             /* [HGM] PV time: now locate end of PV info */
15294             while( *++sep >= '0' && *sep <= '9'); // strip depth
15295             if(time >= 0)
15296             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15297             if(sec >= 0)
15298             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15299             if(deci >= 0)
15300             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15301             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15302         }
15303
15304         if( depth <= 0 ) {
15305             return text;
15306         }
15307
15308         if( time < 0 ) {
15309             time = -1;
15310         }
15311
15312         pvInfoList[index-1].depth = depth;
15313         pvInfoList[index-1].score = score;
15314         pvInfoList[index-1].time  = 10*time; // centi-sec
15315         if(*sep == '}') *sep = 0; else *--sep = '{';
15316         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15317     }
15318     return sep;
15319 }
15320
15321 void
15322 SendToProgram (char *message, ChessProgramState *cps)
15323 {
15324     int count, outCount, error;
15325     char buf[MSG_SIZ];
15326
15327     if (cps->pr == NoProc) return;
15328     Attention(cps);
15329
15330     if (appData.debugMode) {
15331         TimeMark now;
15332         GetTimeMark(&now);
15333         fprintf(debugFP, "%ld >%-6s: %s",
15334                 SubtractTimeMarks(&now, &programStartTime),
15335                 cps->which, message);
15336         if(serverFP)
15337             fprintf(serverFP, "%ld >%-6s: %s",
15338                 SubtractTimeMarks(&now, &programStartTime),
15339                 cps->which, message), fflush(serverFP);
15340     }
15341
15342     count = strlen(message);
15343     outCount = OutputToProcess(cps->pr, message, count, &error);
15344     if (outCount < count && !exiting
15345                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15346       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15347       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15348         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15349             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15350                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15351                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15352                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15353             } else {
15354                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15355                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15356                 gameInfo.result = res;
15357             }
15358             gameInfo.resultDetails = StrSave(buf);
15359         }
15360         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15361         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15362     }
15363 }
15364
15365 void
15366 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15367 {
15368     char *end_str;
15369     char buf[MSG_SIZ];
15370     ChessProgramState *cps = (ChessProgramState *)closure;
15371
15372     if (isr != cps->isr) return; /* Killed intentionally */
15373     if (count <= 0) {
15374         if (count == 0) {
15375             RemoveInputSource(cps->isr);
15376             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15377                     _(cps->which), cps->program);
15378             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15379             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15380                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15381                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15382                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15383                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15384                 } else {
15385                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15386                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15387                     gameInfo.result = res;
15388                 }
15389                 gameInfo.resultDetails = StrSave(buf);
15390             }
15391             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15392             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15393         } else {
15394             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15395                     _(cps->which), cps->program);
15396             RemoveInputSource(cps->isr);
15397
15398             /* [AS] Program is misbehaving badly... kill it */
15399             if( count == -2 ) {
15400                 DestroyChildProcess( cps->pr, 9 );
15401                 cps->pr = NoProc;
15402             }
15403
15404             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15405         }
15406         return;
15407     }
15408
15409     if ((end_str = strchr(message, '\r')) != NULL)
15410       *end_str = NULLCHAR;
15411     if ((end_str = strchr(message, '\n')) != NULL)
15412       *end_str = NULLCHAR;
15413
15414     if (appData.debugMode) {
15415         TimeMark now; int print = 1;
15416         char *quote = ""; char c; int i;
15417
15418         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15419                 char start = message[0];
15420                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15421                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15422                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15423                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15424                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15425                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15426                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15427                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15428                    sscanf(message, "hint: %c", &c)!=1 && 
15429                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15430                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15431                     print = (appData.engineComments >= 2);
15432                 }
15433                 message[0] = start; // restore original message
15434         }
15435         if(print) {
15436                 GetTimeMark(&now);
15437                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15438                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15439                         quote,
15440                         message);
15441                 if(serverFP)
15442                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15443                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15444                         quote,
15445                         message), fflush(serverFP);
15446         }
15447     }
15448
15449     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15450     if (appData.icsEngineAnalyze) {
15451         if (strstr(message, "whisper") != NULL ||
15452              strstr(message, "kibitz") != NULL ||
15453             strstr(message, "tellics") != NULL) return;
15454     }
15455
15456     HandleMachineMove(message, cps);
15457 }
15458
15459
15460 void
15461 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15462 {
15463     char buf[MSG_SIZ];
15464     int seconds;
15465
15466     if( timeControl_2 > 0 ) {
15467         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15468             tc = timeControl_2;
15469         }
15470     }
15471     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15472     inc /= cps->timeOdds;
15473     st  /= cps->timeOdds;
15474
15475     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15476
15477     if (st > 0) {
15478       /* Set exact time per move, normally using st command */
15479       if (cps->stKludge) {
15480         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15481         seconds = st % 60;
15482         if (seconds == 0) {
15483           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15484         } else {
15485           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15486         }
15487       } else {
15488         snprintf(buf, MSG_SIZ, "st %d\n", st);
15489       }
15490     } else {
15491       /* Set conventional or incremental time control, using level command */
15492       if (seconds == 0) {
15493         /* Note old gnuchess bug -- minutes:seconds used to not work.
15494            Fixed in later versions, but still avoid :seconds
15495            when seconds is 0. */
15496         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15497       } else {
15498         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15499                  seconds, inc/1000.);
15500       }
15501     }
15502     SendToProgram(buf, cps);
15503
15504     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15505     /* Orthogonally, limit search to given depth */
15506     if (sd > 0) {
15507       if (cps->sdKludge) {
15508         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15509       } else {
15510         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15511       }
15512       SendToProgram(buf, cps);
15513     }
15514
15515     if(cps->nps >= 0) { /* [HGM] nps */
15516         if(cps->supportsNPS == FALSE)
15517           cps->nps = -1; // don't use if engine explicitly says not supported!
15518         else {
15519           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15520           SendToProgram(buf, cps);
15521         }
15522     }
15523 }
15524
15525 ChessProgramState *
15526 WhitePlayer ()
15527 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15528 {
15529     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15530        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15531         return &second;
15532     return &first;
15533 }
15534
15535 void
15536 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15537 {
15538     char message[MSG_SIZ];
15539     long time, otime;
15540
15541     /* Note: this routine must be called when the clocks are stopped
15542        or when they have *just* been set or switched; otherwise
15543        it will be off by the time since the current tick started.
15544     */
15545     if (machineWhite) {
15546         time = whiteTimeRemaining / 10;
15547         otime = blackTimeRemaining / 10;
15548     } else {
15549         time = blackTimeRemaining / 10;
15550         otime = whiteTimeRemaining / 10;
15551     }
15552     /* [HGM] translate opponent's time by time-odds factor */
15553     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15554
15555     if (time <= 0) time = 1;
15556     if (otime <= 0) otime = 1;
15557
15558     snprintf(message, MSG_SIZ, "time %ld\n", time);
15559     SendToProgram(message, cps);
15560
15561     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15562     SendToProgram(message, cps);
15563 }
15564
15565 int
15566 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15567 {
15568   char buf[MSG_SIZ];
15569   int len = strlen(name);
15570   int val;
15571
15572   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15573     (*p) += len + 1;
15574     sscanf(*p, "%d", &val);
15575     *loc = (val != 0);
15576     while (**p && **p != ' ')
15577       (*p)++;
15578     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15579     SendToProgram(buf, cps);
15580     return TRUE;
15581   }
15582   return FALSE;
15583 }
15584
15585 int
15586 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15587 {
15588   char buf[MSG_SIZ];
15589   int len = strlen(name);
15590   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15591     (*p) += len + 1;
15592     sscanf(*p, "%d", loc);
15593     while (**p && **p != ' ') (*p)++;
15594     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15595     SendToProgram(buf, cps);
15596     return TRUE;
15597   }
15598   return FALSE;
15599 }
15600
15601 int
15602 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15603 {
15604   char buf[MSG_SIZ];
15605   int len = strlen(name);
15606   if (strncmp((*p), name, len) == 0
15607       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15608     (*p) += len + 2;
15609     sscanf(*p, "%[^\"]", loc);
15610     while (**p && **p != '\"') (*p)++;
15611     if (**p == '\"') (*p)++;
15612     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15613     SendToProgram(buf, cps);
15614     return TRUE;
15615   }
15616   return FALSE;
15617 }
15618
15619 int
15620 ParseOption (Option *opt, ChessProgramState *cps)
15621 // [HGM] options: process the string that defines an engine option, and determine
15622 // name, type, default value, and allowed value range
15623 {
15624         char *p, *q, buf[MSG_SIZ];
15625         int n, min = (-1)<<31, max = 1<<31, def;
15626
15627         if(p = strstr(opt->name, " -spin ")) {
15628             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15629             if(max < min) max = min; // enforce consistency
15630             if(def < min) def = min;
15631             if(def > max) def = max;
15632             opt->value = def;
15633             opt->min = min;
15634             opt->max = max;
15635             opt->type = Spin;
15636         } else if((p = strstr(opt->name, " -slider "))) {
15637             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15638             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15639             if(max < min) max = min; // enforce consistency
15640             if(def < min) def = min;
15641             if(def > max) def = max;
15642             opt->value = def;
15643             opt->min = min;
15644             opt->max = max;
15645             opt->type = Spin; // Slider;
15646         } else if((p = strstr(opt->name, " -string "))) {
15647             opt->textValue = p+9;
15648             opt->type = TextBox;
15649         } else if((p = strstr(opt->name, " -file "))) {
15650             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15651             opt->textValue = p+7;
15652             opt->type = FileName; // FileName;
15653         } else if((p = strstr(opt->name, " -path "))) {
15654             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15655             opt->textValue = p+7;
15656             opt->type = PathName; // PathName;
15657         } else if(p = strstr(opt->name, " -check ")) {
15658             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15659             opt->value = (def != 0);
15660             opt->type = CheckBox;
15661         } else if(p = strstr(opt->name, " -combo ")) {
15662             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15663             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15664             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15665             opt->value = n = 0;
15666             while(q = StrStr(q, " /// ")) {
15667                 n++; *q = 0;    // count choices, and null-terminate each of them
15668                 q += 5;
15669                 if(*q == '*') { // remember default, which is marked with * prefix
15670                     q++;
15671                     opt->value = n;
15672                 }
15673                 cps->comboList[cps->comboCnt++] = q;
15674             }
15675             cps->comboList[cps->comboCnt++] = NULL;
15676             opt->max = n + 1;
15677             opt->type = ComboBox;
15678         } else if(p = strstr(opt->name, " -button")) {
15679             opt->type = Button;
15680         } else if(p = strstr(opt->name, " -save")) {
15681             opt->type = SaveButton;
15682         } else return FALSE;
15683         *p = 0; // terminate option name
15684         // now look if the command-line options define a setting for this engine option.
15685         if(cps->optionSettings && cps->optionSettings[0])
15686             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15687         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15688           snprintf(buf, MSG_SIZ, "option %s", p);
15689                 if(p = strstr(buf, ",")) *p = 0;
15690                 if(q = strchr(buf, '=')) switch(opt->type) {
15691                     case ComboBox:
15692                         for(n=0; n<opt->max; n++)
15693                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15694                         break;
15695                     case TextBox:
15696                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15697                         break;
15698                     case Spin:
15699                     case CheckBox:
15700                         opt->value = atoi(q+1);
15701                     default:
15702                         break;
15703                 }
15704                 strcat(buf, "\n");
15705                 SendToProgram(buf, cps);
15706         }
15707         return TRUE;
15708 }
15709
15710 void
15711 FeatureDone (ChessProgramState *cps, int val)
15712 {
15713   DelayedEventCallback cb = GetDelayedEvent();
15714   if ((cb == InitBackEnd3 && cps == &first) ||
15715       (cb == SettingsMenuIfReady && cps == &second) ||
15716       (cb == LoadEngine) ||
15717       (cb == TwoMachinesEventIfReady)) {
15718     CancelDelayedEvent();
15719     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15720   }
15721   cps->initDone = val;
15722 }
15723
15724 /* Parse feature command from engine */
15725 void
15726 ParseFeatures (char *args, ChessProgramState *cps)
15727 {
15728   char *p = args;
15729   char *q;
15730   int val;
15731   char buf[MSG_SIZ];
15732
15733   for (;;) {
15734     while (*p == ' ') p++;
15735     if (*p == NULLCHAR) return;
15736
15737     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15738     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15739     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15740     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15741     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15742     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15743     if (BoolFeature(&p, "reuse", &val, cps)) {
15744       /* Engine can disable reuse, but can't enable it if user said no */
15745       if (!val) cps->reuse = FALSE;
15746       continue;
15747     }
15748     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15749     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15750       if (gameMode == TwoMachinesPlay) {
15751         DisplayTwoMachinesTitle();
15752       } else {
15753         DisplayTitle("");
15754       }
15755       continue;
15756     }
15757     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15758     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15759     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15760     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15761     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15762     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15763     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15764     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15765     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15766     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15767     if (IntFeature(&p, "done", &val, cps)) {
15768       FeatureDone(cps, val);
15769       continue;
15770     }
15771     /* Added by Tord: */
15772     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15773     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15774     /* End of additions by Tord */
15775
15776     /* [HGM] added features: */
15777     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15778     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15779     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15780     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15781     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15782     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15783     if (StringFeature(&p, "option", buf, cps)) {
15784         FREE(cps->option[cps->nrOptions].name);
15785         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15786         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15787         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15788           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15789             SendToProgram(buf, cps);
15790             continue;
15791         }
15792         if(cps->nrOptions >= MAX_OPTIONS) {
15793             cps->nrOptions--;
15794             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15795             DisplayError(buf, 0);
15796         }
15797         continue;
15798     }
15799     /* End of additions by HGM */
15800
15801     /* unknown feature: complain and skip */
15802     q = p;
15803     while (*q && *q != '=') q++;
15804     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15805     SendToProgram(buf, cps);
15806     p = q;
15807     if (*p == '=') {
15808       p++;
15809       if (*p == '\"') {
15810         p++;
15811         while (*p && *p != '\"') p++;
15812         if (*p == '\"') p++;
15813       } else {
15814         while (*p && *p != ' ') p++;
15815       }
15816     }
15817   }
15818
15819 }
15820
15821 void
15822 PeriodicUpdatesEvent (int newState)
15823 {
15824     if (newState == appData.periodicUpdates)
15825       return;
15826
15827     appData.periodicUpdates=newState;
15828
15829     /* Display type changes, so update it now */
15830 //    DisplayAnalysis();
15831
15832     /* Get the ball rolling again... */
15833     if (newState) {
15834         AnalysisPeriodicEvent(1);
15835         StartAnalysisClock();
15836     }
15837 }
15838
15839 void
15840 PonderNextMoveEvent (int newState)
15841 {
15842     if (newState == appData.ponderNextMove) return;
15843     if (gameMode == EditPosition) EditPositionDone(TRUE);
15844     if (newState) {
15845         SendToProgram("hard\n", &first);
15846         if (gameMode == TwoMachinesPlay) {
15847             SendToProgram("hard\n", &second);
15848         }
15849     } else {
15850         SendToProgram("easy\n", &first);
15851         thinkOutput[0] = NULLCHAR;
15852         if (gameMode == TwoMachinesPlay) {
15853             SendToProgram("easy\n", &second);
15854         }
15855     }
15856     appData.ponderNextMove = newState;
15857 }
15858
15859 void
15860 NewSettingEvent (int option, int *feature, char *command, int value)
15861 {
15862     char buf[MSG_SIZ];
15863
15864     if (gameMode == EditPosition) EditPositionDone(TRUE);
15865     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15866     if(feature == NULL || *feature) SendToProgram(buf, &first);
15867     if (gameMode == TwoMachinesPlay) {
15868         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15869     }
15870 }
15871
15872 void
15873 ShowThinkingEvent ()
15874 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15875 {
15876     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15877     int newState = appData.showThinking
15878         // [HGM] thinking: other features now need thinking output as well
15879         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15880
15881     if (oldState == newState) return;
15882     oldState = newState;
15883     if (gameMode == EditPosition) EditPositionDone(TRUE);
15884     if (oldState) {
15885         SendToProgram("post\n", &first);
15886         if (gameMode == TwoMachinesPlay) {
15887             SendToProgram("post\n", &second);
15888         }
15889     } else {
15890         SendToProgram("nopost\n", &first);
15891         thinkOutput[0] = NULLCHAR;
15892         if (gameMode == TwoMachinesPlay) {
15893             SendToProgram("nopost\n", &second);
15894         }
15895     }
15896 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15897 }
15898
15899 void
15900 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15901 {
15902   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15903   if (pr == NoProc) return;
15904   AskQuestion(title, question, replyPrefix, pr);
15905 }
15906
15907 void
15908 TypeInEvent (char firstChar)
15909 {
15910     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15911         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15912         gameMode == AnalyzeMode || gameMode == EditGame || 
15913         gameMode == EditPosition || gameMode == IcsExamining ||
15914         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15915         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15916                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15917                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15918         gameMode == Training) PopUpMoveDialog(firstChar);
15919 }
15920
15921 void
15922 TypeInDoneEvent (char *move)
15923 {
15924         Board board;
15925         int n, fromX, fromY, toX, toY;
15926         char promoChar;
15927         ChessMove moveType;
15928
15929         // [HGM] FENedit
15930         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15931                 EditPositionPasteFEN(move);
15932                 return;
15933         }
15934         // [HGM] movenum: allow move number to be typed in any mode
15935         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15936           ToNrEvent(2*n-1);
15937           return;
15938         }
15939         // undocumented kludge: allow command-line option to be typed in!
15940         // (potentially fatal, and does not implement the effect of the option.)
15941         // should only be used for options that are values on which future decisions will be made,
15942         // and definitely not on options that would be used during initialization.
15943         if(strstr(move, "!!! -") == move) {
15944             ParseArgsFromString(move+4);
15945             return;
15946         }
15947
15948       if (gameMode != EditGame && currentMove != forwardMostMove && 
15949         gameMode != Training) {
15950         DisplayMoveError(_("Displayed move is not current"));
15951       } else {
15952         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15953           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15954         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15955         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15956           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15957           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15958         } else {
15959           DisplayMoveError(_("Could not parse move"));
15960         }
15961       }
15962 }
15963
15964 void
15965 DisplayMove (int moveNumber)
15966 {
15967     char message[MSG_SIZ];
15968     char res[MSG_SIZ];
15969     char cpThinkOutput[MSG_SIZ];
15970
15971     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15972
15973     if (moveNumber == forwardMostMove - 1 ||
15974         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15975
15976         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15977
15978         if (strchr(cpThinkOutput, '\n')) {
15979             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15980         }
15981     } else {
15982         *cpThinkOutput = NULLCHAR;
15983     }
15984
15985     /* [AS] Hide thinking from human user */
15986     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15987         *cpThinkOutput = NULLCHAR;
15988         if( thinkOutput[0] != NULLCHAR ) {
15989             int i;
15990
15991             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15992                 cpThinkOutput[i] = '.';
15993             }
15994             cpThinkOutput[i] = NULLCHAR;
15995             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15996         }
15997     }
15998
15999     if (moveNumber == forwardMostMove - 1 &&
16000         gameInfo.resultDetails != NULL) {
16001         if (gameInfo.resultDetails[0] == NULLCHAR) {
16002           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16003         } else {
16004           snprintf(res, MSG_SIZ, " {%s} %s",
16005                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16006         }
16007     } else {
16008         res[0] = NULLCHAR;
16009     }
16010
16011     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16012         DisplayMessage(res, cpThinkOutput);
16013     } else {
16014       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16015                 WhiteOnMove(moveNumber) ? " " : ".. ",
16016                 parseList[moveNumber], res);
16017         DisplayMessage(message, cpThinkOutput);
16018     }
16019 }
16020
16021 void
16022 DisplayComment (int moveNumber, char *text)
16023 {
16024     char title[MSG_SIZ];
16025
16026     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16027       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16028     } else {
16029       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16030               WhiteOnMove(moveNumber) ? " " : ".. ",
16031               parseList[moveNumber]);
16032     }
16033     if (text != NULL && (appData.autoDisplayComment || commentUp))
16034         CommentPopUp(title, text);
16035 }
16036
16037 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16038  * might be busy thinking or pondering.  It can be omitted if your
16039  * gnuchess is configured to stop thinking immediately on any user
16040  * input.  However, that gnuchess feature depends on the FIONREAD
16041  * ioctl, which does not work properly on some flavors of Unix.
16042  */
16043 void
16044 Attention (ChessProgramState *cps)
16045 {
16046 #if ATTENTION
16047     if (!cps->useSigint) return;
16048     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16049     switch (gameMode) {
16050       case MachinePlaysWhite:
16051       case MachinePlaysBlack:
16052       case TwoMachinesPlay:
16053       case IcsPlayingWhite:
16054       case IcsPlayingBlack:
16055       case AnalyzeMode:
16056       case AnalyzeFile:
16057         /* Skip if we know it isn't thinking */
16058         if (!cps->maybeThinking) return;
16059         if (appData.debugMode)
16060           fprintf(debugFP, "Interrupting %s\n", cps->which);
16061         InterruptChildProcess(cps->pr);
16062         cps->maybeThinking = FALSE;
16063         break;
16064       default:
16065         break;
16066     }
16067 #endif /*ATTENTION*/
16068 }
16069
16070 int
16071 CheckFlags ()
16072 {
16073     if (whiteTimeRemaining <= 0) {
16074         if (!whiteFlag) {
16075             whiteFlag = TRUE;
16076             if (appData.icsActive) {
16077                 if (appData.autoCallFlag &&
16078                     gameMode == IcsPlayingBlack && !blackFlag) {
16079                   SendToICS(ics_prefix);
16080                   SendToICS("flag\n");
16081                 }
16082             } else {
16083                 if (blackFlag) {
16084                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16085                 } else {
16086                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16087                     if (appData.autoCallFlag) {
16088                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16089                         return TRUE;
16090                     }
16091                 }
16092             }
16093         }
16094     }
16095     if (blackTimeRemaining <= 0) {
16096         if (!blackFlag) {
16097             blackFlag = TRUE;
16098             if (appData.icsActive) {
16099                 if (appData.autoCallFlag &&
16100                     gameMode == IcsPlayingWhite && !whiteFlag) {
16101                   SendToICS(ics_prefix);
16102                   SendToICS("flag\n");
16103                 }
16104             } else {
16105                 if (whiteFlag) {
16106                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16107                 } else {
16108                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16109                     if (appData.autoCallFlag) {
16110                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16111                         return TRUE;
16112                     }
16113                 }
16114             }
16115         }
16116     }
16117     return FALSE;
16118 }
16119
16120 void
16121 CheckTimeControl ()
16122 {
16123     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16124         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16125
16126     /*
16127      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16128      */
16129     if ( !WhiteOnMove(forwardMostMove) ) {
16130         /* White made time control */
16131         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16132         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16133         /* [HGM] time odds: correct new time quota for time odds! */
16134                                             / WhitePlayer()->timeOdds;
16135         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16136     } else {
16137         lastBlack -= blackTimeRemaining;
16138         /* Black made time control */
16139         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16140                                             / WhitePlayer()->other->timeOdds;
16141         lastWhite = whiteTimeRemaining;
16142     }
16143 }
16144
16145 void
16146 DisplayBothClocks ()
16147 {
16148     int wom = gameMode == EditPosition ?
16149       !blackPlaysFirst : WhiteOnMove(currentMove);
16150     DisplayWhiteClock(whiteTimeRemaining, wom);
16151     DisplayBlackClock(blackTimeRemaining, !wom);
16152 }
16153
16154
16155 /* Timekeeping seems to be a portability nightmare.  I think everyone
16156    has ftime(), but I'm really not sure, so I'm including some ifdefs
16157    to use other calls if you don't.  Clocks will be less accurate if
16158    you have neither ftime nor gettimeofday.
16159 */
16160
16161 /* VS 2008 requires the #include outside of the function */
16162 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16163 #include <sys/timeb.h>
16164 #endif
16165
16166 /* Get the current time as a TimeMark */
16167 void
16168 GetTimeMark (TimeMark *tm)
16169 {
16170 #if HAVE_GETTIMEOFDAY
16171
16172     struct timeval timeVal;
16173     struct timezone timeZone;
16174
16175     gettimeofday(&timeVal, &timeZone);
16176     tm->sec = (long) timeVal.tv_sec;
16177     tm->ms = (int) (timeVal.tv_usec / 1000L);
16178
16179 #else /*!HAVE_GETTIMEOFDAY*/
16180 #if HAVE_FTIME
16181
16182 // include <sys/timeb.h> / moved to just above start of function
16183     struct timeb timeB;
16184
16185     ftime(&timeB);
16186     tm->sec = (long) timeB.time;
16187     tm->ms = (int) timeB.millitm;
16188
16189 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16190     tm->sec = (long) time(NULL);
16191     tm->ms = 0;
16192 #endif
16193 #endif
16194 }
16195
16196 /* Return the difference in milliseconds between two
16197    time marks.  We assume the difference will fit in a long!
16198 */
16199 long
16200 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16201 {
16202     return 1000L*(tm2->sec - tm1->sec) +
16203            (long) (tm2->ms - tm1->ms);
16204 }
16205
16206
16207 /*
16208  * Code to manage the game clocks.
16209  *
16210  * In tournament play, black starts the clock and then white makes a move.
16211  * We give the human user a slight advantage if he is playing white---the
16212  * clocks don't run until he makes his first move, so it takes zero time.
16213  * Also, we don't account for network lag, so we could get out of sync
16214  * with GNU Chess's clock -- but then, referees are always right.
16215  */
16216
16217 static TimeMark tickStartTM;
16218 static long intendedTickLength;
16219
16220 long
16221 NextTickLength (long timeRemaining)
16222 {
16223     long nominalTickLength, nextTickLength;
16224
16225     if (timeRemaining > 0L && timeRemaining <= 10000L)
16226       nominalTickLength = 100L;
16227     else
16228       nominalTickLength = 1000L;
16229     nextTickLength = timeRemaining % nominalTickLength;
16230     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16231
16232     return nextTickLength;
16233 }
16234
16235 /* Adjust clock one minute up or down */
16236 void
16237 AdjustClock (Boolean which, int dir)
16238 {
16239     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16240     if(which) blackTimeRemaining += 60000*dir;
16241     else      whiteTimeRemaining += 60000*dir;
16242     DisplayBothClocks();
16243     adjustedClock = TRUE;
16244 }
16245
16246 /* Stop clocks and reset to a fresh time control */
16247 void
16248 ResetClocks ()
16249 {
16250     (void) StopClockTimer();
16251     if (appData.icsActive) {
16252         whiteTimeRemaining = blackTimeRemaining = 0;
16253     } else if (searchTime) {
16254         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16255         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16256     } else { /* [HGM] correct new time quote for time odds */
16257         whiteTC = blackTC = fullTimeControlString;
16258         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16259         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16260     }
16261     if (whiteFlag || blackFlag) {
16262         DisplayTitle("");
16263         whiteFlag = blackFlag = FALSE;
16264     }
16265     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16266     DisplayBothClocks();
16267     adjustedClock = FALSE;
16268 }
16269
16270 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16271
16272 /* Decrement running clock by amount of time that has passed */
16273 void
16274 DecrementClocks ()
16275 {
16276     long timeRemaining;
16277     long lastTickLength, fudge;
16278     TimeMark now;
16279
16280     if (!appData.clockMode) return;
16281     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16282
16283     GetTimeMark(&now);
16284
16285     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16286
16287     /* Fudge if we woke up a little too soon */
16288     fudge = intendedTickLength - lastTickLength;
16289     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16290
16291     if (WhiteOnMove(forwardMostMove)) {
16292         if(whiteNPS >= 0) lastTickLength = 0;
16293         timeRemaining = whiteTimeRemaining -= lastTickLength;
16294         if(timeRemaining < 0 && !appData.icsActive) {
16295             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16296             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16297                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16298                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16299             }
16300         }
16301         DisplayWhiteClock(whiteTimeRemaining - fudge,
16302                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16303     } else {
16304         if(blackNPS >= 0) lastTickLength = 0;
16305         timeRemaining = blackTimeRemaining -= lastTickLength;
16306         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16307             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16308             if(suddenDeath) {
16309                 blackStartMove = forwardMostMove;
16310                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16311             }
16312         }
16313         DisplayBlackClock(blackTimeRemaining - fudge,
16314                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16315     }
16316     if (CheckFlags()) return;
16317
16318     if(twoBoards) { // count down secondary board's clocks as well
16319         activePartnerTime -= lastTickLength;
16320         partnerUp = 1;
16321         if(activePartner == 'W')
16322             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16323         else
16324             DisplayBlackClock(activePartnerTime, TRUE);
16325         partnerUp = 0;
16326     }
16327
16328     tickStartTM = now;
16329     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16330     StartClockTimer(intendedTickLength);
16331
16332     /* if the time remaining has fallen below the alarm threshold, sound the
16333      * alarm. if the alarm has sounded and (due to a takeback or time control
16334      * with increment) the time remaining has increased to a level above the
16335      * threshold, reset the alarm so it can sound again.
16336      */
16337
16338     if (appData.icsActive && appData.icsAlarm) {
16339
16340         /* make sure we are dealing with the user's clock */
16341         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16342                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16343            )) return;
16344
16345         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16346             alarmSounded = FALSE;
16347         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16348             PlayAlarmSound();
16349             alarmSounded = TRUE;
16350         }
16351     }
16352 }
16353
16354
16355 /* A player has just moved, so stop the previously running
16356    clock and (if in clock mode) start the other one.
16357    We redisplay both clocks in case we're in ICS mode, because
16358    ICS gives us an update to both clocks after every move.
16359    Note that this routine is called *after* forwardMostMove
16360    is updated, so the last fractional tick must be subtracted
16361    from the color that is *not* on move now.
16362 */
16363 void
16364 SwitchClocks (int newMoveNr)
16365 {
16366     long lastTickLength;
16367     TimeMark now;
16368     int flagged = FALSE;
16369
16370     GetTimeMark(&now);
16371
16372     if (StopClockTimer() && appData.clockMode) {
16373         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16374         if (!WhiteOnMove(forwardMostMove)) {
16375             if(blackNPS >= 0) lastTickLength = 0;
16376             blackTimeRemaining -= lastTickLength;
16377            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16378 //         if(pvInfoList[forwardMostMove].time == -1)
16379                  pvInfoList[forwardMostMove].time =               // use GUI time
16380                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16381         } else {
16382            if(whiteNPS >= 0) lastTickLength = 0;
16383            whiteTimeRemaining -= lastTickLength;
16384            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16385 //         if(pvInfoList[forwardMostMove].time == -1)
16386                  pvInfoList[forwardMostMove].time =
16387                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16388         }
16389         flagged = CheckFlags();
16390     }
16391     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16392     CheckTimeControl();
16393
16394     if (flagged || !appData.clockMode) return;
16395
16396     switch (gameMode) {
16397       case MachinePlaysBlack:
16398       case MachinePlaysWhite:
16399       case BeginningOfGame:
16400         if (pausing) return;
16401         break;
16402
16403       case EditGame:
16404       case PlayFromGameFile:
16405       case IcsExamining:
16406         return;
16407
16408       default:
16409         break;
16410     }
16411
16412     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16413         if(WhiteOnMove(forwardMostMove))
16414              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16415         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16416     }
16417
16418     tickStartTM = now;
16419     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16420       whiteTimeRemaining : blackTimeRemaining);
16421     StartClockTimer(intendedTickLength);
16422 }
16423
16424
16425 /* Stop both clocks */
16426 void
16427 StopClocks ()
16428 {
16429     long lastTickLength;
16430     TimeMark now;
16431
16432     if (!StopClockTimer()) return;
16433     if (!appData.clockMode) return;
16434
16435     GetTimeMark(&now);
16436
16437     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16438     if (WhiteOnMove(forwardMostMove)) {
16439         if(whiteNPS >= 0) lastTickLength = 0;
16440         whiteTimeRemaining -= lastTickLength;
16441         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16442     } else {
16443         if(blackNPS >= 0) lastTickLength = 0;
16444         blackTimeRemaining -= lastTickLength;
16445         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16446     }
16447     CheckFlags();
16448 }
16449
16450 /* Start clock of player on move.  Time may have been reset, so
16451    if clock is already running, stop and restart it. */
16452 void
16453 StartClocks ()
16454 {
16455     (void) StopClockTimer(); /* in case it was running already */
16456     DisplayBothClocks();
16457     if (CheckFlags()) return;
16458
16459     if (!appData.clockMode) return;
16460     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16461
16462     GetTimeMark(&tickStartTM);
16463     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16464       whiteTimeRemaining : blackTimeRemaining);
16465
16466    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16467     whiteNPS = blackNPS = -1;
16468     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16469        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16470         whiteNPS = first.nps;
16471     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16472        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16473         blackNPS = first.nps;
16474     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16475         whiteNPS = second.nps;
16476     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16477         blackNPS = second.nps;
16478     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16479
16480     StartClockTimer(intendedTickLength);
16481 }
16482
16483 char *
16484 TimeString (long ms)
16485 {
16486     long second, minute, hour, day;
16487     char *sign = "";
16488     static char buf[32];
16489
16490     if (ms > 0 && ms <= 9900) {
16491       /* convert milliseconds to tenths, rounding up */
16492       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16493
16494       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16495       return buf;
16496     }
16497
16498     /* convert milliseconds to seconds, rounding up */
16499     /* use floating point to avoid strangeness of integer division
16500        with negative dividends on many machines */
16501     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16502
16503     if (second < 0) {
16504         sign = "-";
16505         second = -second;
16506     }
16507
16508     day = second / (60 * 60 * 24);
16509     second = second % (60 * 60 * 24);
16510     hour = second / (60 * 60);
16511     second = second % (60 * 60);
16512     minute = second / 60;
16513     second = second % 60;
16514
16515     if (day > 0)
16516       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16517               sign, day, hour, minute, second);
16518     else if (hour > 0)
16519       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16520     else
16521       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16522
16523     return buf;
16524 }
16525
16526
16527 /*
16528  * This is necessary because some C libraries aren't ANSI C compliant yet.
16529  */
16530 char *
16531 StrStr (char *string, char *match)
16532 {
16533     int i, length;
16534
16535     length = strlen(match);
16536
16537     for (i = strlen(string) - length; i >= 0; i--, string++)
16538       if (!strncmp(match, string, length))
16539         return string;
16540
16541     return NULL;
16542 }
16543
16544 char *
16545 StrCaseStr (char *string, char *match)
16546 {
16547     int i, j, length;
16548
16549     length = strlen(match);
16550
16551     for (i = strlen(string) - length; i >= 0; i--, string++) {
16552         for (j = 0; j < length; j++) {
16553             if (ToLower(match[j]) != ToLower(string[j]))
16554               break;
16555         }
16556         if (j == length) return string;
16557     }
16558
16559     return NULL;
16560 }
16561
16562 #ifndef _amigados
16563 int
16564 StrCaseCmp (char *s1, char *s2)
16565 {
16566     char c1, c2;
16567
16568     for (;;) {
16569         c1 = ToLower(*s1++);
16570         c2 = ToLower(*s2++);
16571         if (c1 > c2) return 1;
16572         if (c1 < c2) return -1;
16573         if (c1 == NULLCHAR) return 0;
16574     }
16575 }
16576
16577
16578 int
16579 ToLower (int c)
16580 {
16581     return isupper(c) ? tolower(c) : c;
16582 }
16583
16584
16585 int
16586 ToUpper (int c)
16587 {
16588     return islower(c) ? toupper(c) : c;
16589 }
16590 #endif /* !_amigados    */
16591
16592 char *
16593 StrSave (char *s)
16594 {
16595   char *ret;
16596
16597   if ((ret = (char *) malloc(strlen(s) + 1)))
16598     {
16599       safeStrCpy(ret, s, strlen(s)+1);
16600     }
16601   return ret;
16602 }
16603
16604 char *
16605 StrSavePtr (char *s, char **savePtr)
16606 {
16607     if (*savePtr) {
16608         free(*savePtr);
16609     }
16610     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16611       safeStrCpy(*savePtr, s, strlen(s)+1);
16612     }
16613     return(*savePtr);
16614 }
16615
16616 char *
16617 PGNDate ()
16618 {
16619     time_t clock;
16620     struct tm *tm;
16621     char buf[MSG_SIZ];
16622
16623     clock = time((time_t *)NULL);
16624     tm = localtime(&clock);
16625     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16626             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16627     return StrSave(buf);
16628 }
16629
16630
16631 char *
16632 PositionToFEN (int move, char *overrideCastling)
16633 {
16634     int i, j, fromX, fromY, toX, toY;
16635     int whiteToPlay;
16636     char buf[MSG_SIZ];
16637     char *p, *q;
16638     int emptycount;
16639     ChessSquare piece;
16640
16641     whiteToPlay = (gameMode == EditPosition) ?
16642       !blackPlaysFirst : (move % 2 == 0);
16643     p = buf;
16644
16645     /* Piece placement data */
16646     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16647         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16648         emptycount = 0;
16649         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16650             if (boards[move][i][j] == EmptySquare) {
16651                 emptycount++;
16652             } else { ChessSquare piece = boards[move][i][j];
16653                 if (emptycount > 0) {
16654                     if(emptycount<10) /* [HGM] can be >= 10 */
16655                         *p++ = '0' + emptycount;
16656                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16657                     emptycount = 0;
16658                 }
16659                 if(PieceToChar(piece) == '+') {
16660                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16661                     *p++ = '+';
16662                     piece = (ChessSquare)(DEMOTED piece);
16663                 }
16664                 *p++ = PieceToChar(piece);
16665                 if(p[-1] == '~') {
16666                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16667                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16668                     *p++ = '~';
16669                 }
16670             }
16671         }
16672         if (emptycount > 0) {
16673             if(emptycount<10) /* [HGM] can be >= 10 */
16674                 *p++ = '0' + emptycount;
16675             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16676             emptycount = 0;
16677         }
16678         *p++ = '/';
16679     }
16680     *(p - 1) = ' ';
16681
16682     /* [HGM] print Crazyhouse or Shogi holdings */
16683     if( gameInfo.holdingsWidth ) {
16684         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16685         q = p;
16686         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16687             piece = boards[move][i][BOARD_WIDTH-1];
16688             if( piece != EmptySquare )
16689               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16690                   *p++ = PieceToChar(piece);
16691         }
16692         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16693             piece = boards[move][BOARD_HEIGHT-i-1][0];
16694             if( piece != EmptySquare )
16695               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16696                   *p++ = PieceToChar(piece);
16697         }
16698
16699         if( q == p ) *p++ = '-';
16700         *p++ = ']';
16701         *p++ = ' ';
16702     }
16703
16704     /* Active color */
16705     *p++ = whiteToPlay ? 'w' : 'b';
16706     *p++ = ' ';
16707
16708   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16709     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16710   } else {
16711   if(nrCastlingRights) {
16712      q = p;
16713      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16714        /* [HGM] write directly from rights */
16715            if(boards[move][CASTLING][2] != NoRights &&
16716               boards[move][CASTLING][0] != NoRights   )
16717                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16718            if(boards[move][CASTLING][2] != NoRights &&
16719               boards[move][CASTLING][1] != NoRights   )
16720                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16721            if(boards[move][CASTLING][5] != NoRights &&
16722               boards[move][CASTLING][3] != NoRights   )
16723                 *p++ = boards[move][CASTLING][3] + AAA;
16724            if(boards[move][CASTLING][5] != NoRights &&
16725               boards[move][CASTLING][4] != NoRights   )
16726                 *p++ = boards[move][CASTLING][4] + AAA;
16727      } else {
16728
16729         /* [HGM] write true castling rights */
16730         if( nrCastlingRights == 6 ) {
16731             int q, k=0;
16732             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16733                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
16734             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
16735                  boards[move][CASTLING][2] != NoRights  );
16736             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
16737                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
16738                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16739                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
16740                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
16741             }
16742             if(q) *p++ = 'Q';
16743             k = 0;
16744             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16745                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
16746             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
16747                  boards[move][CASTLING][5] != NoRights  );
16748             if(gameInfo.variant == VariantSChess) {
16749                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
16750                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16751                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
16752                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
16753             }
16754             if(q) *p++ = 'q';
16755         }
16756      }
16757      if (q == p) *p++ = '-'; /* No castling rights */
16758      *p++ = ' ';
16759   }
16760
16761   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16762      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16763     /* En passant target square */
16764     if (move > backwardMostMove) {
16765         fromX = moveList[move - 1][0] - AAA;
16766         fromY = moveList[move - 1][1] - ONE;
16767         toX = moveList[move - 1][2] - AAA;
16768         toY = moveList[move - 1][3] - ONE;
16769         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16770             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16771             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16772             fromX == toX) {
16773             /* 2-square pawn move just happened */
16774             *p++ = toX + AAA;
16775             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16776         } else {
16777             *p++ = '-';
16778         }
16779     } else if(move == backwardMostMove) {
16780         // [HGM] perhaps we should always do it like this, and forget the above?
16781         if((signed char)boards[move][EP_STATUS] >= 0) {
16782             *p++ = boards[move][EP_STATUS] + AAA;
16783             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16784         } else {
16785             *p++ = '-';
16786         }
16787     } else {
16788         *p++ = '-';
16789     }
16790     *p++ = ' ';
16791   }
16792   }
16793
16794     /* [HGM] find reversible plies */
16795     {   int i = 0, j=move;
16796
16797         if (appData.debugMode) { int k;
16798             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16799             for(k=backwardMostMove; k<=forwardMostMove; k++)
16800                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16801
16802         }
16803
16804         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16805         if( j == backwardMostMove ) i += initialRulePlies;
16806         sprintf(p, "%d ", i);
16807         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16808     }
16809     /* Fullmove number */
16810     sprintf(p, "%d", (move / 2) + 1);
16811
16812     return StrSave(buf);
16813 }
16814
16815 Boolean
16816 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16817 {
16818     int i, j;
16819     char *p, c;
16820     int emptycount, virgin[BOARD_FILES];
16821     ChessSquare piece;
16822
16823     p = fen;
16824
16825     /* [HGM] by default clear Crazyhouse holdings, if present */
16826     if(gameInfo.holdingsWidth) {
16827        for(i=0; i<BOARD_HEIGHT; i++) {
16828            board[i][0]             = EmptySquare; /* black holdings */
16829            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16830            board[i][1]             = (ChessSquare) 0; /* black counts */
16831            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16832        }
16833     }
16834
16835     /* Piece placement data */
16836     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16837         j = 0;
16838         for (;;) {
16839             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16840                 if (*p == '/') p++;
16841                 emptycount = gameInfo.boardWidth - j;
16842                 while (emptycount--)
16843                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16844                 break;
16845 #if(BOARD_FILES >= 10)
16846             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16847                 p++; emptycount=10;
16848                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16849                 while (emptycount--)
16850                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16851 #endif
16852             } else if (isdigit(*p)) {
16853                 emptycount = *p++ - '0';
16854                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16855                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16856                 while (emptycount--)
16857                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16858             } else if (*p == '+' || isalpha(*p)) {
16859                 if (j >= gameInfo.boardWidth) return FALSE;
16860                 if(*p=='+') {
16861                     piece = CharToPiece(*++p);
16862                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16863                     piece = (ChessSquare) (PROMOTED piece ); p++;
16864                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16865                 } else piece = CharToPiece(*p++);
16866
16867                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16868                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16869                     piece = (ChessSquare) (PROMOTED piece);
16870                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16871                     p++;
16872                 }
16873                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16874             } else {
16875                 return FALSE;
16876             }
16877         }
16878     }
16879     while (*p == '/' || *p == ' ') p++;
16880
16881     /* [HGM] look for Crazyhouse holdings here */
16882     while(*p==' ') p++;
16883     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16884         if(*p == '[') p++;
16885         if(*p == '-' ) p++; /* empty holdings */ else {
16886             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16887             /* if we would allow FEN reading to set board size, we would   */
16888             /* have to add holdings and shift the board read so far here   */
16889             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16890                 p++;
16891                 if((int) piece >= (int) BlackPawn ) {
16892                     i = (int)piece - (int)BlackPawn;
16893                     i = PieceToNumber((ChessSquare)i);
16894                     if( i >= gameInfo.holdingsSize ) return FALSE;
16895                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16896                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16897                 } else {
16898                     i = (int)piece - (int)WhitePawn;
16899                     i = PieceToNumber((ChessSquare)i);
16900                     if( i >= gameInfo.holdingsSize ) return FALSE;
16901                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16902                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16903                 }
16904             }
16905         }
16906         if(*p == ']') p++;
16907     }
16908
16909     while(*p == ' ') p++;
16910
16911     /* Active color */
16912     c = *p++;
16913     if(appData.colorNickNames) {
16914       if( c == appData.colorNickNames[0] ) c = 'w'; else
16915       if( c == appData.colorNickNames[1] ) c = 'b';
16916     }
16917     switch (c) {
16918       case 'w':
16919         *blackPlaysFirst = FALSE;
16920         break;
16921       case 'b':
16922         *blackPlaysFirst = TRUE;
16923         break;
16924       default:
16925         return FALSE;
16926     }
16927
16928     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16929     /* return the extra info in global variiables             */
16930
16931     /* set defaults in case FEN is incomplete */
16932     board[EP_STATUS] = EP_UNKNOWN;
16933     for(i=0; i<nrCastlingRights; i++ ) {
16934         board[CASTLING][i] =
16935             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16936     }   /* assume possible unless obviously impossible */
16937     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16938     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16939     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16940                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16941     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16942     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16943     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16944                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16945     FENrulePlies = 0;
16946
16947     while(*p==' ') p++;
16948     if(nrCastlingRights) {
16949       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
16950       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
16951           /* castling indicator present, so default becomes no castlings */
16952           for(i=0; i<nrCastlingRights; i++ ) {
16953                  board[CASTLING][i] = NoRights;
16954           }
16955       }
16956       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16957              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
16958              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16959              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16960         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
16961
16962         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16963             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16964             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16965         }
16966         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16967             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16968         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16969                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16970         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16971                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16972         switch(c) {
16973           case'K':
16974               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16975               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16976               board[CASTLING][2] = whiteKingFile;
16977               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
16978               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
16979               break;
16980           case'Q':
16981               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16982               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16983               board[CASTLING][2] = whiteKingFile;
16984               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
16985               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
16986               break;
16987           case'k':
16988               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16989               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16990               board[CASTLING][5] = blackKingFile;
16991               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
16992               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
16993               break;
16994           case'q':
16995               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16996               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16997               board[CASTLING][5] = blackKingFile;
16998               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
16999               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17000           case '-':
17001               break;
17002           default: /* FRC castlings */
17003               if(c >= 'a') { /* black rights */
17004                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17005                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17006                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17007                   if(i == BOARD_RGHT) break;
17008                   board[CASTLING][5] = i;
17009                   c -= AAA;
17010                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17011                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17012                   if(c > i)
17013                       board[CASTLING][3] = c;
17014                   else
17015                       board[CASTLING][4] = c;
17016               } else { /* white rights */
17017                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17018                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17019                     if(board[0][i] == WhiteKing) break;
17020                   if(i == BOARD_RGHT) break;
17021                   board[CASTLING][2] = i;
17022                   c -= AAA - 'a' + 'A';
17023                   if(board[0][c] >= WhiteKing) break;
17024                   if(c > i)
17025                       board[CASTLING][0] = c;
17026                   else
17027                       board[CASTLING][1] = c;
17028               }
17029         }
17030       }
17031       for(i=0; i<nrCastlingRights; i++)
17032         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17033       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17034     if (appData.debugMode) {
17035         fprintf(debugFP, "FEN castling rights:");
17036         for(i=0; i<nrCastlingRights; i++)
17037         fprintf(debugFP, " %d", board[CASTLING][i]);
17038         fprintf(debugFP, "\n");
17039     }
17040
17041       while(*p==' ') p++;
17042     }
17043
17044     /* read e.p. field in games that know e.p. capture */
17045     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17046        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17047       if(*p=='-') {
17048         p++; board[EP_STATUS] = EP_NONE;
17049       } else {
17050          char c = *p++ - AAA;
17051
17052          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17053          if(*p >= '0' && *p <='9') p++;
17054          board[EP_STATUS] = c;
17055       }
17056     }
17057
17058
17059     if(sscanf(p, "%d", &i) == 1) {
17060         FENrulePlies = i; /* 50-move ply counter */
17061         /* (The move number is still ignored)    */
17062     }
17063
17064     return TRUE;
17065 }
17066
17067 void
17068 EditPositionPasteFEN (char *fen)
17069 {
17070   if (fen != NULL) {
17071     Board initial_position;
17072
17073     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17074       DisplayError(_("Bad FEN position in clipboard"), 0);
17075       return ;
17076     } else {
17077       int savedBlackPlaysFirst = blackPlaysFirst;
17078       EditPositionEvent();
17079       blackPlaysFirst = savedBlackPlaysFirst;
17080       CopyBoard(boards[0], initial_position);
17081       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17082       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17083       DisplayBothClocks();
17084       DrawPosition(FALSE, boards[currentMove]);
17085     }
17086   }
17087 }
17088
17089 static char cseq[12] = "\\   ";
17090
17091 Boolean
17092 set_cont_sequence (char *new_seq)
17093 {
17094     int len;
17095     Boolean ret;
17096
17097     // handle bad attempts to set the sequence
17098         if (!new_seq)
17099                 return 0; // acceptable error - no debug
17100
17101     len = strlen(new_seq);
17102     ret = (len > 0) && (len < sizeof(cseq));
17103     if (ret)
17104       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17105     else if (appData.debugMode)
17106       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17107     return ret;
17108 }
17109
17110 /*
17111     reformat a source message so words don't cross the width boundary.  internal
17112     newlines are not removed.  returns the wrapped size (no null character unless
17113     included in source message).  If dest is NULL, only calculate the size required
17114     for the dest buffer.  lp argument indicats line position upon entry, and it's
17115     passed back upon exit.
17116 */
17117 int
17118 wrap (char *dest, char *src, int count, int width, int *lp)
17119 {
17120     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17121
17122     cseq_len = strlen(cseq);
17123     old_line = line = *lp;
17124     ansi = len = clen = 0;
17125
17126     for (i=0; i < count; i++)
17127     {
17128         if (src[i] == '\033')
17129             ansi = 1;
17130
17131         // if we hit the width, back up
17132         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17133         {
17134             // store i & len in case the word is too long
17135             old_i = i, old_len = len;
17136
17137             // find the end of the last word
17138             while (i && src[i] != ' ' && src[i] != '\n')
17139             {
17140                 i--;
17141                 len--;
17142             }
17143
17144             // word too long?  restore i & len before splitting it
17145             if ((old_i-i+clen) >= width)
17146             {
17147                 i = old_i;
17148                 len = old_len;
17149             }
17150
17151             // extra space?
17152             if (i && src[i-1] == ' ')
17153                 len--;
17154
17155             if (src[i] != ' ' && src[i] != '\n')
17156             {
17157                 i--;
17158                 if (len)
17159                     len--;
17160             }
17161
17162             // now append the newline and continuation sequence
17163             if (dest)
17164                 dest[len] = '\n';
17165             len++;
17166             if (dest)
17167                 strncpy(dest+len, cseq, cseq_len);
17168             len += cseq_len;
17169             line = cseq_len;
17170             clen = cseq_len;
17171             continue;
17172         }
17173
17174         if (dest)
17175             dest[len] = src[i];
17176         len++;
17177         if (!ansi)
17178             line++;
17179         if (src[i] == '\n')
17180             line = 0;
17181         if (src[i] == 'm')
17182             ansi = 0;
17183     }
17184     if (dest && appData.debugMode)
17185     {
17186         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17187             count, width, line, len, *lp);
17188         show_bytes(debugFP, src, count);
17189         fprintf(debugFP, "\ndest: ");
17190         show_bytes(debugFP, dest, len);
17191         fprintf(debugFP, "\n");
17192     }
17193     *lp = dest ? line : old_line;
17194
17195     return len;
17196 }
17197
17198 // [HGM] vari: routines for shelving variations
17199 Boolean modeRestore = FALSE;
17200
17201 void
17202 PushInner (int firstMove, int lastMove)
17203 {
17204         int i, j, nrMoves = lastMove - firstMove;
17205
17206         // push current tail of game on stack
17207         savedResult[storedGames] = gameInfo.result;
17208         savedDetails[storedGames] = gameInfo.resultDetails;
17209         gameInfo.resultDetails = NULL;
17210         savedFirst[storedGames] = firstMove;
17211         savedLast [storedGames] = lastMove;
17212         savedFramePtr[storedGames] = framePtr;
17213         framePtr -= nrMoves; // reserve space for the boards
17214         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17215             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17216             for(j=0; j<MOVE_LEN; j++)
17217                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17218             for(j=0; j<2*MOVE_LEN; j++)
17219                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17220             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17221             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17222             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17223             pvInfoList[firstMove+i-1].depth = 0;
17224             commentList[framePtr+i] = commentList[firstMove+i];
17225             commentList[firstMove+i] = NULL;
17226         }
17227
17228         storedGames++;
17229         forwardMostMove = firstMove; // truncate game so we can start variation
17230 }
17231
17232 void
17233 PushTail (int firstMove, int lastMove)
17234 {
17235         if(appData.icsActive) { // only in local mode
17236                 forwardMostMove = currentMove; // mimic old ICS behavior
17237                 return;
17238         }
17239         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17240
17241         PushInner(firstMove, lastMove);
17242         if(storedGames == 1) GreyRevert(FALSE);
17243         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17244 }
17245
17246 void
17247 PopInner (Boolean annotate)
17248 {
17249         int i, j, nrMoves;
17250         char buf[8000], moveBuf[20];
17251
17252         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17253         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17254         nrMoves = savedLast[storedGames] - currentMove;
17255         if(annotate) {
17256                 int cnt = 10;
17257                 if(!WhiteOnMove(currentMove))
17258                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17259                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17260                 for(i=currentMove; i<forwardMostMove; i++) {
17261                         if(WhiteOnMove(i))
17262                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17263                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17264                         strcat(buf, moveBuf);
17265                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17266                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17267                 }
17268                 strcat(buf, ")");
17269         }
17270         for(i=1; i<=nrMoves; i++) { // copy last variation back
17271             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17272             for(j=0; j<MOVE_LEN; j++)
17273                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17274             for(j=0; j<2*MOVE_LEN; j++)
17275                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17276             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17277             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17278             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17279             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17280             commentList[currentMove+i] = commentList[framePtr+i];
17281             commentList[framePtr+i] = NULL;
17282         }
17283         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17284         framePtr = savedFramePtr[storedGames];
17285         gameInfo.result = savedResult[storedGames];
17286         if(gameInfo.resultDetails != NULL) {
17287             free(gameInfo.resultDetails);
17288       }
17289         gameInfo.resultDetails = savedDetails[storedGames];
17290         forwardMostMove = currentMove + nrMoves;
17291 }
17292
17293 Boolean
17294 PopTail (Boolean annotate)
17295 {
17296         if(appData.icsActive) return FALSE; // only in local mode
17297         if(!storedGames) return FALSE; // sanity
17298         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17299
17300         PopInner(annotate);
17301         if(currentMove < forwardMostMove) ForwardEvent(); else
17302         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17303
17304         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17305         return TRUE;
17306 }
17307
17308 void
17309 CleanupTail ()
17310 {       // remove all shelved variations
17311         int i;
17312         for(i=0; i<storedGames; i++) {
17313             if(savedDetails[i])
17314                 free(savedDetails[i]);
17315             savedDetails[i] = NULL;
17316         }
17317         for(i=framePtr; i<MAX_MOVES; i++) {
17318                 if(commentList[i]) free(commentList[i]);
17319                 commentList[i] = NULL;
17320         }
17321         framePtr = MAX_MOVES-1;
17322         storedGames = 0;
17323 }
17324
17325 void
17326 LoadVariation (int index, char *text)
17327 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17328         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17329         int level = 0, move;
17330
17331         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17332         // first find outermost bracketing variation
17333         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17334             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17335                 if(*p == '{') wait = '}'; else
17336                 if(*p == '[') wait = ']'; else
17337                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17338                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17339             }
17340             if(*p == wait) wait = NULLCHAR; // closing ]} found
17341             p++;
17342         }
17343         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17344         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17345         end[1] = NULLCHAR; // clip off comment beyond variation
17346         ToNrEvent(currentMove-1);
17347         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17348         // kludge: use ParsePV() to append variation to game
17349         move = currentMove;
17350         ParsePV(start, TRUE, TRUE);
17351         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17352         ClearPremoveHighlights();
17353         CommentPopDown();
17354         ToNrEvent(currentMove+1);
17355 }
17356