Allow second engine to analyze too
[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 void ToggleSecond P((void));
226
227 #ifdef WIN32
228        extern void ConsoleCreate();
229 #endif
230
231 ChessProgramState *WhitePlayer();
232 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
233 int VerifyDisplayMode P(());
234
235 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
236 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
237 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
238 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
239 void ics_update_width P((int new_width));
240 extern char installDir[MSG_SIZ];
241 VariantClass startVariant; /* [HGM] nicks: initial variant */
242 Boolean abortMatch;
243
244 extern int tinyLayout, smallLayout;
245 ChessProgramStats programStats;
246 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
247 int endPV = -1;
248 static int exiting = 0; /* [HGM] moved to top */
249 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
250 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
251 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
252 int partnerHighlight[2];
253 Boolean partnerBoardValid = 0;
254 char partnerStatus[MSG_SIZ];
255 Boolean partnerUp;
256 Boolean originalFlip;
257 Boolean twoBoards = 0;
258 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
259 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
260 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
261 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
262 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
263 int opponentKibitzes;
264 int lastSavedGame; /* [HGM] save: ID of game */
265 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
266 extern int chatCount;
267 int chattingPartner;
268 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
269 char lastMsg[MSG_SIZ];
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
272 int promoDefaultAltered;
273
274 /* States for ics_getting_history */
275 #define H_FALSE 0
276 #define H_REQUESTED 1
277 #define H_GOT_REQ_HEADER 2
278 #define H_GOT_UNREQ_HEADER 3
279 #define H_GETTING_MOVES 4
280 #define H_GOT_UNWANTED_HEADER 5
281
282 /* whosays values for GameEnds */
283 #define GE_ICS 0
284 #define GE_ENGINE 1
285 #define GE_PLAYER 2
286 #define GE_FILE 3
287 #define GE_XBOARD 4
288 #define GE_ENGINE1 5
289 #define GE_ENGINE2 6
290
291 /* Maximum number of games in a cmail message */
292 #define CMAIL_MAX_GAMES 20
293
294 /* Different types of move when calling RegisterMove */
295 #define CMAIL_MOVE   0
296 #define CMAIL_RESIGN 1
297 #define CMAIL_DRAW   2
298 #define CMAIL_ACCEPT 3
299
300 /* Different types of result to remember for each game */
301 #define CMAIL_NOT_RESULT 0
302 #define CMAIL_OLD_RESULT 1
303 #define CMAIL_NEW_RESULT 2
304
305 /* Telnet protocol constants */
306 #define TN_WILL 0373
307 #define TN_WONT 0374
308 #define TN_DO   0375
309 #define TN_DONT 0376
310 #define TN_IAC  0377
311 #define TN_ECHO 0001
312 #define TN_SGA  0003
313 #define TN_PORT 23
314
315 char*
316 safeStrCpy (char *dst, const char *src, size_t count)
317 { // [HGM] made safe
318   int i;
319   assert( dst != NULL );
320   assert( src != NULL );
321   assert( count > 0 );
322
323   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
324   if(  i == count && dst[count-1] != NULLCHAR)
325     {
326       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
327       if(appData.debugMode)
328       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
329     }
330
331   return dst;
332 }
333
334 /* Some compiler can't cast u64 to double
335  * This function do the job for us:
336
337  * We use the highest bit for cast, this only
338  * works if the highest bit is not
339  * in use (This should not happen)
340  *
341  * We used this for all compiler
342  */
343 double
344 u64ToDouble (u64 value)
345 {
346   double r;
347   u64 tmp = value & u64Const(0x7fffffffffffffff);
348   r = (double)(s64)tmp;
349   if (value & u64Const(0x8000000000000000))
350        r +=  9.2233720368547758080e18; /* 2^63 */
351  return r;
352 }
353
354 /* Fake up flags for now, as we aren't keeping track of castling
355    availability yet. [HGM] Change of logic: the flag now only
356    indicates the type of castlings allowed by the rule of the game.
357    The actual rights themselves are maintained in the array
358    castlingRights, as part of the game history, and are not probed
359    by this function.
360  */
361 int
362 PosFlags (index)
363 {
364   int flags = F_ALL_CASTLE_OK;
365   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
366   switch (gameInfo.variant) {
367   case VariantSuicide:
368     flags &= ~F_ALL_CASTLE_OK;
369   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
370     flags |= F_IGNORE_CHECK;
371   case VariantLosers:
372     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
373     break;
374   case VariantAtomic:
375     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
376     break;
377   case VariantKriegspiel:
378     flags |= F_KRIEGSPIEL_CAPTURE;
379     break;
380   case VariantCapaRandom:
381   case VariantFischeRandom:
382     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
383   case VariantNoCastle:
384   case VariantShatranj:
385   case VariantCourier:
386   case VariantMakruk:
387   case VariantGrand:
388     flags &= ~F_ALL_CASTLE_OK;
389     break;
390   default:
391     break;
392   }
393   return flags;
394 }
395
396 FILE *gameFileFP, *debugFP, *serverFP;
397 char *currentDebugFile; // [HGM] debug split: to remember name
398
399 /*
400     [AS] Note: sometimes, the sscanf() function is used to parse the input
401     into a fixed-size buffer. Because of this, we must be prepared to
402     receive strings as long as the size of the input buffer, which is currently
403     set to 4K for Windows and 8K for the rest.
404     So, we must either allocate sufficiently large buffers here, or
405     reduce the size of the input buffer in the input reading part.
406 */
407
408 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
409 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
410 char thinkOutput1[MSG_SIZ*10];
411
412 ChessProgramState first, second, pairing;
413
414 /* premove variables */
415 int premoveToX = 0;
416 int premoveToY = 0;
417 int premoveFromX = 0;
418 int premoveFromY = 0;
419 int premovePromoChar = 0;
420 int gotPremove = 0;
421 Boolean alarmSounded;
422 /* end premove variables */
423
424 char *ics_prefix = "$";
425 enum ICS_TYPE ics_type = ICS_GENERIC;
426
427 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
428 int pauseExamForwardMostMove = 0;
429 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
430 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
431 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
432 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
433 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
434 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
435 int whiteFlag = FALSE, blackFlag = FALSE;
436 int userOfferedDraw = FALSE;
437 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
438 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
439 int cmailMoveType[CMAIL_MAX_GAMES];
440 long ics_clock_paused = 0;
441 ProcRef icsPR = NoProc, cmailPR = NoProc;
442 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
443 GameMode gameMode = BeginningOfGame;
444 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
445 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
446 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
447 int hiddenThinkOutputState = 0; /* [AS] */
448 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
449 int adjudicateLossPlies = 6;
450 char white_holding[64], black_holding[64];
451 TimeMark lastNodeCountTime;
452 long lastNodeCount=0;
453 int shiftKey, controlKey; // [HGM] set by mouse handler
454
455 int have_sent_ICS_logon = 0;
456 int movesPerSession;
457 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
458 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
459 Boolean adjustedClock;
460 long timeControl_2; /* [AS] Allow separate time controls */
461 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
462 long timeRemaining[2][MAX_MOVES];
463 int matchGame = 0, nextGame = 0, roundNr = 0;
464 Boolean waitingForGame = FALSE;
465 TimeMark programStartTime, pauseStart;
466 char ics_handle[MSG_SIZ];
467 int have_set_title = 0;
468
469 /* animateTraining preserves the state of appData.animate
470  * when Training mode is activated. This allows the
471  * response to be animated when appData.animate == TRUE and
472  * appData.animateDragging == TRUE.
473  */
474 Boolean animateTraining;
475
476 GameInfo gameInfo;
477
478 AppData appData;
479
480 Board boards[MAX_MOVES];
481 /* [HGM] Following 7 needed for accurate legality tests: */
482 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
483 signed char  initialRights[BOARD_FILES];
484 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
485 int   initialRulePlies, FENrulePlies;
486 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
487 int loadFlag = 0;
488 Boolean shuffleOpenings;
489 int mute; // mute all sounds
490
491 // [HGM] vari: next 12 to save and restore variations
492 #define MAX_VARIATIONS 10
493 int framePtr = MAX_MOVES-1; // points to free stack entry
494 int storedGames = 0;
495 int savedFirst[MAX_VARIATIONS];
496 int savedLast[MAX_VARIATIONS];
497 int savedFramePtr[MAX_VARIATIONS];
498 char *savedDetails[MAX_VARIATIONS];
499 ChessMove savedResult[MAX_VARIATIONS];
500
501 void PushTail P((int firstMove, int lastMove));
502 Boolean PopTail P((Boolean annotate));
503 void PushInner P((int firstMove, int lastMove));
504 void PopInner P((Boolean annotate));
505 void CleanupTail P((void));
506
507 ChessSquare  FIDEArray[2][BOARD_FILES] = {
508     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
509         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
510     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
511         BlackKing, BlackBishop, BlackKnight, BlackRook }
512 };
513
514 ChessSquare twoKingsArray[2][BOARD_FILES] = {
515     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
516         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
517     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
518         BlackKing, BlackKing, BlackKnight, BlackRook }
519 };
520
521 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
522     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
523         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
524     { BlackRook, BlackMan, BlackBishop, BlackQueen,
525         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
526 };
527
528 ChessSquare SpartanArray[2][BOARD_FILES] = {
529     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
530         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
531     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
532         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
533 };
534
535 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
536     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
537         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
538     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
539         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
540 };
541
542 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
543     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
544         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
545     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
546         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
547 };
548
549 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
550     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
551         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
552     { BlackRook, BlackKnight, BlackMan, BlackFerz,
553         BlackKing, BlackMan, BlackKnight, BlackRook }
554 };
555
556
557 #if (BOARD_FILES>=10)
558 ChessSquare ShogiArray[2][BOARD_FILES] = {
559     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
560         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
561     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
562         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
563 };
564
565 ChessSquare XiangqiArray[2][BOARD_FILES] = {
566     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
567         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
568     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
569         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
570 };
571
572 ChessSquare CapablancaArray[2][BOARD_FILES] = {
573     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
574         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
575     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
576         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
577 };
578
579 ChessSquare GreatArray[2][BOARD_FILES] = {
580     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
581         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
582     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
583         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
584 };
585
586 ChessSquare JanusArray[2][BOARD_FILES] = {
587     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
588         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
589     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
590         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
591 };
592
593 ChessSquare GrandArray[2][BOARD_FILES] = {
594     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
595         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
596     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
597         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
598 };
599
600 #ifdef GOTHIC
601 ChessSquare GothicArray[2][BOARD_FILES] = {
602     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
603         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
604     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
605         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
606 };
607 #else // !GOTHIC
608 #define GothicArray CapablancaArray
609 #endif // !GOTHIC
610
611 #ifdef FALCON
612 ChessSquare FalconArray[2][BOARD_FILES] = {
613     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
614         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
615     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
616         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
617 };
618 #else // !FALCON
619 #define FalconArray CapablancaArray
620 #endif // !FALCON
621
622 #else // !(BOARD_FILES>=10)
623 #define XiangqiPosition FIDEArray
624 #define CapablancaArray FIDEArray
625 #define GothicArray FIDEArray
626 #define GreatArray FIDEArray
627 #endif // !(BOARD_FILES>=10)
628
629 #if (BOARD_FILES>=12)
630 ChessSquare CourierArray[2][BOARD_FILES] = {
631     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
632         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
633     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
634         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
635 };
636 #else // !(BOARD_FILES>=12)
637 #define CourierArray CapablancaArray
638 #endif // !(BOARD_FILES>=12)
639
640
641 Board initialPosition;
642
643
644 /* Convert str to a rating. Checks for special cases of "----",
645
646    "++++", etc. Also strips ()'s */
647 int
648 string_to_rating (char *str)
649 {
650   while(*str && !isdigit(*str)) ++str;
651   if (!*str)
652     return 0;   /* One of the special "no rating" cases */
653   else
654     return atoi(str);
655 }
656
657 void
658 ClearProgramStats ()
659 {
660     /* Init programStats */
661     programStats.movelist[0] = 0;
662     programStats.depth = 0;
663     programStats.nr_moves = 0;
664     programStats.moves_left = 0;
665     programStats.nodes = 0;
666     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
667     programStats.score = 0;
668     programStats.got_only_move = 0;
669     programStats.got_fail = 0;
670     programStats.line_is_book = 0;
671 }
672
673 void
674 CommonEngineInit ()
675 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
676     if (appData.firstPlaysBlack) {
677         first.twoMachinesColor = "black\n";
678         second.twoMachinesColor = "white\n";
679     } else {
680         first.twoMachinesColor = "white\n";
681         second.twoMachinesColor = "black\n";
682     }
683
684     first.other = &second;
685     second.other = &first;
686
687     { float norm = 1;
688         if(appData.timeOddsMode) {
689             norm = appData.timeOdds[0];
690             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
691         }
692         first.timeOdds  = appData.timeOdds[0]/norm;
693         second.timeOdds = appData.timeOdds[1]/norm;
694     }
695
696     if(programVersion) free(programVersion);
697     if (appData.noChessProgram) {
698         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
699         sprintf(programVersion, "%s", PACKAGE_STRING);
700     } else {
701       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
702       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
703       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
704     }
705 }
706
707 void
708 UnloadEngine (ChessProgramState *cps)
709 {
710         /* Kill off first chess program */
711         if (cps->isr != NULL)
712           RemoveInputSource(cps->isr);
713         cps->isr = NULL;
714
715         if (cps->pr != NoProc) {
716             ExitAnalyzeMode();
717             DoSleep( appData.delayBeforeQuit );
718             SendToProgram("quit\n", cps);
719             DoSleep( appData.delayAfterQuit );
720             DestroyChildProcess(cps->pr, cps->useSigterm);
721         }
722         cps->pr = NoProc;
723         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
724 }
725
726 void
727 ClearOptions (ChessProgramState *cps)
728 {
729     int i;
730     cps->nrOptions = cps->comboCnt = 0;
731     for(i=0; i<MAX_OPTIONS; i++) {
732         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
733         cps->option[i].textValue = 0;
734     }
735 }
736
737 char *engineNames[] = {
738   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
739      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
740 N_("first"),
741   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
742      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
743 N_("second")
744 };
745
746 void
747 InitEngine (ChessProgramState *cps, int n)
748 {   // [HGM] all engine initialiation put in a function that does one engine
749
750     ClearOptions(cps);
751
752     cps->which = engineNames[n];
753     cps->maybeThinking = FALSE;
754     cps->pr = NoProc;
755     cps->isr = NULL;
756     cps->sendTime = 2;
757     cps->sendDrawOffers = 1;
758
759     cps->program = appData.chessProgram[n];
760     cps->host = appData.host[n];
761     cps->dir = appData.directory[n];
762     cps->initString = appData.engInitString[n];
763     cps->computerString = appData.computerString[n];
764     cps->useSigint  = TRUE;
765     cps->useSigterm = TRUE;
766     cps->reuse = appData.reuse[n];
767     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
768     cps->useSetboard = FALSE;
769     cps->useSAN = FALSE;
770     cps->usePing = FALSE;
771     cps->lastPing = 0;
772     cps->lastPong = 0;
773     cps->usePlayother = FALSE;
774     cps->useColors = TRUE;
775     cps->useUsermove = FALSE;
776     cps->sendICS = FALSE;
777     cps->sendName = appData.icsActive;
778     cps->sdKludge = FALSE;
779     cps->stKludge = FALSE;
780     TidyProgramName(cps->program, cps->host, cps->tidy);
781     cps->matchWins = 0;
782     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
783     cps->analysisSupport = 2; /* detect */
784     cps->analyzing = FALSE;
785     cps->initDone = FALSE;
786
787     /* New features added by Tord: */
788     cps->useFEN960 = FALSE;
789     cps->useOOCastle = TRUE;
790     /* End of new features added by Tord. */
791     cps->fenOverride  = appData.fenOverride[n];
792
793     /* [HGM] time odds: set factor for each machine */
794     cps->timeOdds  = appData.timeOdds[n];
795
796     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
797     cps->accumulateTC = appData.accumulateTC[n];
798     cps->maxNrOfSessions = 1;
799
800     /* [HGM] debug */
801     cps->debug = FALSE;
802
803     cps->supportsNPS = UNKNOWN;
804     cps->memSize = FALSE;
805     cps->maxCores = FALSE;
806     cps->egtFormats[0] = NULLCHAR;
807
808     /* [HGM] options */
809     cps->optionSettings  = appData.engOptions[n];
810
811     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
812     cps->isUCI = appData.isUCI[n]; /* [AS] */
813     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
814
815     if (appData.protocolVersion[n] > PROTOVER
816         || appData.protocolVersion[n] < 1)
817       {
818         char buf[MSG_SIZ];
819         int len;
820
821         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
822                        appData.protocolVersion[n]);
823         if( (len >= MSG_SIZ) && appData.debugMode )
824           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
825
826         DisplayFatalError(buf, 0, 2);
827       }
828     else
829       {
830         cps->protocolVersion = appData.protocolVersion[n];
831       }
832
833     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
834     ParseFeatures(appData.featureDefaults, cps);
835 }
836
837 ChessProgramState *savCps;
838
839 void
840 LoadEngine ()
841 {
842     int i;
843     if(WaitForEngine(savCps, LoadEngine)) return;
844     CommonEngineInit(); // recalculate time odds
845     if(gameInfo.variant != StringToVariant(appData.variant)) {
846         // we changed variant when loading the engine; this forces us to reset
847         Reset(TRUE, savCps != &first);
848         EditGameEvent(); // for consistency with other path, as Reset changes mode
849     }
850     InitChessProgram(savCps, FALSE);
851     SendToProgram("force\n", savCps);
852     DisplayMessage("", "");
853     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
854     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
855     ThawUI();
856     SetGNUMode();
857 }
858
859 void
860 ReplaceEngine (ChessProgramState *cps, int n)
861 {
862     EditGameEvent();
863     UnloadEngine(cps);
864     appData.noChessProgram = FALSE;
865     appData.clockMode = TRUE;
866     InitEngine(cps, n);
867     UpdateLogos(TRUE);
868     if(n) return; // only startup first engine immediately; second can wait
869     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
870     LoadEngine();
871 }
872
873 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
874 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
875
876 static char resetOptions[] = 
877         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
878         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
879         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
880         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
881
882 void
883 FloatToFront(char **list, char *engineLine)
884 {
885     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
886     int i=0;
887     if(appData.recentEngines <= 0) return;
888     TidyProgramName(engineLine, "localhost", tidy+1);
889     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
890     strncpy(buf+1, *list, MSG_SIZ-50);
891     if(p = strstr(buf, tidy)) { // tidy name appears in list
892         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
893         while(*p++ = *++q); // squeeze out
894     }
895     strcat(tidy, buf+1); // put list behind tidy name
896     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
897     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
898     ASSIGN(*list, tidy+1);
899 }
900
901 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
902
903 void
904 Load (ChessProgramState *cps, int i)
905 {
906     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
907     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
908         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
909         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
910         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
911         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
912         appData.firstProtocolVersion = PROTOVER;
913         ParseArgsFromString(buf);
914         SwapEngines(i);
915         ReplaceEngine(cps, i);
916         FloatToFront(&appData.recentEngineList, engineLine);
917         return;
918     }
919     p = engineName;
920     while(q = strchr(p, SLASH)) p = q+1;
921     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
922     if(engineDir[0] != NULLCHAR) {
923         ASSIGN(appData.directory[i], engineDir); p = engineName;
924     } else if(p != engineName) { // derive directory from engine path, when not given
925         p[-1] = 0;
926         ASSIGN(appData.directory[i], engineName);
927         p[-1] = SLASH;
928         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
929     } else { ASSIGN(appData.directory[i], "."); }
930     if(params[0]) {
931         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
932         snprintf(command, MSG_SIZ, "%s %s", p, params);
933         p = command;
934     }
935     ASSIGN(appData.chessProgram[i], p);
936     appData.isUCI[i] = isUCI;
937     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
938     appData.hasOwnBookUCI[i] = hasBook;
939     if(!nickName[0]) useNick = FALSE;
940     if(useNick) ASSIGN(appData.pgnName[i], nickName);
941     if(addToList) {
942         int len;
943         char quote;
944         q = firstChessProgramNames;
945         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
946         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
947         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
948                         quote, p, quote, appData.directory[i], 
949                         useNick ? " -fn \"" : "",
950                         useNick ? nickName : "",
951                         useNick ? "\"" : "",
952                         v1 ? " -firstProtocolVersion 1" : "",
953                         hasBook ? "" : " -fNoOwnBookUCI",
954                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
955                         storeVariant ? " -variant " : "",
956                         storeVariant ? VariantName(gameInfo.variant) : "");
957         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
958         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
959         if(insert != q) insert[-1] = NULLCHAR;
960         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
961         if(q)   free(q);
962         FloatToFront(&appData.recentEngineList, buf);
963     }
964     ReplaceEngine(cps, i);
965 }
966
967 void
968 InitTimeControls ()
969 {
970     int matched, min, sec;
971     /*
972      * Parse timeControl resource
973      */
974     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
975                           appData.movesPerSession)) {
976         char buf[MSG_SIZ];
977         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
978         DisplayFatalError(buf, 0, 2);
979     }
980
981     /*
982      * Parse searchTime resource
983      */
984     if (*appData.searchTime != NULLCHAR) {
985         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
986         if (matched == 1) {
987             searchTime = min * 60;
988         } else if (matched == 2) {
989             searchTime = min * 60 + sec;
990         } else {
991             char buf[MSG_SIZ];
992             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
993             DisplayFatalError(buf, 0, 2);
994         }
995     }
996 }
997
998 void
999 InitBackEnd1 ()
1000 {
1001
1002     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1003     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1004
1005     GetTimeMark(&programStartTime);
1006     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1007     appData.seedBase = random() + (random()<<15);
1008     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1009
1010     ClearProgramStats();
1011     programStats.ok_to_send = 1;
1012     programStats.seen_stat = 0;
1013
1014     /*
1015      * Initialize game list
1016      */
1017     ListNew(&gameList);
1018
1019
1020     /*
1021      * Internet chess server status
1022      */
1023     if (appData.icsActive) {
1024         appData.matchMode = FALSE;
1025         appData.matchGames = 0;
1026 #if ZIPPY
1027         appData.noChessProgram = !appData.zippyPlay;
1028 #else
1029         appData.zippyPlay = FALSE;
1030         appData.zippyTalk = FALSE;
1031         appData.noChessProgram = TRUE;
1032 #endif
1033         if (*appData.icsHelper != NULLCHAR) {
1034             appData.useTelnet = TRUE;
1035             appData.telnetProgram = appData.icsHelper;
1036         }
1037     } else {
1038         appData.zippyTalk = appData.zippyPlay = FALSE;
1039     }
1040
1041     /* [AS] Initialize pv info list [HGM] and game state */
1042     {
1043         int i, j;
1044
1045         for( i=0; i<=framePtr; i++ ) {
1046             pvInfoList[i].depth = -1;
1047             boards[i][EP_STATUS] = EP_NONE;
1048             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1049         }
1050     }
1051
1052     InitTimeControls();
1053
1054     /* [AS] Adjudication threshold */
1055     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1056
1057     InitEngine(&first, 0);
1058     InitEngine(&second, 1);
1059     CommonEngineInit();
1060
1061     pairing.which = "pairing"; // pairing engine
1062     pairing.pr = NoProc;
1063     pairing.isr = NULL;
1064     pairing.program = appData.pairingEngine;
1065     pairing.host = "localhost";
1066     pairing.dir = ".";
1067
1068     if (appData.icsActive) {
1069         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1070     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1071         appData.clockMode = FALSE;
1072         first.sendTime = second.sendTime = 0;
1073     }
1074
1075 #if ZIPPY
1076     /* Override some settings from environment variables, for backward
1077        compatibility.  Unfortunately it's not feasible to have the env
1078        vars just set defaults, at least in xboard.  Ugh.
1079     */
1080     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1081       ZippyInit();
1082     }
1083 #endif
1084
1085     if (!appData.icsActive) {
1086       char buf[MSG_SIZ];
1087       int len;
1088
1089       /* Check for variants that are supported only in ICS mode,
1090          or not at all.  Some that are accepted here nevertheless
1091          have bugs; see comments below.
1092       */
1093       VariantClass variant = StringToVariant(appData.variant);
1094       switch (variant) {
1095       case VariantBughouse:     /* need four players and two boards */
1096       case VariantKriegspiel:   /* need to hide pieces and move details */
1097         /* case VariantFischeRandom: (Fabien: moved below) */
1098         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1099         if( (len >= MSG_SIZ) && appData.debugMode )
1100           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1101
1102         DisplayFatalError(buf, 0, 2);
1103         return;
1104
1105       case VariantUnknown:
1106       case VariantLoadable:
1107       case Variant29:
1108       case Variant30:
1109       case Variant31:
1110       case Variant32:
1111       case Variant33:
1112       case Variant34:
1113       case Variant35:
1114       case Variant36:
1115       default:
1116         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1117         if( (len >= MSG_SIZ) && appData.debugMode )
1118           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1119
1120         DisplayFatalError(buf, 0, 2);
1121         return;
1122
1123       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1124       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1125       case VariantGothic:     /* [HGM] should work */
1126       case VariantCapablanca: /* [HGM] should work */
1127       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1128       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1129       case VariantKnightmate: /* [HGM] should work */
1130       case VariantCylinder:   /* [HGM] untested */
1131       case VariantFalcon:     /* [HGM] untested */
1132       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1133                                  offboard interposition not understood */
1134       case VariantNormal:     /* definitely works! */
1135       case VariantWildCastle: /* pieces not automatically shuffled */
1136       case VariantNoCastle:   /* pieces not automatically shuffled */
1137       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1138       case VariantLosers:     /* should work except for win condition,
1139                                  and doesn't know captures are mandatory */
1140       case VariantSuicide:    /* should work except for win condition,
1141                                  and doesn't know captures are mandatory */
1142       case VariantGiveaway:   /* should work except for win condition,
1143                                  and doesn't know captures are mandatory */
1144       case VariantTwoKings:   /* should work */
1145       case VariantAtomic:     /* should work except for win condition */
1146       case Variant3Check:     /* should work except for win condition */
1147       case VariantShatranj:   /* should work except for all win conditions */
1148       case VariantMakruk:     /* should work except for draw countdown */
1149       case VariantBerolina:   /* might work if TestLegality is off */
1150       case VariantCapaRandom: /* should work */
1151       case VariantJanus:      /* should work */
1152       case VariantSuper:      /* experimental */
1153       case VariantGreat:      /* experimental, requires legality testing to be off */
1154       case VariantSChess:     /* S-Chess, should work */
1155       case VariantGrand:      /* should work */
1156       case VariantSpartan:    /* should work */
1157         break;
1158       }
1159     }
1160
1161 }
1162
1163 int
1164 NextIntegerFromString (char ** str, long * value)
1165 {
1166     int result = -1;
1167     char * s = *str;
1168
1169     while( *s == ' ' || *s == '\t' ) {
1170         s++;
1171     }
1172
1173     *value = 0;
1174
1175     if( *s >= '0' && *s <= '9' ) {
1176         while( *s >= '0' && *s <= '9' ) {
1177             *value = *value * 10 + (*s - '0');
1178             s++;
1179         }
1180
1181         result = 0;
1182     }
1183
1184     *str = s;
1185
1186     return result;
1187 }
1188
1189 int
1190 NextTimeControlFromString (char ** str, long * value)
1191 {
1192     long temp;
1193     int result = NextIntegerFromString( str, &temp );
1194
1195     if( result == 0 ) {
1196         *value = temp * 60; /* Minutes */
1197         if( **str == ':' ) {
1198             (*str)++;
1199             result = NextIntegerFromString( str, &temp );
1200             *value += temp; /* Seconds */
1201         }
1202     }
1203
1204     return result;
1205 }
1206
1207 int
1208 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1209 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1210     int result = -1, type = 0; long temp, temp2;
1211
1212     if(**str != ':') return -1; // old params remain in force!
1213     (*str)++;
1214     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1215     if( NextIntegerFromString( str, &temp ) ) return -1;
1216     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1217
1218     if(**str != '/') {
1219         /* time only: incremental or sudden-death time control */
1220         if(**str == '+') { /* increment follows; read it */
1221             (*str)++;
1222             if(**str == '!') type = *(*str)++; // Bronstein TC
1223             if(result = NextIntegerFromString( str, &temp2)) return -1;
1224             *inc = temp2 * 1000;
1225             if(**str == '.') { // read fraction of increment
1226                 char *start = ++(*str);
1227                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1228                 temp2 *= 1000;
1229                 while(start++ < *str) temp2 /= 10;
1230                 *inc += temp2;
1231             }
1232         } else *inc = 0;
1233         *moves = 0; *tc = temp * 1000; *incType = type;
1234         return 0;
1235     }
1236
1237     (*str)++; /* classical time control */
1238     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1239
1240     if(result == 0) {
1241         *moves = temp;
1242         *tc    = temp2 * 1000;
1243         *inc   = 0;
1244         *incType = type;
1245     }
1246     return result;
1247 }
1248
1249 int
1250 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1251 {   /* [HGM] get time to add from the multi-session time-control string */
1252     int incType, moves=1; /* kludge to force reading of first session */
1253     long time, increment;
1254     char *s = tcString;
1255
1256     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1257     do {
1258         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1259         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1260         if(movenr == -1) return time;    /* last move before new session     */
1261         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1262         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1263         if(!moves) return increment;     /* current session is incremental   */
1264         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1265     } while(movenr >= -1);               /* try again for next session       */
1266
1267     return 0; // no new time quota on this move
1268 }
1269
1270 int
1271 ParseTimeControl (char *tc, float ti, int mps)
1272 {
1273   long tc1;
1274   long tc2;
1275   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1276   int min, sec=0;
1277
1278   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1279   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1280       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1281   if(ti > 0) {
1282
1283     if(mps)
1284       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1285     else 
1286       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1287   } else {
1288     if(mps)
1289       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1290     else 
1291       snprintf(buf, MSG_SIZ, ":%s", mytc);
1292   }
1293   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1294   
1295   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1296     return FALSE;
1297   }
1298
1299   if( *tc == '/' ) {
1300     /* Parse second time control */
1301     tc++;
1302
1303     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1304       return FALSE;
1305     }
1306
1307     if( tc2 == 0 ) {
1308       return FALSE;
1309     }
1310
1311     timeControl_2 = tc2 * 1000;
1312   }
1313   else {
1314     timeControl_2 = 0;
1315   }
1316
1317   if( tc1 == 0 ) {
1318     return FALSE;
1319   }
1320
1321   timeControl = tc1 * 1000;
1322
1323   if (ti >= 0) {
1324     timeIncrement = ti * 1000;  /* convert to ms */
1325     movesPerSession = 0;
1326   } else {
1327     timeIncrement = 0;
1328     movesPerSession = mps;
1329   }
1330   return TRUE;
1331 }
1332
1333 void
1334 InitBackEnd2 ()
1335 {
1336     if (appData.debugMode) {
1337         fprintf(debugFP, "%s\n", programVersion);
1338     }
1339     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1340
1341     set_cont_sequence(appData.wrapContSeq);
1342     if (appData.matchGames > 0) {
1343         appData.matchMode = TRUE;
1344     } else if (appData.matchMode) {
1345         appData.matchGames = 1;
1346     }
1347     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1348         appData.matchGames = appData.sameColorGames;
1349     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1350         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1351         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1352     }
1353     Reset(TRUE, FALSE);
1354     if (appData.noChessProgram || first.protocolVersion == 1) {
1355       InitBackEnd3();
1356     } else {
1357       /* kludge: allow timeout for initial "feature" commands */
1358       FreezeUI();
1359       DisplayMessage("", _("Starting chess program"));
1360       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1361     }
1362 }
1363
1364 int
1365 CalculateIndex (int index, int gameNr)
1366 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1367     int res;
1368     if(index > 0) return index; // fixed nmber
1369     if(index == 0) return 1;
1370     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1371     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1372     return res;
1373 }
1374
1375 int
1376 LoadGameOrPosition (int gameNr)
1377 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1378     if (*appData.loadGameFile != NULLCHAR) {
1379         if (!LoadGameFromFile(appData.loadGameFile,
1380                 CalculateIndex(appData.loadGameIndex, gameNr),
1381                               appData.loadGameFile, FALSE)) {
1382             DisplayFatalError(_("Bad game file"), 0, 1);
1383             return 0;
1384         }
1385     } else if (*appData.loadPositionFile != NULLCHAR) {
1386         if (!LoadPositionFromFile(appData.loadPositionFile,
1387                 CalculateIndex(appData.loadPositionIndex, gameNr),
1388                                   appData.loadPositionFile)) {
1389             DisplayFatalError(_("Bad position file"), 0, 1);
1390             return 0;
1391         }
1392     }
1393     return 1;
1394 }
1395
1396 void
1397 ReserveGame (int gameNr, char resChar)
1398 {
1399     FILE *tf = fopen(appData.tourneyFile, "r+");
1400     char *p, *q, c, buf[MSG_SIZ];
1401     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1402     safeStrCpy(buf, lastMsg, MSG_SIZ);
1403     DisplayMessage(_("Pick new game"), "");
1404     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1405     ParseArgsFromFile(tf);
1406     p = q = appData.results;
1407     if(appData.debugMode) {
1408       char *r = appData.participants;
1409       fprintf(debugFP, "results = '%s'\n", p);
1410       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1411       fprintf(debugFP, "\n");
1412     }
1413     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1414     nextGame = q - p;
1415     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1416     safeStrCpy(q, p, strlen(p) + 2);
1417     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1418     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1419     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1420         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1421         q[nextGame] = '*';
1422     }
1423     fseek(tf, -(strlen(p)+4), SEEK_END);
1424     c = fgetc(tf);
1425     if(c != '"') // depending on DOS or Unix line endings we can be one off
1426          fseek(tf, -(strlen(p)+2), SEEK_END);
1427     else fseek(tf, -(strlen(p)+3), SEEK_END);
1428     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1429     DisplayMessage(buf, "");
1430     free(p); appData.results = q;
1431     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1432        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1433       int round = appData.defaultMatchGames * appData.tourneyType;
1434       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1435          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1436         UnloadEngine(&first);  // next game belongs to other pairing;
1437         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1438     }
1439     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1440 }
1441
1442 void
1443 MatchEvent (int mode)
1444 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1445         int dummy;
1446         if(matchMode) { // already in match mode: switch it off
1447             abortMatch = TRUE;
1448             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1449             return;
1450         }
1451 //      if(gameMode != BeginningOfGame) {
1452 //          DisplayError(_("You can only start a match from the initial position."), 0);
1453 //          return;
1454 //      }
1455         abortMatch = FALSE;
1456         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1457         /* Set up machine vs. machine match */
1458         nextGame = 0;
1459         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1460         if(appData.tourneyFile[0]) {
1461             ReserveGame(-1, 0);
1462             if(nextGame > appData.matchGames) {
1463                 char buf[MSG_SIZ];
1464                 if(strchr(appData.results, '*') == NULL) {
1465                     FILE *f;
1466                     appData.tourneyCycles++;
1467                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1468                         fclose(f);
1469                         NextTourneyGame(-1, &dummy);
1470                         ReserveGame(-1, 0);
1471                         if(nextGame <= appData.matchGames) {
1472                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1473                             matchMode = mode;
1474                             ScheduleDelayedEvent(NextMatchGame, 10000);
1475                             return;
1476                         }
1477                     }
1478                 }
1479                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1480                 DisplayError(buf, 0);
1481                 appData.tourneyFile[0] = 0;
1482                 return;
1483             }
1484         } else
1485         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1486             DisplayFatalError(_("Can't have a match with no chess programs"),
1487                               0, 2);
1488             return;
1489         }
1490         matchMode = mode;
1491         matchGame = roundNr = 1;
1492         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1493         NextMatchGame();
1494 }
1495
1496 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1497
1498 void
1499 InitBackEnd3 P((void))
1500 {
1501     GameMode initialMode;
1502     char buf[MSG_SIZ];
1503     int err, len;
1504
1505     InitChessProgram(&first, startedFromSetupPosition);
1506
1507     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1508         free(programVersion);
1509         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1510         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1511         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1512     }
1513
1514     if (appData.icsActive) {
1515 #ifdef WIN32
1516         /* [DM] Make a console window if needed [HGM] merged ifs */
1517         ConsoleCreate();
1518 #endif
1519         err = establish();
1520         if (err != 0)
1521           {
1522             if (*appData.icsCommPort != NULLCHAR)
1523               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1524                              appData.icsCommPort);
1525             else
1526               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1527                         appData.icsHost, appData.icsPort);
1528
1529             if( (len >= MSG_SIZ) && appData.debugMode )
1530               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1531
1532             DisplayFatalError(buf, err, 1);
1533             return;
1534         }
1535         SetICSMode();
1536         telnetISR =
1537           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1538         fromUserISR =
1539           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1540         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1541             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1542     } else if (appData.noChessProgram) {
1543         SetNCPMode();
1544     } else {
1545         SetGNUMode();
1546     }
1547
1548     if (*appData.cmailGameName != NULLCHAR) {
1549         SetCmailMode();
1550         OpenLoopback(&cmailPR);
1551         cmailISR =
1552           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1553     }
1554
1555     ThawUI();
1556     DisplayMessage("", "");
1557     if (StrCaseCmp(appData.initialMode, "") == 0) {
1558       initialMode = BeginningOfGame;
1559       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1560         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1561         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1562         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1563         ModeHighlight();
1564       }
1565     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1566       initialMode = TwoMachinesPlay;
1567     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1568       initialMode = AnalyzeFile;
1569     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1570       initialMode = AnalyzeMode;
1571     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1572       initialMode = MachinePlaysWhite;
1573     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1574       initialMode = MachinePlaysBlack;
1575     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1576       initialMode = EditGame;
1577     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1578       initialMode = EditPosition;
1579     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1580       initialMode = Training;
1581     } else {
1582       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1583       if( (len >= MSG_SIZ) && appData.debugMode )
1584         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1585
1586       DisplayFatalError(buf, 0, 2);
1587       return;
1588     }
1589
1590     if (appData.matchMode) {
1591         if(appData.tourneyFile[0]) { // start tourney from command line
1592             FILE *f;
1593             if(f = fopen(appData.tourneyFile, "r")) {
1594                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1595                 fclose(f);
1596                 appData.clockMode = TRUE;
1597                 SetGNUMode();
1598             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1599         }
1600         MatchEvent(TRUE);
1601     } else if (*appData.cmailGameName != NULLCHAR) {
1602         /* Set up cmail mode */
1603         ReloadCmailMsgEvent(TRUE);
1604     } else {
1605         /* Set up other modes */
1606         if (initialMode == AnalyzeFile) {
1607           if (*appData.loadGameFile == NULLCHAR) {
1608             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1609             return;
1610           }
1611         }
1612         if (*appData.loadGameFile != NULLCHAR) {
1613             (void) LoadGameFromFile(appData.loadGameFile,
1614                                     appData.loadGameIndex,
1615                                     appData.loadGameFile, TRUE);
1616         } else if (*appData.loadPositionFile != NULLCHAR) {
1617             (void) LoadPositionFromFile(appData.loadPositionFile,
1618                                         appData.loadPositionIndex,
1619                                         appData.loadPositionFile);
1620             /* [HGM] try to make self-starting even after FEN load */
1621             /* to allow automatic setup of fairy variants with wtm */
1622             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1623                 gameMode = BeginningOfGame;
1624                 setboardSpoiledMachineBlack = 1;
1625             }
1626             /* [HGM] loadPos: make that every new game uses the setup */
1627             /* from file as long as we do not switch variant          */
1628             if(!blackPlaysFirst) {
1629                 startedFromPositionFile = TRUE;
1630                 CopyBoard(filePosition, boards[0]);
1631             }
1632         }
1633         if (initialMode == AnalyzeMode) {
1634           if (appData.noChessProgram) {
1635             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1636             return;
1637           }
1638           if (appData.icsActive) {
1639             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1640             return;
1641           }
1642           AnalyzeModeEvent();
1643         } else if (initialMode == AnalyzeFile) {
1644           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1645           ShowThinkingEvent();
1646           AnalyzeFileEvent();
1647           AnalysisPeriodicEvent(1);
1648         } else if (initialMode == MachinePlaysWhite) {
1649           if (appData.noChessProgram) {
1650             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1651                               0, 2);
1652             return;
1653           }
1654           if (appData.icsActive) {
1655             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1656                               0, 2);
1657             return;
1658           }
1659           MachineWhiteEvent();
1660         } else if (initialMode == MachinePlaysBlack) {
1661           if (appData.noChessProgram) {
1662             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1663                               0, 2);
1664             return;
1665           }
1666           if (appData.icsActive) {
1667             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1668                               0, 2);
1669             return;
1670           }
1671           MachineBlackEvent();
1672         } else if (initialMode == TwoMachinesPlay) {
1673           if (appData.noChessProgram) {
1674             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1675                               0, 2);
1676             return;
1677           }
1678           if (appData.icsActive) {
1679             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1680                               0, 2);
1681             return;
1682           }
1683           TwoMachinesEvent();
1684         } else if (initialMode == EditGame) {
1685           EditGameEvent();
1686         } else if (initialMode == EditPosition) {
1687           EditPositionEvent();
1688         } else if (initialMode == Training) {
1689           if (*appData.loadGameFile == NULLCHAR) {
1690             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1691             return;
1692           }
1693           TrainingEvent();
1694         }
1695     }
1696 }
1697
1698 void
1699 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1700 {
1701     DisplayBook(current+1);
1702
1703     MoveHistorySet( movelist, first, last, current, pvInfoList );
1704
1705     EvalGraphSet( first, last, current, pvInfoList );
1706
1707     MakeEngineOutputTitle();
1708 }
1709
1710 /*
1711  * Establish will establish a contact to a remote host.port.
1712  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1713  *  used to talk to the host.
1714  * Returns 0 if okay, error code if not.
1715  */
1716 int
1717 establish ()
1718 {
1719     char buf[MSG_SIZ];
1720
1721     if (*appData.icsCommPort != NULLCHAR) {
1722         /* Talk to the host through a serial comm port */
1723         return OpenCommPort(appData.icsCommPort, &icsPR);
1724
1725     } else if (*appData.gateway != NULLCHAR) {
1726         if (*appData.remoteShell == NULLCHAR) {
1727             /* Use the rcmd protocol to run telnet program on a gateway host */
1728             snprintf(buf, sizeof(buf), "%s %s %s",
1729                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1730             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1731
1732         } else {
1733             /* Use the rsh program to run telnet program on a gateway host */
1734             if (*appData.remoteUser == NULLCHAR) {
1735                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1736                         appData.gateway, appData.telnetProgram,
1737                         appData.icsHost, appData.icsPort);
1738             } else {
1739                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1740                         appData.remoteShell, appData.gateway,
1741                         appData.remoteUser, appData.telnetProgram,
1742                         appData.icsHost, appData.icsPort);
1743             }
1744             return StartChildProcess(buf, "", &icsPR);
1745
1746         }
1747     } else if (appData.useTelnet) {
1748         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1749
1750     } else {
1751         /* TCP socket interface differs somewhat between
1752            Unix and NT; handle details in the front end.
1753            */
1754         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1755     }
1756 }
1757
1758 void
1759 EscapeExpand (char *p, char *q)
1760 {       // [HGM] initstring: routine to shape up string arguments
1761         while(*p++ = *q++) if(p[-1] == '\\')
1762             switch(*q++) {
1763                 case 'n': p[-1] = '\n'; break;
1764                 case 'r': p[-1] = '\r'; break;
1765                 case 't': p[-1] = '\t'; break;
1766                 case '\\': p[-1] = '\\'; break;
1767                 case 0: *p = 0; return;
1768                 default: p[-1] = q[-1]; break;
1769             }
1770 }
1771
1772 void
1773 show_bytes (FILE *fp, char *buf, int count)
1774 {
1775     while (count--) {
1776         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1777             fprintf(fp, "\\%03o", *buf & 0xff);
1778         } else {
1779             putc(*buf, fp);
1780         }
1781         buf++;
1782     }
1783     fflush(fp);
1784 }
1785
1786 /* Returns an errno value */
1787 int
1788 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1789 {
1790     char buf[8192], *p, *q, *buflim;
1791     int left, newcount, outcount;
1792
1793     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1794         *appData.gateway != NULLCHAR) {
1795         if (appData.debugMode) {
1796             fprintf(debugFP, ">ICS: ");
1797             show_bytes(debugFP, message, count);
1798             fprintf(debugFP, "\n");
1799         }
1800         return OutputToProcess(pr, message, count, outError);
1801     }
1802
1803     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1804     p = message;
1805     q = buf;
1806     left = count;
1807     newcount = 0;
1808     while (left) {
1809         if (q >= buflim) {
1810             if (appData.debugMode) {
1811                 fprintf(debugFP, ">ICS: ");
1812                 show_bytes(debugFP, buf, newcount);
1813                 fprintf(debugFP, "\n");
1814             }
1815             outcount = OutputToProcess(pr, buf, newcount, outError);
1816             if (outcount < newcount) return -1; /* to be sure */
1817             q = buf;
1818             newcount = 0;
1819         }
1820         if (*p == '\n') {
1821             *q++ = '\r';
1822             newcount++;
1823         } else if (((unsigned char) *p) == TN_IAC) {
1824             *q++ = (char) TN_IAC;
1825             newcount ++;
1826         }
1827         *q++ = *p++;
1828         newcount++;
1829         left--;
1830     }
1831     if (appData.debugMode) {
1832         fprintf(debugFP, ">ICS: ");
1833         show_bytes(debugFP, buf, newcount);
1834         fprintf(debugFP, "\n");
1835     }
1836     outcount = OutputToProcess(pr, buf, newcount, outError);
1837     if (outcount < newcount) return -1; /* to be sure */
1838     return count;
1839 }
1840
1841 void
1842 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1843 {
1844     int outError, outCount;
1845     static int gotEof = 0;
1846
1847     /* Pass data read from player on to ICS */
1848     if (count > 0) {
1849         gotEof = 0;
1850         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1851         if (outCount < count) {
1852             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1853         }
1854     } else if (count < 0) {
1855         RemoveInputSource(isr);
1856         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1857     } else if (gotEof++ > 0) {
1858         RemoveInputSource(isr);
1859         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1860     }
1861 }
1862
1863 void
1864 KeepAlive ()
1865 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1866     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1867     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1868     SendToICS("date\n");
1869     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1870 }
1871
1872 /* added routine for printf style output to ics */
1873 void
1874 ics_printf (char *format, ...)
1875 {
1876     char buffer[MSG_SIZ];
1877     va_list args;
1878
1879     va_start(args, format);
1880     vsnprintf(buffer, sizeof(buffer), format, args);
1881     buffer[sizeof(buffer)-1] = '\0';
1882     SendToICS(buffer);
1883     va_end(args);
1884 }
1885
1886 void
1887 SendToICS (char *s)
1888 {
1889     int count, outCount, outError;
1890
1891     if (icsPR == NoProc) return;
1892
1893     count = strlen(s);
1894     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1895     if (outCount < count) {
1896         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1897     }
1898 }
1899
1900 /* This is used for sending logon scripts to the ICS. Sending
1901    without a delay causes problems when using timestamp on ICC
1902    (at least on my machine). */
1903 void
1904 SendToICSDelayed (char *s, long msdelay)
1905 {
1906     int count, outCount, outError;
1907
1908     if (icsPR == NoProc) return;
1909
1910     count = strlen(s);
1911     if (appData.debugMode) {
1912         fprintf(debugFP, ">ICS: ");
1913         show_bytes(debugFP, s, count);
1914         fprintf(debugFP, "\n");
1915     }
1916     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1917                                       msdelay);
1918     if (outCount < count) {
1919         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1920     }
1921 }
1922
1923
1924 /* Remove all highlighting escape sequences in s
1925    Also deletes any suffix starting with '('
1926    */
1927 char *
1928 StripHighlightAndTitle (char *s)
1929 {
1930     static char retbuf[MSG_SIZ];
1931     char *p = retbuf;
1932
1933     while (*s != NULLCHAR) {
1934         while (*s == '\033') {
1935             while (*s != NULLCHAR && !isalpha(*s)) s++;
1936             if (*s != NULLCHAR) s++;
1937         }
1938         while (*s != NULLCHAR && *s != '\033') {
1939             if (*s == '(' || *s == '[') {
1940                 *p = NULLCHAR;
1941                 return retbuf;
1942             }
1943             *p++ = *s++;
1944         }
1945     }
1946     *p = NULLCHAR;
1947     return retbuf;
1948 }
1949
1950 /* Remove all highlighting escape sequences in s */
1951 char *
1952 StripHighlight (char *s)
1953 {
1954     static char retbuf[MSG_SIZ];
1955     char *p = retbuf;
1956
1957     while (*s != NULLCHAR) {
1958         while (*s == '\033') {
1959             while (*s != NULLCHAR && !isalpha(*s)) s++;
1960             if (*s != NULLCHAR) s++;
1961         }
1962         while (*s != NULLCHAR && *s != '\033') {
1963             *p++ = *s++;
1964         }
1965     }
1966     *p = NULLCHAR;
1967     return retbuf;
1968 }
1969
1970 char *variantNames[] = VARIANT_NAMES;
1971 char *
1972 VariantName (VariantClass v)
1973 {
1974     return variantNames[v];
1975 }
1976
1977
1978 /* Identify a variant from the strings the chess servers use or the
1979    PGN Variant tag names we use. */
1980 VariantClass
1981 StringToVariant (char *e)
1982 {
1983     char *p;
1984     int wnum = -1;
1985     VariantClass v = VariantNormal;
1986     int i, found = FALSE;
1987     char buf[MSG_SIZ];
1988     int len;
1989
1990     if (!e) return v;
1991
1992     /* [HGM] skip over optional board-size prefixes */
1993     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1994         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1995         while( *e++ != '_');
1996     }
1997
1998     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1999         v = VariantNormal;
2000         found = TRUE;
2001     } else
2002     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2003       if (StrCaseStr(e, variantNames[i])) {
2004         v = (VariantClass) i;
2005         found = TRUE;
2006         break;
2007       }
2008     }
2009
2010     if (!found) {
2011       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2012           || StrCaseStr(e, "wild/fr")
2013           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2014         v = VariantFischeRandom;
2015       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2016                  (i = 1, p = StrCaseStr(e, "w"))) {
2017         p += i;
2018         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2019         if (isdigit(*p)) {
2020           wnum = atoi(p);
2021         } else {
2022           wnum = -1;
2023         }
2024         switch (wnum) {
2025         case 0: /* FICS only, actually */
2026         case 1:
2027           /* Castling legal even if K starts on d-file */
2028           v = VariantWildCastle;
2029           break;
2030         case 2:
2031         case 3:
2032         case 4:
2033           /* Castling illegal even if K & R happen to start in
2034              normal positions. */
2035           v = VariantNoCastle;
2036           break;
2037         case 5:
2038         case 7:
2039         case 8:
2040         case 10:
2041         case 11:
2042         case 12:
2043         case 13:
2044         case 14:
2045         case 15:
2046         case 18:
2047         case 19:
2048           /* Castling legal iff K & R start in normal positions */
2049           v = VariantNormal;
2050           break;
2051         case 6:
2052         case 20:
2053         case 21:
2054           /* Special wilds for position setup; unclear what to do here */
2055           v = VariantLoadable;
2056           break;
2057         case 9:
2058           /* Bizarre ICC game */
2059           v = VariantTwoKings;
2060           break;
2061         case 16:
2062           v = VariantKriegspiel;
2063           break;
2064         case 17:
2065           v = VariantLosers;
2066           break;
2067         case 22:
2068           v = VariantFischeRandom;
2069           break;
2070         case 23:
2071           v = VariantCrazyhouse;
2072           break;
2073         case 24:
2074           v = VariantBughouse;
2075           break;
2076         case 25:
2077           v = Variant3Check;
2078           break;
2079         case 26:
2080           /* Not quite the same as FICS suicide! */
2081           v = VariantGiveaway;
2082           break;
2083         case 27:
2084           v = VariantAtomic;
2085           break;
2086         case 28:
2087           v = VariantShatranj;
2088           break;
2089
2090         /* Temporary names for future ICC types.  The name *will* change in
2091            the next xboard/WinBoard release after ICC defines it. */
2092         case 29:
2093           v = Variant29;
2094           break;
2095         case 30:
2096           v = Variant30;
2097           break;
2098         case 31:
2099           v = Variant31;
2100           break;
2101         case 32:
2102           v = Variant32;
2103           break;
2104         case 33:
2105           v = Variant33;
2106           break;
2107         case 34:
2108           v = Variant34;
2109           break;
2110         case 35:
2111           v = Variant35;
2112           break;
2113         case 36:
2114           v = Variant36;
2115           break;
2116         case 37:
2117           v = VariantShogi;
2118           break;
2119         case 38:
2120           v = VariantXiangqi;
2121           break;
2122         case 39:
2123           v = VariantCourier;
2124           break;
2125         case 40:
2126           v = VariantGothic;
2127           break;
2128         case 41:
2129           v = VariantCapablanca;
2130           break;
2131         case 42:
2132           v = VariantKnightmate;
2133           break;
2134         case 43:
2135           v = VariantFairy;
2136           break;
2137         case 44:
2138           v = VariantCylinder;
2139           break;
2140         case 45:
2141           v = VariantFalcon;
2142           break;
2143         case 46:
2144           v = VariantCapaRandom;
2145           break;
2146         case 47:
2147           v = VariantBerolina;
2148           break;
2149         case 48:
2150           v = VariantJanus;
2151           break;
2152         case 49:
2153           v = VariantSuper;
2154           break;
2155         case 50:
2156           v = VariantGreat;
2157           break;
2158         case -1:
2159           /* Found "wild" or "w" in the string but no number;
2160              must assume it's normal chess. */
2161           v = VariantNormal;
2162           break;
2163         default:
2164           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2165           if( (len >= MSG_SIZ) && appData.debugMode )
2166             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2167
2168           DisplayError(buf, 0);
2169           v = VariantUnknown;
2170           break;
2171         }
2172       }
2173     }
2174     if (appData.debugMode) {
2175       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2176               e, wnum, VariantName(v));
2177     }
2178     return v;
2179 }
2180
2181 static int leftover_start = 0, leftover_len = 0;
2182 char star_match[STAR_MATCH_N][MSG_SIZ];
2183
2184 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2185    advance *index beyond it, and set leftover_start to the new value of
2186    *index; else return FALSE.  If pattern contains the character '*', it
2187    matches any sequence of characters not containing '\r', '\n', or the
2188    character following the '*' (if any), and the matched sequence(s) are
2189    copied into star_match.
2190    */
2191 int
2192 looking_at ( char *buf, int *index, char *pattern)
2193 {
2194     char *bufp = &buf[*index], *patternp = pattern;
2195     int star_count = 0;
2196     char *matchp = star_match[0];
2197
2198     for (;;) {
2199         if (*patternp == NULLCHAR) {
2200             *index = leftover_start = bufp - buf;
2201             *matchp = NULLCHAR;
2202             return TRUE;
2203         }
2204         if (*bufp == NULLCHAR) return FALSE;
2205         if (*patternp == '*') {
2206             if (*bufp == *(patternp + 1)) {
2207                 *matchp = NULLCHAR;
2208                 matchp = star_match[++star_count];
2209                 patternp += 2;
2210                 bufp++;
2211                 continue;
2212             } else if (*bufp == '\n' || *bufp == '\r') {
2213                 patternp++;
2214                 if (*patternp == NULLCHAR)
2215                   continue;
2216                 else
2217                   return FALSE;
2218             } else {
2219                 *matchp++ = *bufp++;
2220                 continue;
2221             }
2222         }
2223         if (*patternp != *bufp) return FALSE;
2224         patternp++;
2225         bufp++;
2226     }
2227 }
2228
2229 void
2230 SendToPlayer (char *data, int length)
2231 {
2232     int error, outCount;
2233     outCount = OutputToProcess(NoProc, data, length, &error);
2234     if (outCount < length) {
2235         DisplayFatalError(_("Error writing to display"), error, 1);
2236     }
2237 }
2238
2239 void
2240 PackHolding (char packed[], char *holding)
2241 {
2242     char *p = holding;
2243     char *q = packed;
2244     int runlength = 0;
2245     int curr = 9999;
2246     do {
2247         if (*p == curr) {
2248             runlength++;
2249         } else {
2250             switch (runlength) {
2251               case 0:
2252                 break;
2253               case 1:
2254                 *q++ = curr;
2255                 break;
2256               case 2:
2257                 *q++ = curr;
2258                 *q++ = curr;
2259                 break;
2260               default:
2261                 sprintf(q, "%d", runlength);
2262                 while (*q) q++;
2263                 *q++ = curr;
2264                 break;
2265             }
2266             runlength = 1;
2267             curr = *p;
2268         }
2269     } while (*p++);
2270     *q = NULLCHAR;
2271 }
2272
2273 /* Telnet protocol requests from the front end */
2274 void
2275 TelnetRequest (unsigned char ddww, unsigned char option)
2276 {
2277     unsigned char msg[3];
2278     int outCount, outError;
2279
2280     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2281
2282     if (appData.debugMode) {
2283         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2284         switch (ddww) {
2285           case TN_DO:
2286             ddwwStr = "DO";
2287             break;
2288           case TN_DONT:
2289             ddwwStr = "DONT";
2290             break;
2291           case TN_WILL:
2292             ddwwStr = "WILL";
2293             break;
2294           case TN_WONT:
2295             ddwwStr = "WONT";
2296             break;
2297           default:
2298             ddwwStr = buf1;
2299             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2300             break;
2301         }
2302         switch (option) {
2303           case TN_ECHO:
2304             optionStr = "ECHO";
2305             break;
2306           default:
2307             optionStr = buf2;
2308             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2309             break;
2310         }
2311         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2312     }
2313     msg[0] = TN_IAC;
2314     msg[1] = ddww;
2315     msg[2] = option;
2316     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2317     if (outCount < 3) {
2318         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2319     }
2320 }
2321
2322 void
2323 DoEcho ()
2324 {
2325     if (!appData.icsActive) return;
2326     TelnetRequest(TN_DO, TN_ECHO);
2327 }
2328
2329 void
2330 DontEcho ()
2331 {
2332     if (!appData.icsActive) return;
2333     TelnetRequest(TN_DONT, TN_ECHO);
2334 }
2335
2336 void
2337 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2338 {
2339     /* put the holdings sent to us by the server on the board holdings area */
2340     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2341     char p;
2342     ChessSquare piece;
2343
2344     if(gameInfo.holdingsWidth < 2)  return;
2345     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2346         return; // prevent overwriting by pre-board holdings
2347
2348     if( (int)lowestPiece >= BlackPawn ) {
2349         holdingsColumn = 0;
2350         countsColumn = 1;
2351         holdingsStartRow = BOARD_HEIGHT-1;
2352         direction = -1;
2353     } else {
2354         holdingsColumn = BOARD_WIDTH-1;
2355         countsColumn = BOARD_WIDTH-2;
2356         holdingsStartRow = 0;
2357         direction = 1;
2358     }
2359
2360     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2361         board[i][holdingsColumn] = EmptySquare;
2362         board[i][countsColumn]   = (ChessSquare) 0;
2363     }
2364     while( (p=*holdings++) != NULLCHAR ) {
2365         piece = CharToPiece( ToUpper(p) );
2366         if(piece == EmptySquare) continue;
2367         /*j = (int) piece - (int) WhitePawn;*/
2368         j = PieceToNumber(piece);
2369         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2370         if(j < 0) continue;               /* should not happen */
2371         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2372         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2373         board[holdingsStartRow+j*direction][countsColumn]++;
2374     }
2375 }
2376
2377
2378 void
2379 VariantSwitch (Board board, VariantClass newVariant)
2380 {
2381    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2382    static Board oldBoard;
2383
2384    startedFromPositionFile = FALSE;
2385    if(gameInfo.variant == newVariant) return;
2386
2387    /* [HGM] This routine is called each time an assignment is made to
2388     * gameInfo.variant during a game, to make sure the board sizes
2389     * are set to match the new variant. If that means adding or deleting
2390     * holdings, we shift the playing board accordingly
2391     * This kludge is needed because in ICS observe mode, we get boards
2392     * of an ongoing game without knowing the variant, and learn about the
2393     * latter only later. This can be because of the move list we requested,
2394     * in which case the game history is refilled from the beginning anyway,
2395     * but also when receiving holdings of a crazyhouse game. In the latter
2396     * case we want to add those holdings to the already received position.
2397     */
2398
2399
2400    if (appData.debugMode) {
2401      fprintf(debugFP, "Switch board from %s to %s\n",
2402              VariantName(gameInfo.variant), VariantName(newVariant));
2403      setbuf(debugFP, NULL);
2404    }
2405    shuffleOpenings = 0;       /* [HGM] shuffle */
2406    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2407    switch(newVariant)
2408      {
2409      case VariantShogi:
2410        newWidth = 9;  newHeight = 9;
2411        gameInfo.holdingsSize = 7;
2412      case VariantBughouse:
2413      case VariantCrazyhouse:
2414        newHoldingsWidth = 2; break;
2415      case VariantGreat:
2416        newWidth = 10;
2417      case VariantSuper:
2418        newHoldingsWidth = 2;
2419        gameInfo.holdingsSize = 8;
2420        break;
2421      case VariantGothic:
2422      case VariantCapablanca:
2423      case VariantCapaRandom:
2424        newWidth = 10;
2425      default:
2426        newHoldingsWidth = gameInfo.holdingsSize = 0;
2427      };
2428
2429    if(newWidth  != gameInfo.boardWidth  ||
2430       newHeight != gameInfo.boardHeight ||
2431       newHoldingsWidth != gameInfo.holdingsWidth ) {
2432
2433      /* shift position to new playing area, if needed */
2434      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2435        for(i=0; i<BOARD_HEIGHT; i++)
2436          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2437            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2438              board[i][j];
2439        for(i=0; i<newHeight; i++) {
2440          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2441          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2442        }
2443      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2444        for(i=0; i<BOARD_HEIGHT; i++)
2445          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2446            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2447              board[i][j];
2448      }
2449      board[HOLDINGS_SET] = 0;
2450      gameInfo.boardWidth  = newWidth;
2451      gameInfo.boardHeight = newHeight;
2452      gameInfo.holdingsWidth = newHoldingsWidth;
2453      gameInfo.variant = newVariant;
2454      InitDrawingSizes(-2, 0);
2455    } else gameInfo.variant = newVariant;
2456    CopyBoard(oldBoard, board);   // remember correctly formatted board
2457      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2458    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2459 }
2460
2461 static int loggedOn = FALSE;
2462
2463 /*-- Game start info cache: --*/
2464 int gs_gamenum;
2465 char gs_kind[MSG_SIZ];
2466 static char player1Name[128] = "";
2467 static char player2Name[128] = "";
2468 static char cont_seq[] = "\n\\   ";
2469 static int player1Rating = -1;
2470 static int player2Rating = -1;
2471 /*----------------------------*/
2472
2473 ColorClass curColor = ColorNormal;
2474 int suppressKibitz = 0;
2475
2476 // [HGM] seekgraph
2477 Boolean soughtPending = FALSE;
2478 Boolean seekGraphUp;
2479 #define MAX_SEEK_ADS 200
2480 #define SQUARE 0x80
2481 char *seekAdList[MAX_SEEK_ADS];
2482 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2483 float tcList[MAX_SEEK_ADS];
2484 char colorList[MAX_SEEK_ADS];
2485 int nrOfSeekAds = 0;
2486 int minRating = 1010, maxRating = 2800;
2487 int hMargin = 10, vMargin = 20, h, w;
2488 extern int squareSize, lineGap;
2489
2490 void
2491 PlotSeekAd (int i)
2492 {
2493         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2494         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2495         if(r < minRating+100 && r >=0 ) r = minRating+100;
2496         if(r > maxRating) r = maxRating;
2497         if(tc < 1.f) tc = 1.f;
2498         if(tc > 95.f) tc = 95.f;
2499         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2500         y = ((double)r - minRating)/(maxRating - minRating)
2501             * (h-vMargin-squareSize/8-1) + vMargin;
2502         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2503         if(strstr(seekAdList[i], " u ")) color = 1;
2504         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2505            !strstr(seekAdList[i], "bullet") &&
2506            !strstr(seekAdList[i], "blitz") &&
2507            !strstr(seekAdList[i], "standard") ) color = 2;
2508         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2509         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2510 }
2511
2512 void
2513 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2514 {
2515         char buf[MSG_SIZ], *ext = "";
2516         VariantClass v = StringToVariant(type);
2517         if(strstr(type, "wild")) {
2518             ext = type + 4; // append wild number
2519             if(v == VariantFischeRandom) type = "chess960"; else
2520             if(v == VariantLoadable) type = "setup"; else
2521             type = VariantName(v);
2522         }
2523         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2524         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2525             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2526             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2527             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2528             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2529             seekNrList[nrOfSeekAds] = nr;
2530             zList[nrOfSeekAds] = 0;
2531             seekAdList[nrOfSeekAds++] = StrSave(buf);
2532             if(plot) PlotSeekAd(nrOfSeekAds-1);
2533         }
2534 }
2535
2536 void
2537 EraseSeekDot (int i)
2538 {
2539     int x = xList[i], y = yList[i], d=squareSize/4, k;
2540     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2541     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2542     // now replot every dot that overlapped
2543     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2544         int xx = xList[k], yy = yList[k];
2545         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2546             DrawSeekDot(xx, yy, colorList[k]);
2547     }
2548 }
2549
2550 void
2551 RemoveSeekAd (int nr)
2552 {
2553         int i;
2554         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2555             EraseSeekDot(i);
2556             if(seekAdList[i]) free(seekAdList[i]);
2557             seekAdList[i] = seekAdList[--nrOfSeekAds];
2558             seekNrList[i] = seekNrList[nrOfSeekAds];
2559             ratingList[i] = ratingList[nrOfSeekAds];
2560             colorList[i]  = colorList[nrOfSeekAds];
2561             tcList[i] = tcList[nrOfSeekAds];
2562             xList[i]  = xList[nrOfSeekAds];
2563             yList[i]  = yList[nrOfSeekAds];
2564             zList[i]  = zList[nrOfSeekAds];
2565             seekAdList[nrOfSeekAds] = NULL;
2566             break;
2567         }
2568 }
2569
2570 Boolean
2571 MatchSoughtLine (char *line)
2572 {
2573     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2574     int nr, base, inc, u=0; char dummy;
2575
2576     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2577        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2578        (u=1) &&
2579        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2580         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2581         // match: compact and save the line
2582         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2583         return TRUE;
2584     }
2585     return FALSE;
2586 }
2587
2588 int
2589 DrawSeekGraph ()
2590 {
2591     int i;
2592     if(!seekGraphUp) return FALSE;
2593     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2594     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2595
2596     DrawSeekBackground(0, 0, w, h);
2597     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2598     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2599     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2600         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2601         yy = h-1-yy;
2602         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2603         if(i%500 == 0) {
2604             char buf[MSG_SIZ];
2605             snprintf(buf, MSG_SIZ, "%d", i);
2606             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2607         }
2608     }
2609     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2610     for(i=1; i<100; i+=(i<10?1:5)) {
2611         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2612         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2613         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2614             char buf[MSG_SIZ];
2615             snprintf(buf, MSG_SIZ, "%d", i);
2616             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2617         }
2618     }
2619     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2620     return TRUE;
2621 }
2622
2623 int
2624 SeekGraphClick (ClickType click, int x, int y, int moving)
2625 {
2626     static int lastDown = 0, displayed = 0, lastSecond;
2627     if(y < 0) return FALSE;
2628     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2629         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2630         if(!seekGraphUp) return FALSE;
2631         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2632         DrawPosition(TRUE, NULL);
2633         return TRUE;
2634     }
2635     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2636         if(click == Release || moving) return FALSE;
2637         nrOfSeekAds = 0;
2638         soughtPending = TRUE;
2639         SendToICS(ics_prefix);
2640         SendToICS("sought\n"); // should this be "sought all"?
2641     } else { // issue challenge based on clicked ad
2642         int dist = 10000; int i, closest = 0, second = 0;
2643         for(i=0; i<nrOfSeekAds; i++) {
2644             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2645             if(d < dist) { dist = d; closest = i; }
2646             second += (d - zList[i] < 120); // count in-range ads
2647             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2648         }
2649         if(dist < 120) {
2650             char buf[MSG_SIZ];
2651             second = (second > 1);
2652             if(displayed != closest || second != lastSecond) {
2653                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2654                 lastSecond = second; displayed = closest;
2655             }
2656             if(click == Press) {
2657                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2658                 lastDown = closest;
2659                 return TRUE;
2660             } // on press 'hit', only show info
2661             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2662             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2663             SendToICS(ics_prefix);
2664             SendToICS(buf);
2665             return TRUE; // let incoming board of started game pop down the graph
2666         } else if(click == Release) { // release 'miss' is ignored
2667             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2668             if(moving == 2) { // right up-click
2669                 nrOfSeekAds = 0; // refresh graph
2670                 soughtPending = TRUE;
2671                 SendToICS(ics_prefix);
2672                 SendToICS("sought\n"); // should this be "sought all"?
2673             }
2674             return TRUE;
2675         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2676         // press miss or release hit 'pop down' seek graph
2677         seekGraphUp = FALSE;
2678         DrawPosition(TRUE, NULL);
2679     }
2680     return TRUE;
2681 }
2682
2683 void
2684 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2685 {
2686 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2687 #define STARTED_NONE 0
2688 #define STARTED_MOVES 1
2689 #define STARTED_BOARD 2
2690 #define STARTED_OBSERVE 3
2691 #define STARTED_HOLDINGS 4
2692 #define STARTED_CHATTER 5
2693 #define STARTED_COMMENT 6
2694 #define STARTED_MOVES_NOHIDE 7
2695
2696     static int started = STARTED_NONE;
2697     static char parse[20000];
2698     static int parse_pos = 0;
2699     static char buf[BUF_SIZE + 1];
2700     static int firstTime = TRUE, intfSet = FALSE;
2701     static ColorClass prevColor = ColorNormal;
2702     static int savingComment = FALSE;
2703     static int cmatch = 0; // continuation sequence match
2704     char *bp;
2705     char str[MSG_SIZ];
2706     int i, oldi;
2707     int buf_len;
2708     int next_out;
2709     int tkind;
2710     int backup;    /* [DM] For zippy color lines */
2711     char *p;
2712     char talker[MSG_SIZ]; // [HGM] chat
2713     int channel;
2714
2715     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2716
2717     if (appData.debugMode) {
2718       if (!error) {
2719         fprintf(debugFP, "<ICS: ");
2720         show_bytes(debugFP, data, count);
2721         fprintf(debugFP, "\n");
2722       }
2723     }
2724
2725     if (appData.debugMode) { int f = forwardMostMove;
2726         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2727                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2728                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2729     }
2730     if (count > 0) {
2731         /* If last read ended with a partial line that we couldn't parse,
2732            prepend it to the new read and try again. */
2733         if (leftover_len > 0) {
2734             for (i=0; i<leftover_len; i++)
2735               buf[i] = buf[leftover_start + i];
2736         }
2737
2738     /* copy new characters into the buffer */
2739     bp = buf + leftover_len;
2740     buf_len=leftover_len;
2741     for (i=0; i<count; i++)
2742     {
2743         // ignore these
2744         if (data[i] == '\r')
2745             continue;
2746
2747         // join lines split by ICS?
2748         if (!appData.noJoin)
2749         {
2750             /*
2751                 Joining just consists of finding matches against the
2752                 continuation sequence, and discarding that sequence
2753                 if found instead of copying it.  So, until a match
2754                 fails, there's nothing to do since it might be the
2755                 complete sequence, and thus, something we don't want
2756                 copied.
2757             */
2758             if (data[i] == cont_seq[cmatch])
2759             {
2760                 cmatch++;
2761                 if (cmatch == strlen(cont_seq))
2762                 {
2763                     cmatch = 0; // complete match.  just reset the counter
2764
2765                     /*
2766                         it's possible for the ICS to not include the space
2767                         at the end of the last word, making our [correct]
2768                         join operation fuse two separate words.  the server
2769                         does this when the space occurs at the width setting.
2770                     */
2771                     if (!buf_len || buf[buf_len-1] != ' ')
2772                     {
2773                         *bp++ = ' ';
2774                         buf_len++;
2775                     }
2776                 }
2777                 continue;
2778             }
2779             else if (cmatch)
2780             {
2781                 /*
2782                     match failed, so we have to copy what matched before
2783                     falling through and copying this character.  In reality,
2784                     this will only ever be just the newline character, but
2785                     it doesn't hurt to be precise.
2786                 */
2787                 strncpy(bp, cont_seq, cmatch);
2788                 bp += cmatch;
2789                 buf_len += cmatch;
2790                 cmatch = 0;
2791             }
2792         }
2793
2794         // copy this char
2795         *bp++ = data[i];
2796         buf_len++;
2797     }
2798
2799         buf[buf_len] = NULLCHAR;
2800 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2801         next_out = 0;
2802         leftover_start = 0;
2803
2804         i = 0;
2805         while (i < buf_len) {
2806             /* Deal with part of the TELNET option negotiation
2807                protocol.  We refuse to do anything beyond the
2808                defaults, except that we allow the WILL ECHO option,
2809                which ICS uses to turn off password echoing when we are
2810                directly connected to it.  We reject this option
2811                if localLineEditing mode is on (always on in xboard)
2812                and we are talking to port 23, which might be a real
2813                telnet server that will try to keep WILL ECHO on permanently.
2814              */
2815             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2816                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2817                 unsigned char option;
2818                 oldi = i;
2819                 switch ((unsigned char) buf[++i]) {
2820                   case TN_WILL:
2821                     if (appData.debugMode)
2822                       fprintf(debugFP, "\n<WILL ");
2823                     switch (option = (unsigned char) buf[++i]) {
2824                       case TN_ECHO:
2825                         if (appData.debugMode)
2826                           fprintf(debugFP, "ECHO ");
2827                         /* Reply only if this is a change, according
2828                            to the protocol rules. */
2829                         if (remoteEchoOption) break;
2830                         if (appData.localLineEditing &&
2831                             atoi(appData.icsPort) == TN_PORT) {
2832                             TelnetRequest(TN_DONT, TN_ECHO);
2833                         } else {
2834                             EchoOff();
2835                             TelnetRequest(TN_DO, TN_ECHO);
2836                             remoteEchoOption = TRUE;
2837                         }
2838                         break;
2839                       default:
2840                         if (appData.debugMode)
2841                           fprintf(debugFP, "%d ", option);
2842                         /* Whatever this is, we don't want it. */
2843                         TelnetRequest(TN_DONT, option);
2844                         break;
2845                     }
2846                     break;
2847                   case TN_WONT:
2848                     if (appData.debugMode)
2849                       fprintf(debugFP, "\n<WONT ");
2850                     switch (option = (unsigned char) buf[++i]) {
2851                       case TN_ECHO:
2852                         if (appData.debugMode)
2853                           fprintf(debugFP, "ECHO ");
2854                         /* Reply only if this is a change, according
2855                            to the protocol rules. */
2856                         if (!remoteEchoOption) break;
2857                         EchoOn();
2858                         TelnetRequest(TN_DONT, TN_ECHO);
2859                         remoteEchoOption = FALSE;
2860                         break;
2861                       default:
2862                         if (appData.debugMode)
2863                           fprintf(debugFP, "%d ", (unsigned char) option);
2864                         /* Whatever this is, it must already be turned
2865                            off, because we never agree to turn on
2866                            anything non-default, so according to the
2867                            protocol rules, we don't reply. */
2868                         break;
2869                     }
2870                     break;
2871                   case TN_DO:
2872                     if (appData.debugMode)
2873                       fprintf(debugFP, "\n<DO ");
2874                     switch (option = (unsigned char) buf[++i]) {
2875                       default:
2876                         /* Whatever this is, we refuse to do it. */
2877                         if (appData.debugMode)
2878                           fprintf(debugFP, "%d ", option);
2879                         TelnetRequest(TN_WONT, option);
2880                         break;
2881                     }
2882                     break;
2883                   case TN_DONT:
2884                     if (appData.debugMode)
2885                       fprintf(debugFP, "\n<DONT ");
2886                     switch (option = (unsigned char) buf[++i]) {
2887                       default:
2888                         if (appData.debugMode)
2889                           fprintf(debugFP, "%d ", option);
2890                         /* Whatever this is, we are already not doing
2891                            it, because we never agree to do anything
2892                            non-default, so according to the protocol
2893                            rules, we don't reply. */
2894                         break;
2895                     }
2896                     break;
2897                   case TN_IAC:
2898                     if (appData.debugMode)
2899                       fprintf(debugFP, "\n<IAC ");
2900                     /* Doubled IAC; pass it through */
2901                     i--;
2902                     break;
2903                   default:
2904                     if (appData.debugMode)
2905                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2906                     /* Drop all other telnet commands on the floor */
2907                     break;
2908                 }
2909                 if (oldi > next_out)
2910                   SendToPlayer(&buf[next_out], oldi - next_out);
2911                 if (++i > next_out)
2912                   next_out = i;
2913                 continue;
2914             }
2915
2916             /* OK, this at least will *usually* work */
2917             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2918                 loggedOn = TRUE;
2919             }
2920
2921             if (loggedOn && !intfSet) {
2922                 if (ics_type == ICS_ICC) {
2923                   snprintf(str, MSG_SIZ,
2924                           "/set-quietly interface %s\n/set-quietly style 12\n",
2925                           programVersion);
2926                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2927                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2928                 } else if (ics_type == ICS_CHESSNET) {
2929                   snprintf(str, MSG_SIZ, "/style 12\n");
2930                 } else {
2931                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2932                   strcat(str, programVersion);
2933                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2934                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2935                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2936 #ifdef WIN32
2937                   strcat(str, "$iset nohighlight 1\n");
2938 #endif
2939                   strcat(str, "$iset lock 1\n$style 12\n");
2940                 }
2941                 SendToICS(str);
2942                 NotifyFrontendLogin();
2943                 intfSet = TRUE;
2944             }
2945
2946             if (started == STARTED_COMMENT) {
2947                 /* Accumulate characters in comment */
2948                 parse[parse_pos++] = buf[i];
2949                 if (buf[i] == '\n') {
2950                     parse[parse_pos] = NULLCHAR;
2951                     if(chattingPartner>=0) {
2952                         char mess[MSG_SIZ];
2953                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2954                         OutputChatMessage(chattingPartner, mess);
2955                         chattingPartner = -1;
2956                         next_out = i+1; // [HGM] suppress printing in ICS window
2957                     } else
2958                     if(!suppressKibitz) // [HGM] kibitz
2959                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2960                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2961                         int nrDigit = 0, nrAlph = 0, j;
2962                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2963                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2964                         parse[parse_pos] = NULLCHAR;
2965                         // try to be smart: if it does not look like search info, it should go to
2966                         // ICS interaction window after all, not to engine-output window.
2967                         for(j=0; j<parse_pos; j++) { // count letters and digits
2968                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2969                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2970                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2971                         }
2972                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2973                             int depth=0; float score;
2974                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2975                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2976                                 pvInfoList[forwardMostMove-1].depth = depth;
2977                                 pvInfoList[forwardMostMove-1].score = 100*score;
2978                             }
2979                             OutputKibitz(suppressKibitz, parse);
2980                         } else {
2981                             char tmp[MSG_SIZ];
2982                             if(gameMode == IcsObserving) // restore original ICS messages
2983                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2984                             else
2985                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2986                             SendToPlayer(tmp, strlen(tmp));
2987                         }
2988                         next_out = i+1; // [HGM] suppress printing in ICS window
2989                     }
2990                     started = STARTED_NONE;
2991                 } else {
2992                     /* Don't match patterns against characters in comment */
2993                     i++;
2994                     continue;
2995                 }
2996             }
2997             if (started == STARTED_CHATTER) {
2998                 if (buf[i] != '\n') {
2999                     /* Don't match patterns against characters in chatter */
3000                     i++;
3001                     continue;
3002                 }
3003                 started = STARTED_NONE;
3004                 if(suppressKibitz) next_out = i+1;
3005             }
3006
3007             /* Kludge to deal with rcmd protocol */
3008             if (firstTime && looking_at(buf, &i, "\001*")) {
3009                 DisplayFatalError(&buf[1], 0, 1);
3010                 continue;
3011             } else {
3012                 firstTime = FALSE;
3013             }
3014
3015             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3016                 ics_type = ICS_ICC;
3017                 ics_prefix = "/";
3018                 if (appData.debugMode)
3019                   fprintf(debugFP, "ics_type %d\n", ics_type);
3020                 continue;
3021             }
3022             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3023                 ics_type = ICS_FICS;
3024                 ics_prefix = "$";
3025                 if (appData.debugMode)
3026                   fprintf(debugFP, "ics_type %d\n", ics_type);
3027                 continue;
3028             }
3029             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3030                 ics_type = ICS_CHESSNET;
3031                 ics_prefix = "/";
3032                 if (appData.debugMode)
3033                   fprintf(debugFP, "ics_type %d\n", ics_type);
3034                 continue;
3035             }
3036
3037             if (!loggedOn &&
3038                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3039                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3040                  looking_at(buf, &i, "will be \"*\""))) {
3041               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3042               continue;
3043             }
3044
3045             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3046               char buf[MSG_SIZ];
3047               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3048               DisplayIcsInteractionTitle(buf);
3049               have_set_title = TRUE;
3050             }
3051
3052             /* skip finger notes */
3053             if (started == STARTED_NONE &&
3054                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3055                  (buf[i] == '1' && buf[i+1] == '0')) &&
3056                 buf[i+2] == ':' && buf[i+3] == ' ') {
3057               started = STARTED_CHATTER;
3058               i += 3;
3059               continue;
3060             }
3061
3062             oldi = i;
3063             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3064             if(appData.seekGraph) {
3065                 if(soughtPending && MatchSoughtLine(buf+i)) {
3066                     i = strstr(buf+i, "rated") - buf;
3067                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3068                     next_out = leftover_start = i;
3069                     started = STARTED_CHATTER;
3070                     suppressKibitz = TRUE;
3071                     continue;
3072                 }
3073                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3074                         && looking_at(buf, &i, "* ads displayed")) {
3075                     soughtPending = FALSE;
3076                     seekGraphUp = TRUE;
3077                     DrawSeekGraph();
3078                     continue;
3079                 }
3080                 if(appData.autoRefresh) {
3081                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3082                         int s = (ics_type == ICS_ICC); // ICC format differs
3083                         if(seekGraphUp)
3084                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3085                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3086                         looking_at(buf, &i, "*% "); // eat prompt
3087                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3088                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3089                         next_out = i; // suppress
3090                         continue;
3091                     }
3092                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3093                         char *p = star_match[0];
3094                         while(*p) {
3095                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3096                             while(*p && *p++ != ' '); // next
3097                         }
3098                         looking_at(buf, &i, "*% "); // eat prompt
3099                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3100                         next_out = i;
3101                         continue;
3102                     }
3103                 }
3104             }
3105
3106             /* skip formula vars */
3107             if (started == STARTED_NONE &&
3108                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3109               started = STARTED_CHATTER;
3110               i += 3;
3111               continue;
3112             }
3113
3114             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3115             if (appData.autoKibitz && started == STARTED_NONE &&
3116                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3117                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3118                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3119                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3120                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3121                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3122                         suppressKibitz = TRUE;
3123                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3124                         next_out = i;
3125                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3126                                 && (gameMode == IcsPlayingWhite)) ||
3127                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3128                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3129                             started = STARTED_CHATTER; // own kibitz we simply discard
3130                         else {
3131                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3132                             parse_pos = 0; parse[0] = NULLCHAR;
3133                             savingComment = TRUE;
3134                             suppressKibitz = gameMode != IcsObserving ? 2 :
3135                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3136                         }
3137                         continue;
3138                 } else
3139                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3140                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3141                          && atoi(star_match[0])) {
3142                     // suppress the acknowledgements of our own autoKibitz
3143                     char *p;
3144                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3145                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3146                     SendToPlayer(star_match[0], strlen(star_match[0]));
3147                     if(looking_at(buf, &i, "*% ")) // eat prompt
3148                         suppressKibitz = FALSE;
3149                     next_out = i;
3150                     continue;
3151                 }
3152             } // [HGM] kibitz: end of patch
3153
3154             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3155
3156             // [HGM] chat: intercept tells by users for which we have an open chat window
3157             channel = -1;
3158             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3159                                            looking_at(buf, &i, "* whispers:") ||
3160                                            looking_at(buf, &i, "* kibitzes:") ||
3161                                            looking_at(buf, &i, "* shouts:") ||
3162                                            looking_at(buf, &i, "* c-shouts:") ||
3163                                            looking_at(buf, &i, "--> * ") ||
3164                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3165                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3166                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3167                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3168                 int p;
3169                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3170                 chattingPartner = -1;
3171
3172                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3173                 for(p=0; p<MAX_CHAT; p++) {
3174                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3175                     talker[0] = '['; strcat(talker, "] ");
3176                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3177                     chattingPartner = p; break;
3178                     }
3179                 } else
3180                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3181                 for(p=0; p<MAX_CHAT; p++) {
3182                     if(!strcmp("kibitzes", chatPartner[p])) {
3183                         talker[0] = '['; strcat(talker, "] ");
3184                         chattingPartner = p; break;
3185                     }
3186                 } else
3187                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3188                 for(p=0; p<MAX_CHAT; p++) {
3189                     if(!strcmp("whispers", chatPartner[p])) {
3190                         talker[0] = '['; strcat(talker, "] ");
3191                         chattingPartner = p; break;
3192                     }
3193                 } else
3194                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3195                   if(buf[i-8] == '-' && buf[i-3] == 't')
3196                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3197                     if(!strcmp("c-shouts", chatPartner[p])) {
3198                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3199                         chattingPartner = p; break;
3200                     }
3201                   }
3202                   if(chattingPartner < 0)
3203                   for(p=0; p<MAX_CHAT; p++) {
3204                     if(!strcmp("shouts", chatPartner[p])) {
3205                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3206                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3207                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3208                         chattingPartner = p; break;
3209                     }
3210                   }
3211                 }
3212                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3213                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3214                     talker[0] = 0; Colorize(ColorTell, FALSE);
3215                     chattingPartner = p; break;
3216                 }
3217                 if(chattingPartner<0) i = oldi; else {
3218                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3219                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3220                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3221                     started = STARTED_COMMENT;
3222                     parse_pos = 0; parse[0] = NULLCHAR;
3223                     savingComment = 3 + chattingPartner; // counts as TRUE
3224                     suppressKibitz = TRUE;
3225                     continue;
3226                 }
3227             } // [HGM] chat: end of patch
3228
3229           backup = i;
3230             if (appData.zippyTalk || appData.zippyPlay) {
3231                 /* [DM] Backup address for color zippy lines */
3232 #if ZIPPY
3233                if (loggedOn == TRUE)
3234                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3235                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3236 #endif
3237             } // [DM] 'else { ' deleted
3238                 if (
3239                     /* Regular tells and says */
3240                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3241                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3242                     looking_at(buf, &i, "* says: ") ||
3243                     /* Don't color "message" or "messages" output */
3244                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3245                     looking_at(buf, &i, "*. * at *:*: ") ||
3246                     looking_at(buf, &i, "--* (*:*): ") ||
3247                     /* Message notifications (same color as tells) */
3248                     looking_at(buf, &i, "* has left a message ") ||
3249                     looking_at(buf, &i, "* just sent you a message:\n") ||
3250                     /* Whispers and kibitzes */
3251                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3252                     looking_at(buf, &i, "* kibitzes: ") ||
3253                     /* Channel tells */
3254                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3255
3256                   if (tkind == 1 && strchr(star_match[0], ':')) {
3257                       /* Avoid "tells you:" spoofs in channels */
3258                      tkind = 3;
3259                   }
3260                   if (star_match[0][0] == NULLCHAR ||
3261                       strchr(star_match[0], ' ') ||
3262                       (tkind == 3 && strchr(star_match[1], ' '))) {
3263                     /* Reject bogus matches */
3264                     i = oldi;
3265                   } else {
3266                     if (appData.colorize) {
3267                       if (oldi > next_out) {
3268                         SendToPlayer(&buf[next_out], oldi - next_out);
3269                         next_out = oldi;
3270                       }
3271                       switch (tkind) {
3272                       case 1:
3273                         Colorize(ColorTell, FALSE);
3274                         curColor = ColorTell;
3275                         break;
3276                       case 2:
3277                         Colorize(ColorKibitz, FALSE);
3278                         curColor = ColorKibitz;
3279                         break;
3280                       case 3:
3281                         p = strrchr(star_match[1], '(');
3282                         if (p == NULL) {
3283                           p = star_match[1];
3284                         } else {
3285                           p++;
3286                         }
3287                         if (atoi(p) == 1) {
3288                           Colorize(ColorChannel1, FALSE);
3289                           curColor = ColorChannel1;
3290                         } else {
3291                           Colorize(ColorChannel, FALSE);
3292                           curColor = ColorChannel;
3293                         }
3294                         break;
3295                       case 5:
3296                         curColor = ColorNormal;
3297                         break;
3298                       }
3299                     }
3300                     if (started == STARTED_NONE && appData.autoComment &&
3301                         (gameMode == IcsObserving ||
3302                          gameMode == IcsPlayingWhite ||
3303                          gameMode == IcsPlayingBlack)) {
3304                       parse_pos = i - oldi;
3305                       memcpy(parse, &buf[oldi], parse_pos);
3306                       parse[parse_pos] = NULLCHAR;
3307                       started = STARTED_COMMENT;
3308                       savingComment = TRUE;
3309                     } else {
3310                       started = STARTED_CHATTER;
3311                       savingComment = FALSE;
3312                     }
3313                     loggedOn = TRUE;
3314                     continue;
3315                   }
3316                 }
3317
3318                 if (looking_at(buf, &i, "* s-shouts: ") ||
3319                     looking_at(buf, &i, "* c-shouts: ")) {
3320                     if (appData.colorize) {
3321                         if (oldi > next_out) {
3322                             SendToPlayer(&buf[next_out], oldi - next_out);
3323                             next_out = oldi;
3324                         }
3325                         Colorize(ColorSShout, FALSE);
3326                         curColor = ColorSShout;
3327                     }
3328                     loggedOn = TRUE;
3329                     started = STARTED_CHATTER;
3330                     continue;
3331                 }
3332
3333                 if (looking_at(buf, &i, "--->")) {
3334                     loggedOn = TRUE;
3335                     continue;
3336                 }
3337
3338                 if (looking_at(buf, &i, "* shouts: ") ||
3339                     looking_at(buf, &i, "--> ")) {
3340                     if (appData.colorize) {
3341                         if (oldi > next_out) {
3342                             SendToPlayer(&buf[next_out], oldi - next_out);
3343                             next_out = oldi;
3344                         }
3345                         Colorize(ColorShout, FALSE);
3346                         curColor = ColorShout;
3347                     }
3348                     loggedOn = TRUE;
3349                     started = STARTED_CHATTER;
3350                     continue;
3351                 }
3352
3353                 if (looking_at( buf, &i, "Challenge:")) {
3354                     if (appData.colorize) {
3355                         if (oldi > next_out) {
3356                             SendToPlayer(&buf[next_out], oldi - next_out);
3357                             next_out = oldi;
3358                         }
3359                         Colorize(ColorChallenge, FALSE);
3360                         curColor = ColorChallenge;
3361                     }
3362                     loggedOn = TRUE;
3363                     continue;
3364                 }
3365
3366                 if (looking_at(buf, &i, "* offers you") ||
3367                     looking_at(buf, &i, "* offers to be") ||
3368                     looking_at(buf, &i, "* would like to") ||
3369                     looking_at(buf, &i, "* requests to") ||
3370                     looking_at(buf, &i, "Your opponent offers") ||
3371                     looking_at(buf, &i, "Your opponent requests")) {
3372
3373                     if (appData.colorize) {
3374                         if (oldi > next_out) {
3375                             SendToPlayer(&buf[next_out], oldi - next_out);
3376                             next_out = oldi;
3377                         }
3378                         Colorize(ColorRequest, FALSE);
3379                         curColor = ColorRequest;
3380                     }
3381                     continue;
3382                 }
3383
3384                 if (looking_at(buf, &i, "* (*) seeking")) {
3385                     if (appData.colorize) {
3386                         if (oldi > next_out) {
3387                             SendToPlayer(&buf[next_out], oldi - next_out);
3388                             next_out = oldi;
3389                         }
3390                         Colorize(ColorSeek, FALSE);
3391                         curColor = ColorSeek;
3392                     }
3393                     continue;
3394             }
3395
3396           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3397
3398             if (looking_at(buf, &i, "\\   ")) {
3399                 if (prevColor != ColorNormal) {
3400                     if (oldi > next_out) {
3401                         SendToPlayer(&buf[next_out], oldi - next_out);
3402                         next_out = oldi;
3403                     }
3404                     Colorize(prevColor, TRUE);
3405                     curColor = prevColor;
3406                 }
3407                 if (savingComment) {
3408                     parse_pos = i - oldi;
3409                     memcpy(parse, &buf[oldi], parse_pos);
3410                     parse[parse_pos] = NULLCHAR;
3411                     started = STARTED_COMMENT;
3412                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3413                         chattingPartner = savingComment - 3; // kludge to remember the box
3414                 } else {
3415                     started = STARTED_CHATTER;
3416                 }
3417                 continue;
3418             }
3419
3420             if (looking_at(buf, &i, "Black Strength :") ||
3421                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3422                 looking_at(buf, &i, "<10>") ||
3423                 looking_at(buf, &i, "#@#")) {
3424                 /* Wrong board style */
3425                 loggedOn = TRUE;
3426                 SendToICS(ics_prefix);
3427                 SendToICS("set style 12\n");
3428                 SendToICS(ics_prefix);
3429                 SendToICS("refresh\n");
3430                 continue;
3431             }
3432
3433             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3434                 ICSInitScript();
3435                 have_sent_ICS_logon = 1;
3436                 continue;
3437             }
3438
3439             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3440                 (looking_at(buf, &i, "\n<12> ") ||
3441                  looking_at(buf, &i, "<12> "))) {
3442                 loggedOn = TRUE;
3443                 if (oldi > next_out) {
3444                     SendToPlayer(&buf[next_out], oldi - next_out);
3445                 }
3446                 next_out = i;
3447                 started = STARTED_BOARD;
3448                 parse_pos = 0;
3449                 continue;
3450             }
3451
3452             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3453                 looking_at(buf, &i, "<b1> ")) {
3454                 if (oldi > next_out) {
3455                     SendToPlayer(&buf[next_out], oldi - next_out);
3456                 }
3457                 next_out = i;
3458                 started = STARTED_HOLDINGS;
3459                 parse_pos = 0;
3460                 continue;
3461             }
3462
3463             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3464                 loggedOn = TRUE;
3465                 /* Header for a move list -- first line */
3466
3467                 switch (ics_getting_history) {
3468                   case H_FALSE:
3469                     switch (gameMode) {
3470                       case IcsIdle:
3471                       case BeginningOfGame:
3472                         /* User typed "moves" or "oldmoves" while we
3473                            were idle.  Pretend we asked for these
3474                            moves and soak them up so user can step
3475                            through them and/or save them.
3476                            */
3477                         Reset(FALSE, TRUE);
3478                         gameMode = IcsObserving;
3479                         ModeHighlight();
3480                         ics_gamenum = -1;
3481                         ics_getting_history = H_GOT_UNREQ_HEADER;
3482                         break;
3483                       case EditGame: /*?*/
3484                       case EditPosition: /*?*/
3485                         /* Should above feature work in these modes too? */
3486                         /* For now it doesn't */
3487                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3488                         break;
3489                       default:
3490                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3491                         break;
3492                     }
3493                     break;
3494                   case H_REQUESTED:
3495                     /* Is this the right one? */
3496                     if (gameInfo.white && gameInfo.black &&
3497                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3498                         strcmp(gameInfo.black, star_match[2]) == 0) {
3499                         /* All is well */
3500                         ics_getting_history = H_GOT_REQ_HEADER;
3501                     }
3502                     break;
3503                   case H_GOT_REQ_HEADER:
3504                   case H_GOT_UNREQ_HEADER:
3505                   case H_GOT_UNWANTED_HEADER:
3506                   case H_GETTING_MOVES:
3507                     /* Should not happen */
3508                     DisplayError(_("Error gathering move list: two headers"), 0);
3509                     ics_getting_history = H_FALSE;
3510                     break;
3511                 }
3512
3513                 /* Save player ratings into gameInfo if needed */
3514                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3515                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3516                     (gameInfo.whiteRating == -1 ||
3517                      gameInfo.blackRating == -1)) {
3518
3519                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3520                     gameInfo.blackRating = string_to_rating(star_match[3]);
3521                     if (appData.debugMode)
3522                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3523                               gameInfo.whiteRating, gameInfo.blackRating);
3524                 }
3525                 continue;
3526             }
3527
3528             if (looking_at(buf, &i,
3529               "* * match, initial time: * minute*, increment: * second")) {
3530                 /* Header for a move list -- second line */
3531                 /* Initial board will follow if this is a wild game */
3532                 if (gameInfo.event != NULL) free(gameInfo.event);
3533                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3534                 gameInfo.event = StrSave(str);
3535                 /* [HGM] we switched variant. Translate boards if needed. */
3536                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3537                 continue;
3538             }
3539
3540             if (looking_at(buf, &i, "Move  ")) {
3541                 /* Beginning of a move list */
3542                 switch (ics_getting_history) {
3543                   case H_FALSE:
3544                     /* Normally should not happen */
3545                     /* Maybe user hit reset while we were parsing */
3546                     break;
3547                   case H_REQUESTED:
3548                     /* Happens if we are ignoring a move list that is not
3549                      * the one we just requested.  Common if the user
3550                      * tries to observe two games without turning off
3551                      * getMoveList */
3552                     break;
3553                   case H_GETTING_MOVES:
3554                     /* Should not happen */
3555                     DisplayError(_("Error gathering move list: nested"), 0);
3556                     ics_getting_history = H_FALSE;
3557                     break;
3558                   case H_GOT_REQ_HEADER:
3559                     ics_getting_history = H_GETTING_MOVES;
3560                     started = STARTED_MOVES;
3561                     parse_pos = 0;
3562                     if (oldi > next_out) {
3563                         SendToPlayer(&buf[next_out], oldi - next_out);
3564                     }
3565                     break;
3566                   case H_GOT_UNREQ_HEADER:
3567                     ics_getting_history = H_GETTING_MOVES;
3568                     started = STARTED_MOVES_NOHIDE;
3569                     parse_pos = 0;
3570                     break;
3571                   case H_GOT_UNWANTED_HEADER:
3572                     ics_getting_history = H_FALSE;
3573                     break;
3574                 }
3575                 continue;
3576             }
3577
3578             if (looking_at(buf, &i, "% ") ||
3579                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3580                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3581                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3582                     soughtPending = FALSE;
3583                     seekGraphUp = TRUE;
3584                     DrawSeekGraph();
3585                 }
3586                 if(suppressKibitz) next_out = i;
3587                 savingComment = FALSE;
3588                 suppressKibitz = 0;
3589                 switch (started) {
3590                   case STARTED_MOVES:
3591                   case STARTED_MOVES_NOHIDE:
3592                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3593                     parse[parse_pos + i - oldi] = NULLCHAR;
3594                     ParseGameHistory(parse);
3595 #if ZIPPY
3596                     if (appData.zippyPlay && first.initDone) {
3597                         FeedMovesToProgram(&first, forwardMostMove);
3598                         if (gameMode == IcsPlayingWhite) {
3599                             if (WhiteOnMove(forwardMostMove)) {
3600                                 if (first.sendTime) {
3601                                   if (first.useColors) {
3602                                     SendToProgram("black\n", &first);
3603                                   }
3604                                   SendTimeRemaining(&first, TRUE);
3605                                 }
3606                                 if (first.useColors) {
3607                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3608                                 }
3609                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3610                                 first.maybeThinking = TRUE;
3611                             } else {
3612                                 if (first.usePlayother) {
3613                                   if (first.sendTime) {
3614                                     SendTimeRemaining(&first, TRUE);
3615                                   }
3616                                   SendToProgram("playother\n", &first);
3617                                   firstMove = FALSE;
3618                                 } else {
3619                                   firstMove = TRUE;
3620                                 }
3621                             }
3622                         } else if (gameMode == IcsPlayingBlack) {
3623                             if (!WhiteOnMove(forwardMostMove)) {
3624                                 if (first.sendTime) {
3625                                   if (first.useColors) {
3626                                     SendToProgram("white\n", &first);
3627                                   }
3628                                   SendTimeRemaining(&first, FALSE);
3629                                 }
3630                                 if (first.useColors) {
3631                                   SendToProgram("black\n", &first);
3632                                 }
3633                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3634                                 first.maybeThinking = TRUE;
3635                             } else {
3636                                 if (first.usePlayother) {
3637                                   if (first.sendTime) {
3638                                     SendTimeRemaining(&first, FALSE);
3639                                   }
3640                                   SendToProgram("playother\n", &first);
3641                                   firstMove = FALSE;
3642                                 } else {
3643                                   firstMove = TRUE;
3644                                 }
3645                             }
3646                         }
3647                     }
3648 #endif
3649                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3650                         /* Moves came from oldmoves or moves command
3651                            while we weren't doing anything else.
3652                            */
3653                         currentMove = forwardMostMove;
3654                         ClearHighlights();/*!!could figure this out*/
3655                         flipView = appData.flipView;
3656                         DrawPosition(TRUE, boards[currentMove]);
3657                         DisplayBothClocks();
3658                         snprintf(str, MSG_SIZ, "%s %s %s",
3659                                 gameInfo.white, _("vs."),  gameInfo.black);
3660                         DisplayTitle(str);
3661                         gameMode = IcsIdle;
3662                     } else {
3663                         /* Moves were history of an active game */
3664                         if (gameInfo.resultDetails != NULL) {
3665                             free(gameInfo.resultDetails);
3666                             gameInfo.resultDetails = NULL;
3667                         }
3668                     }
3669                     HistorySet(parseList, backwardMostMove,
3670                                forwardMostMove, currentMove-1);
3671                     DisplayMove(currentMove - 1);
3672                     if (started == STARTED_MOVES) next_out = i;
3673                     started = STARTED_NONE;
3674                     ics_getting_history = H_FALSE;
3675                     break;
3676
3677                   case STARTED_OBSERVE:
3678                     started = STARTED_NONE;
3679                     SendToICS(ics_prefix);
3680                     SendToICS("refresh\n");
3681                     break;
3682
3683                   default:
3684                     break;
3685                 }
3686                 if(bookHit) { // [HGM] book: simulate book reply
3687                     static char bookMove[MSG_SIZ]; // a bit generous?
3688
3689                     programStats.nodes = programStats.depth = programStats.time =
3690                     programStats.score = programStats.got_only_move = 0;
3691                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3692
3693                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3694                     strcat(bookMove, bookHit);
3695                     HandleMachineMove(bookMove, &first);
3696                 }
3697                 continue;
3698             }
3699
3700             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3701                  started == STARTED_HOLDINGS ||
3702                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3703                 /* Accumulate characters in move list or board */
3704                 parse[parse_pos++] = buf[i];
3705             }
3706
3707             /* Start of game messages.  Mostly we detect start of game
3708                when the first board image arrives.  On some versions
3709                of the ICS, though, we need to do a "refresh" after starting
3710                to observe in order to get the current board right away. */
3711             if (looking_at(buf, &i, "Adding game * to observation list")) {
3712                 started = STARTED_OBSERVE;
3713                 continue;
3714             }
3715
3716             /* Handle auto-observe */
3717             if (appData.autoObserve &&
3718                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3719                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3720                 char *player;
3721                 /* Choose the player that was highlighted, if any. */
3722                 if (star_match[0][0] == '\033' ||
3723                     star_match[1][0] != '\033') {
3724                     player = star_match[0];
3725                 } else {
3726                     player = star_match[2];
3727                 }
3728                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3729                         ics_prefix, StripHighlightAndTitle(player));
3730                 SendToICS(str);
3731
3732                 /* Save ratings from notify string */
3733                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3734                 player1Rating = string_to_rating(star_match[1]);
3735                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3736                 player2Rating = string_to_rating(star_match[3]);
3737
3738                 if (appData.debugMode)
3739                   fprintf(debugFP,
3740                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3741                           player1Name, player1Rating,
3742                           player2Name, player2Rating);
3743
3744                 continue;
3745             }
3746
3747             /* Deal with automatic examine mode after a game,
3748                and with IcsObserving -> IcsExamining transition */
3749             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3750                 looking_at(buf, &i, "has made you an examiner of game *")) {
3751
3752                 int gamenum = atoi(star_match[0]);
3753                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3754                     gamenum == ics_gamenum) {
3755                     /* We were already playing or observing this game;
3756                        no need to refetch history */
3757                     gameMode = IcsExamining;
3758                     if (pausing) {
3759                         pauseExamForwardMostMove = forwardMostMove;
3760                     } else if (currentMove < forwardMostMove) {
3761                         ForwardInner(forwardMostMove);
3762                     }
3763                 } else {
3764                     /* I don't think this case really can happen */
3765                     SendToICS(ics_prefix);
3766                     SendToICS("refresh\n");
3767                 }
3768                 continue;
3769             }
3770
3771             /* Error messages */
3772 //          if (ics_user_moved) {
3773             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3774                 if (looking_at(buf, &i, "Illegal move") ||
3775                     looking_at(buf, &i, "Not a legal move") ||
3776                     looking_at(buf, &i, "Your king is in check") ||
3777                     looking_at(buf, &i, "It isn't your turn") ||
3778                     looking_at(buf, &i, "It is not your move")) {
3779                     /* Illegal move */
3780                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3781                         currentMove = forwardMostMove-1;
3782                         DisplayMove(currentMove - 1); /* before DMError */
3783                         DrawPosition(FALSE, boards[currentMove]);
3784                         SwitchClocks(forwardMostMove-1); // [HGM] race
3785                         DisplayBothClocks();
3786                     }
3787                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3788                     ics_user_moved = 0;
3789                     continue;
3790                 }
3791             }
3792
3793             if (looking_at(buf, &i, "still have time") ||
3794                 looking_at(buf, &i, "not out of time") ||
3795                 looking_at(buf, &i, "either player is out of time") ||
3796                 looking_at(buf, &i, "has timeseal; checking")) {
3797                 /* We must have called his flag a little too soon */
3798                 whiteFlag = blackFlag = FALSE;
3799                 continue;
3800             }
3801
3802             if (looking_at(buf, &i, "added * seconds to") ||
3803                 looking_at(buf, &i, "seconds were added to")) {
3804                 /* Update the clocks */
3805                 SendToICS(ics_prefix);
3806                 SendToICS("refresh\n");
3807                 continue;
3808             }
3809
3810             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3811                 ics_clock_paused = TRUE;
3812                 StopClocks();
3813                 continue;
3814             }
3815
3816             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3817                 ics_clock_paused = FALSE;
3818                 StartClocks();
3819                 continue;
3820             }
3821
3822             /* Grab player ratings from the Creating: message.
3823                Note we have to check for the special case when
3824                the ICS inserts things like [white] or [black]. */
3825             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3826                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3827                 /* star_matches:
3828                    0    player 1 name (not necessarily white)
3829                    1    player 1 rating
3830                    2    empty, white, or black (IGNORED)
3831                    3    player 2 name (not necessarily black)
3832                    4    player 2 rating
3833
3834                    The names/ratings are sorted out when the game
3835                    actually starts (below).
3836                 */
3837                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3838                 player1Rating = string_to_rating(star_match[1]);
3839                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3840                 player2Rating = string_to_rating(star_match[4]);
3841
3842                 if (appData.debugMode)
3843                   fprintf(debugFP,
3844                           "Ratings from 'Creating:' %s %d, %s %d\n",
3845                           player1Name, player1Rating,
3846                           player2Name, player2Rating);
3847
3848                 continue;
3849             }
3850
3851             /* Improved generic start/end-of-game messages */
3852             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3853                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3854                 /* If tkind == 0: */
3855                 /* star_match[0] is the game number */
3856                 /*           [1] is the white player's name */
3857                 /*           [2] is the black player's name */
3858                 /* For end-of-game: */
3859                 /*           [3] is the reason for the game end */
3860                 /*           [4] is a PGN end game-token, preceded by " " */
3861                 /* For start-of-game: */
3862                 /*           [3] begins with "Creating" or "Continuing" */
3863                 /*           [4] is " *" or empty (don't care). */
3864                 int gamenum = atoi(star_match[0]);
3865                 char *whitename, *blackname, *why, *endtoken;
3866                 ChessMove endtype = EndOfFile;
3867
3868                 if (tkind == 0) {
3869                   whitename = star_match[1];
3870                   blackname = star_match[2];
3871                   why = star_match[3];
3872                   endtoken = star_match[4];
3873                 } else {
3874                   whitename = star_match[1];
3875                   blackname = star_match[3];
3876                   why = star_match[5];
3877                   endtoken = star_match[6];
3878                 }
3879
3880                 /* Game start messages */
3881                 if (strncmp(why, "Creating ", 9) == 0 ||
3882                     strncmp(why, "Continuing ", 11) == 0) {
3883                     gs_gamenum = gamenum;
3884                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3885                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3886                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3887 #if ZIPPY
3888                     if (appData.zippyPlay) {
3889                         ZippyGameStart(whitename, blackname);
3890                     }
3891 #endif /*ZIPPY*/
3892                     partnerBoardValid = FALSE; // [HGM] bughouse
3893                     continue;
3894                 }
3895
3896                 /* Game end messages */
3897                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3898                     ics_gamenum != gamenum) {
3899                     continue;
3900                 }
3901                 while (endtoken[0] == ' ') endtoken++;
3902                 switch (endtoken[0]) {
3903                   case '*':
3904                   default:
3905                     endtype = GameUnfinished;
3906                     break;
3907                   case '0':
3908                     endtype = BlackWins;
3909                     break;
3910                   case '1':
3911                     if (endtoken[1] == '/')
3912                       endtype = GameIsDrawn;
3913                     else
3914                       endtype = WhiteWins;
3915                     break;
3916                 }
3917                 GameEnds(endtype, why, GE_ICS);
3918 #if ZIPPY
3919                 if (appData.zippyPlay && first.initDone) {
3920                     ZippyGameEnd(endtype, why);
3921                     if (first.pr == NoProc) {
3922                       /* Start the next process early so that we'll
3923                          be ready for the next challenge */
3924                       StartChessProgram(&first);
3925                     }
3926                     /* Send "new" early, in case this command takes
3927                        a long time to finish, so that we'll be ready
3928                        for the next challenge. */
3929                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3930                     Reset(TRUE, TRUE);
3931                 }
3932 #endif /*ZIPPY*/
3933                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3934                 continue;
3935             }
3936
3937             if (looking_at(buf, &i, "Removing game * from observation") ||
3938                 looking_at(buf, &i, "no longer observing game *") ||
3939                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3940                 if (gameMode == IcsObserving &&
3941                     atoi(star_match[0]) == ics_gamenum)
3942                   {
3943                       /* icsEngineAnalyze */
3944                       if (appData.icsEngineAnalyze) {
3945                             ExitAnalyzeMode();
3946                             ModeHighlight();
3947                       }
3948                       StopClocks();
3949                       gameMode = IcsIdle;
3950                       ics_gamenum = -1;
3951                       ics_user_moved = FALSE;
3952                   }
3953                 continue;
3954             }
3955
3956             if (looking_at(buf, &i, "no longer examining game *")) {
3957                 if (gameMode == IcsExamining &&
3958                     atoi(star_match[0]) == ics_gamenum)
3959                   {
3960                       gameMode = IcsIdle;
3961                       ics_gamenum = -1;
3962                       ics_user_moved = FALSE;
3963                   }
3964                 continue;
3965             }
3966
3967             /* Advance leftover_start past any newlines we find,
3968                so only partial lines can get reparsed */
3969             if (looking_at(buf, &i, "\n")) {
3970                 prevColor = curColor;
3971                 if (curColor != ColorNormal) {
3972                     if (oldi > next_out) {
3973                         SendToPlayer(&buf[next_out], oldi - next_out);
3974                         next_out = oldi;
3975                     }
3976                     Colorize(ColorNormal, FALSE);
3977                     curColor = ColorNormal;
3978                 }
3979                 if (started == STARTED_BOARD) {
3980                     started = STARTED_NONE;
3981                     parse[parse_pos] = NULLCHAR;
3982                     ParseBoard12(parse);
3983                     ics_user_moved = 0;
3984
3985                     /* Send premove here */
3986                     if (appData.premove) {
3987                       char str[MSG_SIZ];
3988                       if (currentMove == 0 &&
3989                           gameMode == IcsPlayingWhite &&
3990                           appData.premoveWhite) {
3991                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3992                         if (appData.debugMode)
3993                           fprintf(debugFP, "Sending premove:\n");
3994                         SendToICS(str);
3995                       } else if (currentMove == 1 &&
3996                                  gameMode == IcsPlayingBlack &&
3997                                  appData.premoveBlack) {
3998                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3999                         if (appData.debugMode)
4000                           fprintf(debugFP, "Sending premove:\n");
4001                         SendToICS(str);
4002                       } else if (gotPremove) {
4003                         gotPremove = 0;
4004                         ClearPremoveHighlights();
4005                         if (appData.debugMode)
4006                           fprintf(debugFP, "Sending premove:\n");
4007                           UserMoveEvent(premoveFromX, premoveFromY,
4008                                         premoveToX, premoveToY,
4009                                         premovePromoChar);
4010                       }
4011                     }
4012
4013                     /* Usually suppress following prompt */
4014                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4015                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4016                         if (looking_at(buf, &i, "*% ")) {
4017                             savingComment = FALSE;
4018                             suppressKibitz = 0;
4019                         }
4020                     }
4021                     next_out = i;
4022                 } else if (started == STARTED_HOLDINGS) {
4023                     int gamenum;
4024                     char new_piece[MSG_SIZ];
4025                     started = STARTED_NONE;
4026                     parse[parse_pos] = NULLCHAR;
4027                     if (appData.debugMode)
4028                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4029                                                         parse, currentMove);
4030                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4031                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4032                         if (gameInfo.variant == VariantNormal) {
4033                           /* [HGM] We seem to switch variant during a game!
4034                            * Presumably no holdings were displayed, so we have
4035                            * to move the position two files to the right to
4036                            * create room for them!
4037                            */
4038                           VariantClass newVariant;
4039                           switch(gameInfo.boardWidth) { // base guess on board width
4040                                 case 9:  newVariant = VariantShogi; break;
4041                                 case 10: newVariant = VariantGreat; break;
4042                                 default: newVariant = VariantCrazyhouse; break;
4043                           }
4044                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4045                           /* Get a move list just to see the header, which
4046                              will tell us whether this is really bug or zh */
4047                           if (ics_getting_history == H_FALSE) {
4048                             ics_getting_history = H_REQUESTED;
4049                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4050                             SendToICS(str);
4051                           }
4052                         }
4053                         new_piece[0] = NULLCHAR;
4054                         sscanf(parse, "game %d white [%s black [%s <- %s",
4055                                &gamenum, white_holding, black_holding,
4056                                new_piece);
4057                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4058                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4059                         /* [HGM] copy holdings to board holdings area */
4060                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4061                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4062                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4063 #if ZIPPY
4064                         if (appData.zippyPlay && first.initDone) {
4065                             ZippyHoldings(white_holding, black_holding,
4066                                           new_piece);
4067                         }
4068 #endif /*ZIPPY*/
4069                         if (tinyLayout || smallLayout) {
4070                             char wh[16], bh[16];
4071                             PackHolding(wh, white_holding);
4072                             PackHolding(bh, black_holding);
4073                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4074                                     gameInfo.white, gameInfo.black);
4075                         } else {
4076                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4077                                     gameInfo.white, white_holding, _("vs."),
4078                                     gameInfo.black, black_holding);
4079                         }
4080                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4081                         DrawPosition(FALSE, boards[currentMove]);
4082                         DisplayTitle(str);
4083                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4084                         sscanf(parse, "game %d white [%s black [%s <- %s",
4085                                &gamenum, white_holding, black_holding,
4086                                new_piece);
4087                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4088                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4089                         /* [HGM] copy holdings to partner-board holdings area */
4090                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4091                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4092                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4093                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4094                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4095                       }
4096                     }
4097                     /* Suppress following prompt */
4098                     if (looking_at(buf, &i, "*% ")) {
4099                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4100                         savingComment = FALSE;
4101                         suppressKibitz = 0;
4102                     }
4103                     next_out = i;
4104                 }
4105                 continue;
4106             }
4107
4108             i++;                /* skip unparsed character and loop back */
4109         }
4110
4111         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4112 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4113 //          SendToPlayer(&buf[next_out], i - next_out);
4114             started != STARTED_HOLDINGS && leftover_start > next_out) {
4115             SendToPlayer(&buf[next_out], leftover_start - next_out);
4116             next_out = i;
4117         }
4118
4119         leftover_len = buf_len - leftover_start;
4120         /* if buffer ends with something we couldn't parse,
4121            reparse it after appending the next read */
4122
4123     } else if (count == 0) {
4124         RemoveInputSource(isr);
4125         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4126     } else {
4127         DisplayFatalError(_("Error reading from ICS"), error, 1);
4128     }
4129 }
4130
4131
4132 /* Board style 12 looks like this:
4133
4134    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4135
4136  * The "<12> " is stripped before it gets to this routine.  The two
4137  * trailing 0's (flip state and clock ticking) are later addition, and
4138  * some chess servers may not have them, or may have only the first.
4139  * Additional trailing fields may be added in the future.
4140  */
4141
4142 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4143
4144 #define RELATION_OBSERVING_PLAYED    0
4145 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4146 #define RELATION_PLAYING_MYMOVE      1
4147 #define RELATION_PLAYING_NOTMYMOVE  -1
4148 #define RELATION_EXAMINING           2
4149 #define RELATION_ISOLATED_BOARD     -3
4150 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4151
4152 void
4153 ParseBoard12 (char *string)
4154 {
4155     GameMode newGameMode;
4156     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4157     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4158     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4159     char to_play, board_chars[200];
4160     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4161     char black[32], white[32];
4162     Board board;
4163     int prevMove = currentMove;
4164     int ticking = 2;
4165     ChessMove moveType;
4166     int fromX, fromY, toX, toY;
4167     char promoChar;
4168     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4169     char *bookHit = NULL; // [HGM] book
4170     Boolean weird = FALSE, reqFlag = FALSE;
4171
4172     fromX = fromY = toX = toY = -1;
4173
4174     newGame = FALSE;
4175
4176     if (appData.debugMode)
4177       fprintf(debugFP, _("Parsing board: %s\n"), string);
4178
4179     move_str[0] = NULLCHAR;
4180     elapsed_time[0] = NULLCHAR;
4181     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4182         int  i = 0, j;
4183         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4184             if(string[i] == ' ') { ranks++; files = 0; }
4185             else files++;
4186             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4187             i++;
4188         }
4189         for(j = 0; j <i; j++) board_chars[j] = string[j];
4190         board_chars[i] = '\0';
4191         string += i + 1;
4192     }
4193     n = sscanf(string, PATTERN, &to_play, &double_push,
4194                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4195                &gamenum, white, black, &relation, &basetime, &increment,
4196                &white_stren, &black_stren, &white_time, &black_time,
4197                &moveNum, str, elapsed_time, move_str, &ics_flip,
4198                &ticking);
4199
4200     if (n < 21) {
4201         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4202         DisplayError(str, 0);
4203         return;
4204     }
4205
4206     /* Convert the move number to internal form */
4207     moveNum = (moveNum - 1) * 2;
4208     if (to_play == 'B') moveNum++;
4209     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4210       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4211                         0, 1);
4212       return;
4213     }
4214
4215     switch (relation) {
4216       case RELATION_OBSERVING_PLAYED:
4217       case RELATION_OBSERVING_STATIC:
4218         if (gamenum == -1) {
4219             /* Old ICC buglet */
4220             relation = RELATION_OBSERVING_STATIC;
4221         }
4222         newGameMode = IcsObserving;
4223         break;
4224       case RELATION_PLAYING_MYMOVE:
4225       case RELATION_PLAYING_NOTMYMOVE:
4226         newGameMode =
4227           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4228             IcsPlayingWhite : IcsPlayingBlack;
4229         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4230         break;
4231       case RELATION_EXAMINING:
4232         newGameMode = IcsExamining;
4233         break;
4234       case RELATION_ISOLATED_BOARD:
4235       default:
4236         /* Just display this board.  If user was doing something else,
4237            we will forget about it until the next board comes. */
4238         newGameMode = IcsIdle;
4239         break;
4240       case RELATION_STARTING_POSITION:
4241         newGameMode = gameMode;
4242         break;
4243     }
4244
4245     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4246         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4247          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4248       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4249       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4250       static int lastBgGame = -1;
4251       char *toSqr;
4252       for (k = 0; k < ranks; k++) {
4253         for (j = 0; j < files; j++)
4254           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4255         if(gameInfo.holdingsWidth > 1) {
4256              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4257              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4258         }
4259       }
4260       CopyBoard(partnerBoard, board);
4261       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4262         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4263         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4264       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4265       if(toSqr = strchr(str, '-')) {
4266         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4267         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4268       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4269       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4270       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4271       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4272       if(twoBoards) {
4273           DisplayWhiteClock(white_time*fac, to_play == 'W');
4274           DisplayBlackClock(black_time*fac, to_play != 'W');
4275           activePartner = to_play;
4276           if(gamenum != lastBgGame) {
4277               char buf[MSG_SIZ];
4278               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4279               DisplayTitle(buf);
4280           }
4281           lastBgGame = gamenum;
4282           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4283                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4284       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4285                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4286       DisplayMessage(partnerStatus, "");
4287         partnerBoardValid = TRUE;
4288       return;
4289     }
4290
4291     if(appData.dualBoard && appData.bgObserve) {
4292         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4293             SendToICS(ics_prefix), SendToICS("pobserve\n");
4294         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4295             char buf[MSG_SIZ];
4296             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4297             SendToICS(buf);
4298         }
4299     }
4300
4301     /* Modify behavior for initial board display on move listing
4302        of wild games.
4303        */
4304     switch (ics_getting_history) {
4305       case H_FALSE:
4306       case H_REQUESTED:
4307         break;
4308       case H_GOT_REQ_HEADER:
4309       case H_GOT_UNREQ_HEADER:
4310         /* This is the initial position of the current game */
4311         gamenum = ics_gamenum;
4312         moveNum = 0;            /* old ICS bug workaround */
4313         if (to_play == 'B') {
4314           startedFromSetupPosition = TRUE;
4315           blackPlaysFirst = TRUE;
4316           moveNum = 1;
4317           if (forwardMostMove == 0) forwardMostMove = 1;
4318           if (backwardMostMove == 0) backwardMostMove = 1;
4319           if (currentMove == 0) currentMove = 1;
4320         }
4321         newGameMode = gameMode;
4322         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4323         break;
4324       case H_GOT_UNWANTED_HEADER:
4325         /* This is an initial board that we don't want */
4326         return;
4327       case H_GETTING_MOVES:
4328         /* Should not happen */
4329         DisplayError(_("Error gathering move list: extra board"), 0);
4330         ics_getting_history = H_FALSE;
4331         return;
4332     }
4333
4334    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4335                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4336                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4337      /* [HGM] We seem to have switched variant unexpectedly
4338       * Try to guess new variant from board size
4339       */
4340           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4341           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4342           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4343           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4344           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4345           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4346           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4347           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4348           /* Get a move list just to see the header, which
4349              will tell us whether this is really bug or zh */
4350           if (ics_getting_history == H_FALSE) {
4351             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4352             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4353             SendToICS(str);
4354           }
4355     }
4356
4357     /* Take action if this is the first board of a new game, or of a
4358        different game than is currently being displayed.  */
4359     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4360         relation == RELATION_ISOLATED_BOARD) {
4361
4362         /* Forget the old game and get the history (if any) of the new one */
4363         if (gameMode != BeginningOfGame) {
4364           Reset(TRUE, TRUE);
4365         }
4366         newGame = TRUE;
4367         if (appData.autoRaiseBoard) BoardToTop();
4368         prevMove = -3;
4369         if (gamenum == -1) {
4370             newGameMode = IcsIdle;
4371         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4372                    appData.getMoveList && !reqFlag) {
4373             /* Need to get game history */
4374             ics_getting_history = H_REQUESTED;
4375             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4376             SendToICS(str);
4377         }
4378
4379         /* Initially flip the board to have black on the bottom if playing
4380            black or if the ICS flip flag is set, but let the user change
4381            it with the Flip View button. */
4382         flipView = appData.autoFlipView ?
4383           (newGameMode == IcsPlayingBlack) || ics_flip :
4384           appData.flipView;
4385
4386         /* Done with values from previous mode; copy in new ones */
4387         gameMode = newGameMode;
4388         ModeHighlight();
4389         ics_gamenum = gamenum;
4390         if (gamenum == gs_gamenum) {
4391             int klen = strlen(gs_kind);
4392             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4393             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4394             gameInfo.event = StrSave(str);
4395         } else {
4396             gameInfo.event = StrSave("ICS game");
4397         }
4398         gameInfo.site = StrSave(appData.icsHost);
4399         gameInfo.date = PGNDate();
4400         gameInfo.round = StrSave("-");
4401         gameInfo.white = StrSave(white);
4402         gameInfo.black = StrSave(black);
4403         timeControl = basetime * 60 * 1000;
4404         timeControl_2 = 0;
4405         timeIncrement = increment * 1000;
4406         movesPerSession = 0;
4407         gameInfo.timeControl = TimeControlTagValue();
4408         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4409   if (appData.debugMode) {
4410     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4411     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4412     setbuf(debugFP, NULL);
4413   }
4414
4415         gameInfo.outOfBook = NULL;
4416
4417         /* Do we have the ratings? */
4418         if (strcmp(player1Name, white) == 0 &&
4419             strcmp(player2Name, black) == 0) {
4420             if (appData.debugMode)
4421               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4422                       player1Rating, player2Rating);
4423             gameInfo.whiteRating = player1Rating;
4424             gameInfo.blackRating = player2Rating;
4425         } else if (strcmp(player2Name, white) == 0 &&
4426                    strcmp(player1Name, black) == 0) {
4427             if (appData.debugMode)
4428               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4429                       player2Rating, player1Rating);
4430             gameInfo.whiteRating = player2Rating;
4431             gameInfo.blackRating = player1Rating;
4432         }
4433         player1Name[0] = player2Name[0] = NULLCHAR;
4434
4435         /* Silence shouts if requested */
4436         if (appData.quietPlay &&
4437             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4438             SendToICS(ics_prefix);
4439             SendToICS("set shout 0\n");
4440         }
4441     }
4442
4443     /* Deal with midgame name changes */
4444     if (!newGame) {
4445         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4446             if (gameInfo.white) free(gameInfo.white);
4447             gameInfo.white = StrSave(white);
4448         }
4449         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4450             if (gameInfo.black) free(gameInfo.black);
4451             gameInfo.black = StrSave(black);
4452         }
4453     }
4454
4455     /* Throw away game result if anything actually changes in examine mode */
4456     if (gameMode == IcsExamining && !newGame) {
4457         gameInfo.result = GameUnfinished;
4458         if (gameInfo.resultDetails != NULL) {
4459             free(gameInfo.resultDetails);
4460             gameInfo.resultDetails = NULL;
4461         }
4462     }
4463
4464     /* In pausing && IcsExamining mode, we ignore boards coming
4465        in if they are in a different variation than we are. */
4466     if (pauseExamInvalid) return;
4467     if (pausing && gameMode == IcsExamining) {
4468         if (moveNum <= pauseExamForwardMostMove) {
4469             pauseExamInvalid = TRUE;
4470             forwardMostMove = pauseExamForwardMostMove;
4471             return;
4472         }
4473     }
4474
4475   if (appData.debugMode) {
4476     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4477   }
4478     /* Parse the board */
4479     for (k = 0; k < ranks; k++) {
4480       for (j = 0; j < files; j++)
4481         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4482       if(gameInfo.holdingsWidth > 1) {
4483            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4484            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4485       }
4486     }
4487     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4488       board[5][BOARD_RGHT+1] = WhiteAngel;
4489       board[6][BOARD_RGHT+1] = WhiteMarshall;
4490       board[1][0] = BlackMarshall;
4491       board[2][0] = BlackAngel;
4492       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4493     }
4494     CopyBoard(boards[moveNum], board);
4495     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4496     if (moveNum == 0) {
4497         startedFromSetupPosition =
4498           !CompareBoards(board, initialPosition);
4499         if(startedFromSetupPosition)
4500             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4501     }
4502
4503     /* [HGM] Set castling rights. Take the outermost Rooks,
4504        to make it also work for FRC opening positions. Note that board12
4505        is really defective for later FRC positions, as it has no way to
4506        indicate which Rook can castle if they are on the same side of King.
4507        For the initial position we grant rights to the outermost Rooks,
4508        and remember thos rights, and we then copy them on positions
4509        later in an FRC game. This means WB might not recognize castlings with
4510        Rooks that have moved back to their original position as illegal,
4511        but in ICS mode that is not its job anyway.
4512     */
4513     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4514     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4515
4516         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4517             if(board[0][i] == WhiteRook) j = i;
4518         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4519         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4520             if(board[0][i] == WhiteRook) j = i;
4521         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4522         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4523             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4524         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4525         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4526             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4527         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4528
4529         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4530         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4531         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4532             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4533         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4534             if(board[BOARD_HEIGHT-1][k] == bKing)
4535                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4536         if(gameInfo.variant == VariantTwoKings) {
4537             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4538             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4539             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4540         }
4541     } else { int r;
4542         r = boards[moveNum][CASTLING][0] = initialRights[0];
4543         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4544         r = boards[moveNum][CASTLING][1] = initialRights[1];
4545         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4546         r = boards[moveNum][CASTLING][3] = initialRights[3];
4547         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4548         r = boards[moveNum][CASTLING][4] = initialRights[4];
4549         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4550         /* wildcastle kludge: always assume King has rights */
4551         r = boards[moveNum][CASTLING][2] = initialRights[2];
4552         r = boards[moveNum][CASTLING][5] = initialRights[5];
4553     }
4554     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4555     boards[moveNum][EP_STATUS] = EP_NONE;
4556     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4557     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4558     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4559
4560
4561     if (ics_getting_history == H_GOT_REQ_HEADER ||
4562         ics_getting_history == H_GOT_UNREQ_HEADER) {
4563         /* This was an initial position from a move list, not
4564            the current position */
4565         return;
4566     }
4567
4568     /* Update currentMove and known move number limits */
4569     newMove = newGame || moveNum > forwardMostMove;
4570
4571     if (newGame) {
4572         forwardMostMove = backwardMostMove = currentMove = moveNum;
4573         if (gameMode == IcsExamining && moveNum == 0) {
4574           /* Workaround for ICS limitation: we are not told the wild
4575              type when starting to examine a game.  But if we ask for
4576              the move list, the move list header will tell us */
4577             ics_getting_history = H_REQUESTED;
4578             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4579             SendToICS(str);
4580         }
4581     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4582                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4583 #if ZIPPY
4584         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4585         /* [HGM] applied this also to an engine that is silently watching        */
4586         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4587             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4588             gameInfo.variant == currentlyInitializedVariant) {
4589           takeback = forwardMostMove - moveNum;
4590           for (i = 0; i < takeback; i++) {
4591             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4592             SendToProgram("undo\n", &first);
4593           }
4594         }
4595 #endif
4596
4597         forwardMostMove = moveNum;
4598         if (!pausing || currentMove > forwardMostMove)
4599           currentMove = forwardMostMove;
4600     } else {
4601         /* New part of history that is not contiguous with old part */
4602         if (pausing && gameMode == IcsExamining) {
4603             pauseExamInvalid = TRUE;
4604             forwardMostMove = pauseExamForwardMostMove;
4605             return;
4606         }
4607         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4608 #if ZIPPY
4609             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4610                 // [HGM] when we will receive the move list we now request, it will be
4611                 // fed to the engine from the first move on. So if the engine is not
4612                 // in the initial position now, bring it there.
4613                 InitChessProgram(&first, 0);
4614             }
4615 #endif
4616             ics_getting_history = H_REQUESTED;
4617             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4618             SendToICS(str);
4619         }
4620         forwardMostMove = backwardMostMove = currentMove = moveNum;
4621     }
4622
4623     /* Update the clocks */
4624     if (strchr(elapsed_time, '.')) {
4625       /* Time is in ms */
4626       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4627       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4628     } else {
4629       /* Time is in seconds */
4630       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4631       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4632     }
4633
4634
4635 #if ZIPPY
4636     if (appData.zippyPlay && newGame &&
4637         gameMode != IcsObserving && gameMode != IcsIdle &&
4638         gameMode != IcsExamining)
4639       ZippyFirstBoard(moveNum, basetime, increment);
4640 #endif
4641
4642     /* Put the move on the move list, first converting
4643        to canonical algebraic form. */
4644     if (moveNum > 0) {
4645   if (appData.debugMode) {
4646     if (appData.debugMode) { int f = forwardMostMove;
4647         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4648                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4649                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4650     }
4651     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4652     fprintf(debugFP, "moveNum = %d\n", moveNum);
4653     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4654     setbuf(debugFP, NULL);
4655   }
4656         if (moveNum <= backwardMostMove) {
4657             /* We don't know what the board looked like before
4658                this move.  Punt. */
4659           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4660             strcat(parseList[moveNum - 1], " ");
4661             strcat(parseList[moveNum - 1], elapsed_time);
4662             moveList[moveNum - 1][0] = NULLCHAR;
4663         } else if (strcmp(move_str, "none") == 0) {
4664             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4665             /* Again, we don't know what the board looked like;
4666                this is really the start of the game. */
4667             parseList[moveNum - 1][0] = NULLCHAR;
4668             moveList[moveNum - 1][0] = NULLCHAR;
4669             backwardMostMove = moveNum;
4670             startedFromSetupPosition = TRUE;
4671             fromX = fromY = toX = toY = -1;
4672         } else {
4673           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4674           //                 So we parse the long-algebraic move string in stead of the SAN move
4675           int valid; char buf[MSG_SIZ], *prom;
4676
4677           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4678                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4679           // str looks something like "Q/a1-a2"; kill the slash
4680           if(str[1] == '/')
4681             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4682           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4683           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4684                 strcat(buf, prom); // long move lacks promo specification!
4685           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4686                 if(appData.debugMode)
4687                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4688                 safeStrCpy(move_str, buf, MSG_SIZ);
4689           }
4690           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4691                                 &fromX, &fromY, &toX, &toY, &promoChar)
4692                || ParseOneMove(buf, moveNum - 1, &moveType,
4693                                 &fromX, &fromY, &toX, &toY, &promoChar);
4694           // end of long SAN patch
4695           if (valid) {
4696             (void) CoordsToAlgebraic(boards[moveNum - 1],
4697                                      PosFlags(moveNum - 1),
4698                                      fromY, fromX, toY, toX, promoChar,
4699                                      parseList[moveNum-1]);
4700             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4701               case MT_NONE:
4702               case MT_STALEMATE:
4703               default:
4704                 break;
4705               case MT_CHECK:
4706                 if(gameInfo.variant != VariantShogi)
4707                     strcat(parseList[moveNum - 1], "+");
4708                 break;
4709               case MT_CHECKMATE:
4710               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4711                 strcat(parseList[moveNum - 1], "#");
4712                 break;
4713             }
4714             strcat(parseList[moveNum - 1], " ");
4715             strcat(parseList[moveNum - 1], elapsed_time);
4716             /* currentMoveString is set as a side-effect of ParseOneMove */
4717             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4718             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4719             strcat(moveList[moveNum - 1], "\n");
4720
4721             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4722                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4723               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4724                 ChessSquare old, new = boards[moveNum][k][j];
4725                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4726                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4727                   if(old == new) continue;
4728                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4729                   else if(new == WhiteWazir || new == BlackWazir) {
4730                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4731                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4732                       else boards[moveNum][k][j] = old; // preserve type of Gold
4733                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4734                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4735               }
4736           } else {
4737             /* Move from ICS was illegal!?  Punt. */
4738             if (appData.debugMode) {
4739               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4740               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4741             }
4742             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4743             strcat(parseList[moveNum - 1], " ");
4744             strcat(parseList[moveNum - 1], elapsed_time);
4745             moveList[moveNum - 1][0] = NULLCHAR;
4746             fromX = fromY = toX = toY = -1;
4747           }
4748         }
4749   if (appData.debugMode) {
4750     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4751     setbuf(debugFP, NULL);
4752   }
4753
4754 #if ZIPPY
4755         /* Send move to chess program (BEFORE animating it). */
4756         if (appData.zippyPlay && !newGame && newMove &&
4757            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4758
4759             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4760                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4761                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4762                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4763                             move_str);
4764                     DisplayError(str, 0);
4765                 } else {
4766                     if (first.sendTime) {
4767                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4768                     }
4769                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4770                     if (firstMove && !bookHit) {
4771                         firstMove = FALSE;
4772                         if (first.useColors) {
4773                           SendToProgram(gameMode == IcsPlayingWhite ?
4774                                         "white\ngo\n" :
4775                                         "black\ngo\n", &first);
4776                         } else {
4777                           SendToProgram("go\n", &first);
4778                         }
4779                         first.maybeThinking = TRUE;
4780                     }
4781                 }
4782             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4783               if (moveList[moveNum - 1][0] == NULLCHAR) {
4784                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4785                 DisplayError(str, 0);
4786               } else {
4787                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4788                 SendMoveToProgram(moveNum - 1, &first);
4789               }
4790             }
4791         }
4792 #endif
4793     }
4794
4795     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4796         /* If move comes from a remote source, animate it.  If it
4797            isn't remote, it will have already been animated. */
4798         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4799             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4800         }
4801         if (!pausing && appData.highlightLastMove) {
4802             SetHighlights(fromX, fromY, toX, toY);
4803         }
4804     }
4805
4806     /* Start the clocks */
4807     whiteFlag = blackFlag = FALSE;
4808     appData.clockMode = !(basetime == 0 && increment == 0);
4809     if (ticking == 0) {
4810       ics_clock_paused = TRUE;
4811       StopClocks();
4812     } else if (ticking == 1) {
4813       ics_clock_paused = FALSE;
4814     }
4815     if (gameMode == IcsIdle ||
4816         relation == RELATION_OBSERVING_STATIC ||
4817         relation == RELATION_EXAMINING ||
4818         ics_clock_paused)
4819       DisplayBothClocks();
4820     else
4821       StartClocks();
4822
4823     /* Display opponents and material strengths */
4824     if (gameInfo.variant != VariantBughouse &&
4825         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4826         if (tinyLayout || smallLayout) {
4827             if(gameInfo.variant == VariantNormal)
4828               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4829                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4830                     basetime, increment);
4831             else
4832               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4833                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4834                     basetime, increment, (int) gameInfo.variant);
4835         } else {
4836             if(gameInfo.variant == VariantNormal)
4837               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4838                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4839                     basetime, increment);
4840             else
4841               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4842                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4843                     basetime, increment, VariantName(gameInfo.variant));
4844         }
4845         DisplayTitle(str);
4846   if (appData.debugMode) {
4847     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4848   }
4849     }
4850
4851
4852     /* Display the board */
4853     if (!pausing && !appData.noGUI) {
4854
4855       if (appData.premove)
4856           if (!gotPremove ||
4857              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4858              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4859               ClearPremoveHighlights();
4860
4861       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4862         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4863       DrawPosition(j, boards[currentMove]);
4864
4865       DisplayMove(moveNum - 1);
4866       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4867             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4868               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4869         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4870       }
4871     }
4872
4873     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4874 #if ZIPPY
4875     if(bookHit) { // [HGM] book: simulate book reply
4876         static char bookMove[MSG_SIZ]; // a bit generous?
4877
4878         programStats.nodes = programStats.depth = programStats.time =
4879         programStats.score = programStats.got_only_move = 0;
4880         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4881
4882         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4883         strcat(bookMove, bookHit);
4884         HandleMachineMove(bookMove, &first);
4885     }
4886 #endif
4887 }
4888
4889 void
4890 GetMoveListEvent ()
4891 {
4892     char buf[MSG_SIZ];
4893     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4894         ics_getting_history = H_REQUESTED;
4895         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4896         SendToICS(buf);
4897     }
4898 }
4899
4900 void
4901 AnalysisPeriodicEvent (int force)
4902 {
4903     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4904          && !force) || !appData.periodicUpdates)
4905       return;
4906
4907     /* Send . command to Crafty to collect stats */
4908     SendToProgram(".\n", &first);
4909     if(second.analyzing) SendToProgram(".\n", &second);
4910
4911     /* Don't send another until we get a response (this makes
4912        us stop sending to old Crafty's which don't understand
4913        the "." command (sending illegal cmds resets node count & time,
4914        which looks bad)) */
4915     programStats.ok_to_send = 0;
4916 }
4917
4918 void
4919 ics_update_width (int new_width)
4920 {
4921         ics_printf("set width %d\n", new_width);
4922 }
4923
4924 void
4925 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4926 {
4927     char buf[MSG_SIZ];
4928
4929     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4930         // null move in variant where engine does not understand it (for analysis purposes)
4931         SendBoard(cps, moveNum + 1); // send position after move in stead.
4932         return;
4933     }
4934     if (cps->useUsermove) {
4935       SendToProgram("usermove ", cps);
4936     }
4937     if (cps->useSAN) {
4938       char *space;
4939       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4940         int len = space - parseList[moveNum];
4941         memcpy(buf, parseList[moveNum], len);
4942         buf[len++] = '\n';
4943         buf[len] = NULLCHAR;
4944       } else {
4945         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4946       }
4947       SendToProgram(buf, cps);
4948     } else {
4949       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4950         AlphaRank(moveList[moveNum], 4);
4951         SendToProgram(moveList[moveNum], cps);
4952         AlphaRank(moveList[moveNum], 4); // and back
4953       } else
4954       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4955        * the engine. It would be nice to have a better way to identify castle
4956        * moves here. */
4957       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4958                                                                          && cps->useOOCastle) {
4959         int fromX = moveList[moveNum][0] - AAA;
4960         int fromY = moveList[moveNum][1] - ONE;
4961         int toX = moveList[moveNum][2] - AAA;
4962         int toY = moveList[moveNum][3] - ONE;
4963         if((boards[moveNum][fromY][fromX] == WhiteKing
4964             && boards[moveNum][toY][toX] == WhiteRook)
4965            || (boards[moveNum][fromY][fromX] == BlackKing
4966                && boards[moveNum][toY][toX] == BlackRook)) {
4967           if(toX > fromX) SendToProgram("O-O\n", cps);
4968           else SendToProgram("O-O-O\n", cps);
4969         }
4970         else SendToProgram(moveList[moveNum], cps);
4971       } else
4972       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4973         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4974           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4975           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4976                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4977         } else
4978           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4979                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4980         SendToProgram(buf, cps);
4981       }
4982       else SendToProgram(moveList[moveNum], cps);
4983       /* End of additions by Tord */
4984     }
4985
4986     /* [HGM] setting up the opening has brought engine in force mode! */
4987     /*       Send 'go' if we are in a mode where machine should play. */
4988     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4989         (gameMode == TwoMachinesPlay   ||
4990 #if ZIPPY
4991          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4992 #endif
4993          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4994         SendToProgram("go\n", cps);
4995   if (appData.debugMode) {
4996     fprintf(debugFP, "(extra)\n");
4997   }
4998     }
4999     setboardSpoiledMachineBlack = 0;
5000 }
5001
5002 void
5003 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5004 {
5005     char user_move[MSG_SIZ];
5006     char suffix[4];
5007
5008     if(gameInfo.variant == VariantSChess && promoChar) {
5009         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5010         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5011     } else suffix[0] = NULLCHAR;
5012
5013     switch (moveType) {
5014       default:
5015         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5016                 (int)moveType, fromX, fromY, toX, toY);
5017         DisplayError(user_move + strlen("say "), 0);
5018         break;
5019       case WhiteKingSideCastle:
5020       case BlackKingSideCastle:
5021       case WhiteQueenSideCastleWild:
5022       case BlackQueenSideCastleWild:
5023       /* PUSH Fabien */
5024       case WhiteHSideCastleFR:
5025       case BlackHSideCastleFR:
5026       /* POP Fabien */
5027         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5028         break;
5029       case WhiteQueenSideCastle:
5030       case BlackQueenSideCastle:
5031       case WhiteKingSideCastleWild:
5032       case BlackKingSideCastleWild:
5033       /* PUSH Fabien */
5034       case WhiteASideCastleFR:
5035       case BlackASideCastleFR:
5036       /* POP Fabien */
5037         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5038         break;
5039       case WhiteNonPromotion:
5040       case BlackNonPromotion:
5041         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5042         break;
5043       case WhitePromotion:
5044       case BlackPromotion:
5045         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5046           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5047                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5048                 PieceToChar(WhiteFerz));
5049         else if(gameInfo.variant == VariantGreat)
5050           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5051                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5052                 PieceToChar(WhiteMan));
5053         else
5054           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5055                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5056                 promoChar);
5057         break;
5058       case WhiteDrop:
5059       case BlackDrop:
5060       drop:
5061         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5062                  ToUpper(PieceToChar((ChessSquare) fromX)),
5063                  AAA + toX, ONE + toY);
5064         break;
5065       case IllegalMove:  /* could be a variant we don't quite understand */
5066         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5067       case NormalMove:
5068       case WhiteCapturesEnPassant:
5069       case BlackCapturesEnPassant:
5070         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5071                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5072         break;
5073     }
5074     SendToICS(user_move);
5075     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5076         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5077 }
5078
5079 void
5080 UploadGameEvent ()
5081 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5082     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5083     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5084     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5085       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5086       return;
5087     }
5088     if(gameMode != IcsExamining) { // is this ever not the case?
5089         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5090
5091         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5092           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5093         } else { // on FICS we must first go to general examine mode
5094           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5095         }
5096         if(gameInfo.variant != VariantNormal) {
5097             // try figure out wild number, as xboard names are not always valid on ICS
5098             for(i=1; i<=36; i++) {
5099               snprintf(buf, MSG_SIZ, "wild/%d", i);
5100                 if(StringToVariant(buf) == gameInfo.variant) break;
5101             }
5102             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5103             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5104             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5105         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5106         SendToICS(ics_prefix);
5107         SendToICS(buf);
5108         if(startedFromSetupPosition || backwardMostMove != 0) {
5109           fen = PositionToFEN(backwardMostMove, NULL);
5110           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5111             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5112             SendToICS(buf);
5113           } else { // FICS: everything has to set by separate bsetup commands
5114             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5115             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5116             SendToICS(buf);
5117             if(!WhiteOnMove(backwardMostMove)) {
5118                 SendToICS("bsetup tomove black\n");
5119             }
5120             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5121             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5122             SendToICS(buf);
5123             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5124             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5125             SendToICS(buf);
5126             i = boards[backwardMostMove][EP_STATUS];
5127             if(i >= 0) { // set e.p.
5128               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5129                 SendToICS(buf);
5130             }
5131             bsetup++;
5132           }
5133         }
5134       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5135             SendToICS("bsetup done\n"); // switch to normal examining.
5136     }
5137     for(i = backwardMostMove; i<last; i++) {
5138         char buf[20];
5139         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5140         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5141             int len = strlen(moveList[i]);
5142             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5143             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5144         }
5145         SendToICS(buf);
5146     }
5147     SendToICS(ics_prefix);
5148     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5149 }
5150
5151 void
5152 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5153 {
5154     if (rf == DROP_RANK) {
5155       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5156       sprintf(move, "%c@%c%c\n",
5157                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5158     } else {
5159         if (promoChar == 'x' || promoChar == NULLCHAR) {
5160           sprintf(move, "%c%c%c%c\n",
5161                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5162         } else {
5163             sprintf(move, "%c%c%c%c%c\n",
5164                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5165         }
5166     }
5167 }
5168
5169 void
5170 ProcessICSInitScript (FILE *f)
5171 {
5172     char buf[MSG_SIZ];
5173
5174     while (fgets(buf, MSG_SIZ, f)) {
5175         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5176     }
5177
5178     fclose(f);
5179 }
5180
5181
5182 static int lastX, lastY, selectFlag, dragging;
5183
5184 void
5185 Sweep (int step)
5186 {
5187     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5188     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5189     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5190     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5191     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5192     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5193     do {
5194         promoSweep -= step;
5195         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5196         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5197         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5198         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5199         if(!step) step = -1;
5200     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5201             appData.testLegality && (promoSweep == king ||
5202             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5203     if(toX >= 0) {
5204         int victim = boards[currentMove][toY][toX];
5205         boards[currentMove][toY][toX] = promoSweep;
5206         DrawPosition(FALSE, boards[currentMove]);
5207         boards[currentMove][toY][toX] = victim;
5208     } else
5209     ChangeDragPiece(promoSweep);
5210 }
5211
5212 int
5213 PromoScroll (int x, int y)
5214 {
5215   int step = 0;
5216
5217   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5218   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5219   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5220   if(!step) return FALSE;
5221   lastX = x; lastY = y;
5222   if((promoSweep < BlackPawn) == flipView) step = -step;
5223   if(step > 0) selectFlag = 1;
5224   if(!selectFlag) Sweep(step);
5225   return FALSE;
5226 }
5227
5228 void
5229 NextPiece (int step)
5230 {
5231     ChessSquare piece = boards[currentMove][toY][toX];
5232     do {
5233         pieceSweep -= step;
5234         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5235         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5236         if(!step) step = -1;
5237     } while(PieceToChar(pieceSweep) == '.');
5238     boards[currentMove][toY][toX] = pieceSweep;
5239     DrawPosition(FALSE, boards[currentMove]);
5240     boards[currentMove][toY][toX] = piece;
5241 }
5242 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5243 void
5244 AlphaRank (char *move, int n)
5245 {
5246 //    char *p = move, c; int x, y;
5247
5248     if (appData.debugMode) {
5249         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5250     }
5251
5252     if(move[1]=='*' &&
5253        move[2]>='0' && move[2]<='9' &&
5254        move[3]>='a' && move[3]<='x'    ) {
5255         move[1] = '@';
5256         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5257         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5258     } else
5259     if(move[0]>='0' && move[0]<='9' &&
5260        move[1]>='a' && move[1]<='x' &&
5261        move[2]>='0' && move[2]<='9' &&
5262        move[3]>='a' && move[3]<='x'    ) {
5263         /* input move, Shogi -> normal */
5264         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5265         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5266         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5267         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5268     } else
5269     if(move[1]=='@' &&
5270        move[3]>='0' && move[3]<='9' &&
5271        move[2]>='a' && move[2]<='x'    ) {
5272         move[1] = '*';
5273         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5274         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5275     } else
5276     if(
5277        move[0]>='a' && move[0]<='x' &&
5278        move[3]>='0' && move[3]<='9' &&
5279        move[2]>='a' && move[2]<='x'    ) {
5280          /* output move, normal -> Shogi */
5281         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5282         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5283         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5284         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5285         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5286     }
5287     if (appData.debugMode) {
5288         fprintf(debugFP, "   out = '%s'\n", move);
5289     }
5290 }
5291
5292 char yy_textstr[8000];
5293
5294 /* Parser for moves from gnuchess, ICS, or user typein box */
5295 Boolean
5296 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5297 {
5298     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5299
5300     switch (*moveType) {
5301       case WhitePromotion:
5302       case BlackPromotion:
5303       case WhiteNonPromotion:
5304       case BlackNonPromotion:
5305       case NormalMove:
5306       case WhiteCapturesEnPassant:
5307       case BlackCapturesEnPassant:
5308       case WhiteKingSideCastle:
5309       case WhiteQueenSideCastle:
5310       case BlackKingSideCastle:
5311       case BlackQueenSideCastle:
5312       case WhiteKingSideCastleWild:
5313       case WhiteQueenSideCastleWild:
5314       case BlackKingSideCastleWild:
5315       case BlackQueenSideCastleWild:
5316       /* Code added by Tord: */
5317       case WhiteHSideCastleFR:
5318       case WhiteASideCastleFR:
5319       case BlackHSideCastleFR:
5320       case BlackASideCastleFR:
5321       /* End of code added by Tord */
5322       case IllegalMove:         /* bug or odd chess variant */
5323         *fromX = currentMoveString[0] - AAA;
5324         *fromY = currentMoveString[1] - ONE;
5325         *toX = currentMoveString[2] - AAA;
5326         *toY = currentMoveString[3] - ONE;
5327         *promoChar = currentMoveString[4];
5328         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5329             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5330     if (appData.debugMode) {
5331         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5332     }
5333             *fromX = *fromY = *toX = *toY = 0;
5334             return FALSE;
5335         }
5336         if (appData.testLegality) {
5337           return (*moveType != IllegalMove);
5338         } else {
5339           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5340                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5341         }
5342
5343       case WhiteDrop:
5344       case BlackDrop:
5345         *fromX = *moveType == WhiteDrop ?
5346           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5347           (int) CharToPiece(ToLower(currentMoveString[0]));
5348         *fromY = DROP_RANK;
5349         *toX = currentMoveString[2] - AAA;
5350         *toY = currentMoveString[3] - ONE;
5351         *promoChar = NULLCHAR;
5352         return TRUE;
5353
5354       case AmbiguousMove:
5355       case ImpossibleMove:
5356       case EndOfFile:
5357       case ElapsedTime:
5358       case Comment:
5359       case PGNTag:
5360       case NAG:
5361       case WhiteWins:
5362       case BlackWins:
5363       case GameIsDrawn:
5364       default:
5365     if (appData.debugMode) {
5366         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5367     }
5368         /* bug? */
5369         *fromX = *fromY = *toX = *toY = 0;
5370         *promoChar = NULLCHAR;
5371         return FALSE;
5372     }
5373 }
5374
5375 Boolean pushed = FALSE;
5376 char *lastParseAttempt;
5377
5378 void
5379 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5380 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5381   int fromX, fromY, toX, toY; char promoChar;
5382   ChessMove moveType;
5383   Boolean valid;
5384   int nr = 0;
5385
5386   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5387     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5388     pushed = TRUE;
5389   }
5390   endPV = forwardMostMove;
5391   do {
5392     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5393     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5394     lastParseAttempt = pv;
5395     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5396     if(!valid && nr == 0 &&
5397        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5398         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5399         // Hande case where played move is different from leading PV move
5400         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5401         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5402         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5403         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5404           endPV += 2; // if position different, keep this
5405           moveList[endPV-1][0] = fromX + AAA;
5406           moveList[endPV-1][1] = fromY + ONE;
5407           moveList[endPV-1][2] = toX + AAA;
5408           moveList[endPV-1][3] = toY + ONE;
5409           parseList[endPV-1][0] = NULLCHAR;
5410           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5411         }
5412       }
5413     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5414     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5415     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5416     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5417         valid++; // allow comments in PV
5418         continue;
5419     }
5420     nr++;
5421     if(endPV+1 > framePtr) break; // no space, truncate
5422     if(!valid) break;
5423     endPV++;
5424     CopyBoard(boards[endPV], boards[endPV-1]);
5425     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5426     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5427     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5428     CoordsToAlgebraic(boards[endPV - 1],
5429                              PosFlags(endPV - 1),
5430                              fromY, fromX, toY, toX, promoChar,
5431                              parseList[endPV - 1]);
5432   } while(valid);
5433   if(atEnd == 2) return; // used hidden, for PV conversion
5434   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5435   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5436   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5437                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5438   DrawPosition(TRUE, boards[currentMove]);
5439 }
5440
5441 int
5442 MultiPV (ChessProgramState *cps)
5443 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5444         int i;
5445         for(i=0; i<cps->nrOptions; i++)
5446             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5447                 return i;
5448         return -1;
5449 }
5450
5451 Boolean
5452 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5453 {
5454         int startPV, multi, lineStart, origIndex = index;
5455         char *p, buf2[MSG_SIZ];
5456         ChessProgramState *cps = (pane ? &second : &first);
5457
5458         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5459         lastX = x; lastY = y;
5460         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5461         lineStart = startPV = index;
5462         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5463         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5464         index = startPV;
5465         do{ while(buf[index] && buf[index] != '\n') index++;
5466         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5467         buf[index] = 0;
5468         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5469                 int n = cps->option[multi].value;
5470                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5471                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5472                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5473                 cps->option[multi].value = n;
5474                 *start = *end = 0;
5475                 return FALSE;
5476         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5477                 ExcludeClick(origIndex - lineStart);
5478                 return FALSE;
5479         }
5480         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5481         *start = startPV; *end = index-1;
5482         return TRUE;
5483 }
5484
5485 char *
5486 PvToSAN (char *pv)
5487 {
5488         static char buf[10*MSG_SIZ];
5489         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5490         *buf = NULLCHAR;
5491         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5492         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5493         for(i = forwardMostMove; i<endPV; i++){
5494             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5495             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5496             k += strlen(buf+k);
5497         }
5498         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5499         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5500         endPV = savedEnd;
5501         return buf;
5502 }
5503
5504 Boolean
5505 LoadPV (int x, int y)
5506 { // called on right mouse click to load PV
5507   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5508   lastX = x; lastY = y;
5509   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5510   return TRUE;
5511 }
5512
5513 void
5514 UnLoadPV ()
5515 {
5516   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5517   if(endPV < 0) return;
5518   if(appData.autoCopyPV) CopyFENToClipboard();
5519   endPV = -1;
5520   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5521         Boolean saveAnimate = appData.animate;
5522         if(pushed) {
5523             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5524                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5525             } else storedGames--; // abandon shelved tail of original game
5526         }
5527         pushed = FALSE;
5528         forwardMostMove = currentMove;
5529         currentMove = oldFMM;
5530         appData.animate = FALSE;
5531         ToNrEvent(forwardMostMove);
5532         appData.animate = saveAnimate;
5533   }
5534   currentMove = forwardMostMove;
5535   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5536   ClearPremoveHighlights();
5537   DrawPosition(TRUE, boards[currentMove]);
5538 }
5539
5540 void
5541 MovePV (int x, int y, int h)
5542 { // step through PV based on mouse coordinates (called on mouse move)
5543   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5544
5545   // we must somehow check if right button is still down (might be released off board!)
5546   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5547   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5548   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5549   if(!step) return;
5550   lastX = x; lastY = y;
5551
5552   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5553   if(endPV < 0) return;
5554   if(y < margin) step = 1; else
5555   if(y > h - margin) step = -1;
5556   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5557   currentMove += step;
5558   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5559   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5560                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5561   DrawPosition(FALSE, boards[currentMove]);
5562 }
5563
5564
5565 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5566 // All positions will have equal probability, but the current method will not provide a unique
5567 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5568 #define DARK 1
5569 #define LITE 2
5570 #define ANY 3
5571
5572 int squaresLeft[4];
5573 int piecesLeft[(int)BlackPawn];
5574 int seed, nrOfShuffles;
5575
5576 void
5577 GetPositionNumber ()
5578 {       // sets global variable seed
5579         int i;
5580
5581         seed = appData.defaultFrcPosition;
5582         if(seed < 0) { // randomize based on time for negative FRC position numbers
5583                 for(i=0; i<50; i++) seed += random();
5584                 seed = random() ^ random() >> 8 ^ random() << 8;
5585                 if(seed<0) seed = -seed;
5586         }
5587 }
5588
5589 int
5590 put (Board board, int pieceType, int rank, int n, int shade)
5591 // put the piece on the (n-1)-th empty squares of the given shade
5592 {
5593         int i;
5594
5595         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5596                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5597                         board[rank][i] = (ChessSquare) pieceType;
5598                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5599                         squaresLeft[ANY]--;
5600                         piecesLeft[pieceType]--;
5601                         return i;
5602                 }
5603         }
5604         return -1;
5605 }
5606
5607
5608 void
5609 AddOnePiece (Board board, int pieceType, int rank, int shade)
5610 // calculate where the next piece goes, (any empty square), and put it there
5611 {
5612         int i;
5613
5614         i = seed % squaresLeft[shade];
5615         nrOfShuffles *= squaresLeft[shade];
5616         seed /= squaresLeft[shade];
5617         put(board, pieceType, rank, i, shade);
5618 }
5619
5620 void
5621 AddTwoPieces (Board board, int pieceType, int rank)
5622 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5623 {
5624         int i, n=squaresLeft[ANY], j=n-1, k;
5625
5626         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5627         i = seed % k;  // pick one
5628         nrOfShuffles *= k;
5629         seed /= k;
5630         while(i >= j) i -= j--;
5631         j = n - 1 - j; i += j;
5632         put(board, pieceType, rank, j, ANY);
5633         put(board, pieceType, rank, i, ANY);
5634 }
5635
5636 void
5637 SetUpShuffle (Board board, int number)
5638 {
5639         int i, p, first=1;
5640
5641         GetPositionNumber(); nrOfShuffles = 1;
5642
5643         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5644         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5645         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5646
5647         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5648
5649         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5650             p = (int) board[0][i];
5651             if(p < (int) BlackPawn) piecesLeft[p] ++;
5652             board[0][i] = EmptySquare;
5653         }
5654
5655         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5656             // shuffles restricted to allow normal castling put KRR first
5657             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5658                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5659             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5660                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5661             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5662                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5663             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5664                 put(board, WhiteRook, 0, 0, ANY);
5665             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5666         }
5667
5668         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5669             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5670             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5671                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5672                 while(piecesLeft[p] >= 2) {
5673                     AddOnePiece(board, p, 0, LITE);
5674                     AddOnePiece(board, p, 0, DARK);
5675                 }
5676                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5677             }
5678
5679         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5680             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5681             // but we leave King and Rooks for last, to possibly obey FRC restriction
5682             if(p == (int)WhiteRook) continue;
5683             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5684             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5685         }
5686
5687         // now everything is placed, except perhaps King (Unicorn) and Rooks
5688
5689         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5690             // Last King gets castling rights
5691             while(piecesLeft[(int)WhiteUnicorn]) {
5692                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5693                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5694             }
5695
5696             while(piecesLeft[(int)WhiteKing]) {
5697                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5698                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5699             }
5700
5701
5702         } else {
5703             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5704             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5705         }
5706
5707         // Only Rooks can be left; simply place them all
5708         while(piecesLeft[(int)WhiteRook]) {
5709                 i = put(board, WhiteRook, 0, 0, ANY);
5710                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5711                         if(first) {
5712                                 first=0;
5713                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5714                         }
5715                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5716                 }
5717         }
5718         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5719             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5720         }
5721
5722         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5723 }
5724
5725 int
5726 SetCharTable (char *table, const char * map)
5727 /* [HGM] moved here from winboard.c because of its general usefulness */
5728 /*       Basically a safe strcpy that uses the last character as King */
5729 {
5730     int result = FALSE; int NrPieces;
5731
5732     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5733                     && NrPieces >= 12 && !(NrPieces&1)) {
5734         int i; /* [HGM] Accept even length from 12 to 34 */
5735
5736         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5737         for( i=0; i<NrPieces/2-1; i++ ) {
5738             table[i] = map[i];
5739             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5740         }
5741         table[(int) WhiteKing]  = map[NrPieces/2-1];
5742         table[(int) BlackKing]  = map[NrPieces-1];
5743
5744         result = TRUE;
5745     }
5746
5747     return result;
5748 }
5749
5750 void
5751 Prelude (Board board)
5752 {       // [HGM] superchess: random selection of exo-pieces
5753         int i, j, k; ChessSquare p;
5754         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5755
5756         GetPositionNumber(); // use FRC position number
5757
5758         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5759             SetCharTable(pieceToChar, appData.pieceToCharTable);
5760             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5761                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5762         }
5763
5764         j = seed%4;                 seed /= 4;
5765         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5766         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5767         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5768         j = seed%3 + (seed%3 >= j); seed /= 3;
5769         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5770         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5771         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5772         j = seed%3;                 seed /= 3;
5773         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5774         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5775         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5776         j = seed%2 + (seed%2 >= j); seed /= 2;
5777         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5778         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5779         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5780         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5781         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5782         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5783         put(board, exoPieces[0],    0, 0, ANY);
5784         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5785 }
5786
5787 void
5788 InitPosition (int redraw)
5789 {
5790     ChessSquare (* pieces)[BOARD_FILES];
5791     int i, j, pawnRow, overrule,
5792     oldx = gameInfo.boardWidth,
5793     oldy = gameInfo.boardHeight,
5794     oldh = gameInfo.holdingsWidth;
5795     static int oldv;
5796
5797     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5798
5799     /* [AS] Initialize pv info list [HGM] and game status */
5800     {
5801         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5802             pvInfoList[i].depth = 0;
5803             boards[i][EP_STATUS] = EP_NONE;
5804             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5805         }
5806
5807         initialRulePlies = 0; /* 50-move counter start */
5808
5809         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5810         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5811     }
5812
5813
5814     /* [HGM] logic here is completely changed. In stead of full positions */
5815     /* the initialized data only consist of the two backranks. The switch */
5816     /* selects which one we will use, which is than copied to the Board   */
5817     /* initialPosition, which for the rest is initialized by Pawns and    */
5818     /* empty squares. This initial position is then copied to boards[0],  */
5819     /* possibly after shuffling, so that it remains available.            */
5820
5821     gameInfo.holdingsWidth = 0; /* default board sizes */
5822     gameInfo.boardWidth    = 8;
5823     gameInfo.boardHeight   = 8;
5824     gameInfo.holdingsSize  = 0;
5825     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5826     for(i=0; i<BOARD_FILES-2; i++)
5827       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5828     initialPosition[EP_STATUS] = EP_NONE;
5829     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5830     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5831          SetCharTable(pieceNickName, appData.pieceNickNames);
5832     else SetCharTable(pieceNickName, "............");
5833     pieces = FIDEArray;
5834
5835     switch (gameInfo.variant) {
5836     case VariantFischeRandom:
5837       shuffleOpenings = TRUE;
5838     default:
5839       break;
5840     case VariantShatranj:
5841       pieces = ShatranjArray;
5842       nrCastlingRights = 0;
5843       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5844       break;
5845     case VariantMakruk:
5846       pieces = makrukArray;
5847       nrCastlingRights = 0;
5848       startedFromSetupPosition = TRUE;
5849       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5850       break;
5851     case VariantTwoKings:
5852       pieces = twoKingsArray;
5853       break;
5854     case VariantGrand:
5855       pieces = GrandArray;
5856       nrCastlingRights = 0;
5857       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5858       gameInfo.boardWidth = 10;
5859       gameInfo.boardHeight = 10;
5860       gameInfo.holdingsSize = 7;
5861       break;
5862     case VariantCapaRandom:
5863       shuffleOpenings = TRUE;
5864     case VariantCapablanca:
5865       pieces = CapablancaArray;
5866       gameInfo.boardWidth = 10;
5867       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5868       break;
5869     case VariantGothic:
5870       pieces = GothicArray;
5871       gameInfo.boardWidth = 10;
5872       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5873       break;
5874     case VariantSChess:
5875       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5876       gameInfo.holdingsSize = 7;
5877       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5878       break;
5879     case VariantJanus:
5880       pieces = JanusArray;
5881       gameInfo.boardWidth = 10;
5882       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5883       nrCastlingRights = 6;
5884         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5885         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5886         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5887         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5888         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5889         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5890       break;
5891     case VariantFalcon:
5892       pieces = FalconArray;
5893       gameInfo.boardWidth = 10;
5894       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5895       break;
5896     case VariantXiangqi:
5897       pieces = XiangqiArray;
5898       gameInfo.boardWidth  = 9;
5899       gameInfo.boardHeight = 10;
5900       nrCastlingRights = 0;
5901       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5902       break;
5903     case VariantShogi:
5904       pieces = ShogiArray;
5905       gameInfo.boardWidth  = 9;
5906       gameInfo.boardHeight = 9;
5907       gameInfo.holdingsSize = 7;
5908       nrCastlingRights = 0;
5909       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5910       break;
5911     case VariantCourier:
5912       pieces = CourierArray;
5913       gameInfo.boardWidth  = 12;
5914       nrCastlingRights = 0;
5915       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5916       break;
5917     case VariantKnightmate:
5918       pieces = KnightmateArray;
5919       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5920       break;
5921     case VariantSpartan:
5922       pieces = SpartanArray;
5923       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5924       break;
5925     case VariantFairy:
5926       pieces = fairyArray;
5927       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5928       break;
5929     case VariantGreat:
5930       pieces = GreatArray;
5931       gameInfo.boardWidth = 10;
5932       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5933       gameInfo.holdingsSize = 8;
5934       break;
5935     case VariantSuper:
5936       pieces = FIDEArray;
5937       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5938       gameInfo.holdingsSize = 8;
5939       startedFromSetupPosition = TRUE;
5940       break;
5941     case VariantCrazyhouse:
5942     case VariantBughouse:
5943       pieces = FIDEArray;
5944       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5945       gameInfo.holdingsSize = 5;
5946       break;
5947     case VariantWildCastle:
5948       pieces = FIDEArray;
5949       /* !!?shuffle with kings guaranteed to be on d or e file */
5950       shuffleOpenings = 1;
5951       break;
5952     case VariantNoCastle:
5953       pieces = FIDEArray;
5954       nrCastlingRights = 0;
5955       /* !!?unconstrained back-rank shuffle */
5956       shuffleOpenings = 1;
5957       break;
5958     }
5959
5960     overrule = 0;
5961     if(appData.NrFiles >= 0) {
5962         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5963         gameInfo.boardWidth = appData.NrFiles;
5964     }
5965     if(appData.NrRanks >= 0) {
5966         gameInfo.boardHeight = appData.NrRanks;
5967     }
5968     if(appData.holdingsSize >= 0) {
5969         i = appData.holdingsSize;
5970         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5971         gameInfo.holdingsSize = i;
5972     }
5973     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5974     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5975         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5976
5977     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5978     if(pawnRow < 1) pawnRow = 1;
5979     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5980
5981     /* User pieceToChar list overrules defaults */
5982     if(appData.pieceToCharTable != NULL)
5983         SetCharTable(pieceToChar, appData.pieceToCharTable);
5984
5985     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5986
5987         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5988             s = (ChessSquare) 0; /* account holding counts in guard band */
5989         for( i=0; i<BOARD_HEIGHT; i++ )
5990             initialPosition[i][j] = s;
5991
5992         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5993         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5994         initialPosition[pawnRow][j] = WhitePawn;
5995         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5996         if(gameInfo.variant == VariantXiangqi) {
5997             if(j&1) {
5998                 initialPosition[pawnRow][j] =
5999                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6000                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6001                    initialPosition[2][j] = WhiteCannon;
6002                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6003                 }
6004             }
6005         }
6006         if(gameInfo.variant == VariantGrand) {
6007             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6008                initialPosition[0][j] = WhiteRook;
6009                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6010             }
6011         }
6012         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6013     }
6014     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6015
6016             j=BOARD_LEFT+1;
6017             initialPosition[1][j] = WhiteBishop;
6018             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6019             j=BOARD_RGHT-2;
6020             initialPosition[1][j] = WhiteRook;
6021             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6022     }
6023
6024     if( nrCastlingRights == -1) {
6025         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6026         /*       This sets default castling rights from none to normal corners   */
6027         /* Variants with other castling rights must set them themselves above    */
6028         nrCastlingRights = 6;
6029
6030         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6031         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6032         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6033         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6034         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6035         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6036      }
6037
6038      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6039      if(gameInfo.variant == VariantGreat) { // promotion commoners
6040         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6041         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6042         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6043         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6044      }
6045      if( gameInfo.variant == VariantSChess ) {
6046       initialPosition[1][0] = BlackMarshall;
6047       initialPosition[2][0] = BlackAngel;
6048       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6049       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6050       initialPosition[1][1] = initialPosition[2][1] = 
6051       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6052      }
6053   if (appData.debugMode) {
6054     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6055   }
6056     if(shuffleOpenings) {
6057         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6058         startedFromSetupPosition = TRUE;
6059     }
6060     if(startedFromPositionFile) {
6061       /* [HGM] loadPos: use PositionFile for every new game */
6062       CopyBoard(initialPosition, filePosition);
6063       for(i=0; i<nrCastlingRights; i++)
6064           initialRights[i] = filePosition[CASTLING][i];
6065       startedFromSetupPosition = TRUE;
6066     }
6067
6068     CopyBoard(boards[0], initialPosition);
6069
6070     if(oldx != gameInfo.boardWidth ||
6071        oldy != gameInfo.boardHeight ||
6072        oldv != gameInfo.variant ||
6073        oldh != gameInfo.holdingsWidth
6074                                          )
6075             InitDrawingSizes(-2 ,0);
6076
6077     oldv = gameInfo.variant;
6078     if (redraw)
6079       DrawPosition(TRUE, boards[currentMove]);
6080 }
6081
6082 void
6083 SendBoard (ChessProgramState *cps, int moveNum)
6084 {
6085     char message[MSG_SIZ];
6086
6087     if (cps->useSetboard) {
6088       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6089       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6090       SendToProgram(message, cps);
6091       free(fen);
6092
6093     } else {
6094       ChessSquare *bp;
6095       int i, j, left=0, right=BOARD_WIDTH;
6096       /* Kludge to set black to move, avoiding the troublesome and now
6097        * deprecated "black" command.
6098        */
6099       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6100         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6101
6102       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6103
6104       SendToProgram("edit\n", cps);
6105       SendToProgram("#\n", cps);
6106       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6107         bp = &boards[moveNum][i][left];
6108         for (j = left; j < right; j++, bp++) {
6109           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6110           if ((int) *bp < (int) BlackPawn) {
6111             if(j == BOARD_RGHT+1)
6112                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6113             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6114             if(message[0] == '+' || message[0] == '~') {
6115               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6116                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6117                         AAA + j, ONE + i);
6118             }
6119             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6120                 message[1] = BOARD_RGHT   - 1 - j + '1';
6121                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6122             }
6123             SendToProgram(message, cps);
6124           }
6125         }
6126       }
6127
6128       SendToProgram("c\n", cps);
6129       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6130         bp = &boards[moveNum][i][left];
6131         for (j = left; j < right; j++, bp++) {
6132           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6133           if (((int) *bp != (int) EmptySquare)
6134               && ((int) *bp >= (int) BlackPawn)) {
6135             if(j == BOARD_LEFT-2)
6136                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6137             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6138                     AAA + j, ONE + i);
6139             if(message[0] == '+' || message[0] == '~') {
6140               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6141                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6142                         AAA + j, ONE + i);
6143             }
6144             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6145                 message[1] = BOARD_RGHT   - 1 - j + '1';
6146                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6147             }
6148             SendToProgram(message, cps);
6149           }
6150         }
6151       }
6152
6153       SendToProgram(".\n", cps);
6154     }
6155     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6156 }
6157
6158 char exclusionHeader[MSG_SIZ];
6159 int exCnt, excludePtr;
6160 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6161 static Exclusion excluTab[200];
6162 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6163
6164 static void
6165 WriteMap (int s)
6166 {
6167     int j;
6168     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6169     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6170 }
6171
6172 static void
6173 ClearMap ()
6174 {
6175     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6176     excludePtr = 24; exCnt = 0;
6177     WriteMap(0);
6178 }
6179
6180 static void
6181 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6182 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6183     char buf[2*MOVE_LEN], *p;
6184     Exclusion *e = excluTab;
6185     int i;
6186     for(i=0; i<exCnt; i++)
6187         if(e[i].ff == fromX && e[i].fr == fromY &&
6188            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6189     if(i == exCnt) { // was not in exclude list; add it
6190         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6191         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6192             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6193             return; // abort
6194         }
6195         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6196         excludePtr++; e[i].mark = excludePtr++;
6197         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6198         exCnt++;
6199     }
6200     exclusionHeader[e[i].mark] = state;
6201 }
6202
6203 static int
6204 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6205 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6206     char buf[MSG_SIZ];
6207     int j, k;
6208     ChessMove moveType;
6209     if((signed char)promoChar == -1) { // kludge to indicate best move
6210         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6211             return 1; // if unparsable, abort
6212     }
6213     // update exclusion map (resolving toggle by consulting existing state)
6214     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6215     j = k%8; k >>= 3;
6216     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6217     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6218          excludeMap[k] |=   1<<j;
6219     else excludeMap[k] &= ~(1<<j);
6220     // update header
6221     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6222     // inform engine
6223     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6224     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6225     SendToProgram(buf, &first);
6226     return (state == '+');
6227 }
6228
6229 static void
6230 ExcludeClick (int index)
6231 {
6232     int i, j;
6233     Exclusion *e = excluTab;
6234     if(index < 25) { // none, best or tail clicked
6235         if(index < 13) { // none: include all
6236             WriteMap(0); // clear map
6237             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6238             SendToProgram("include all\n", &first); // and inform engine
6239         } else if(index > 18) { // tail
6240             if(exclusionHeader[19] == '-') { // tail was excluded
6241                 SendToProgram("include all\n", &first);
6242                 WriteMap(0); // clear map completely
6243                 // now re-exclude selected moves
6244                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6245                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6246             } else { // tail was included or in mixed state
6247                 SendToProgram("exclude all\n", &first);
6248                 WriteMap(0xFF); // fill map completely
6249                 // now re-include selected moves
6250                 j = 0; // count them
6251                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6252                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6253                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6254             }
6255         } else { // best
6256             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6257         }
6258     } else {
6259         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6260             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6261             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6262             break;
6263         }
6264     }
6265 }
6266
6267 ChessSquare
6268 DefaultPromoChoice (int white)
6269 {
6270     ChessSquare result;
6271     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6272         result = WhiteFerz; // no choice
6273     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6274         result= WhiteKing; // in Suicide Q is the last thing we want
6275     else if(gameInfo.variant == VariantSpartan)
6276         result = white ? WhiteQueen : WhiteAngel;
6277     else result = WhiteQueen;
6278     if(!white) result = WHITE_TO_BLACK result;
6279     return result;
6280 }
6281
6282 static int autoQueen; // [HGM] oneclick
6283
6284 int
6285 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6286 {
6287     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6288     /* [HGM] add Shogi promotions */
6289     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6290     ChessSquare piece;
6291     ChessMove moveType;
6292     Boolean premove;
6293
6294     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6295     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6296
6297     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6298       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6299         return FALSE;
6300
6301     piece = boards[currentMove][fromY][fromX];
6302     if(gameInfo.variant == VariantShogi) {
6303         promotionZoneSize = BOARD_HEIGHT/3;
6304         highestPromotingPiece = (int)WhiteFerz;
6305     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6306         promotionZoneSize = 3;
6307     }
6308
6309     // Treat Lance as Pawn when it is not representing Amazon
6310     if(gameInfo.variant != VariantSuper) {
6311         if(piece == WhiteLance) piece = WhitePawn; else
6312         if(piece == BlackLance) piece = BlackPawn;
6313     }
6314
6315     // next weed out all moves that do not touch the promotion zone at all
6316     if((int)piece >= BlackPawn) {
6317         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6318              return FALSE;
6319         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6320     } else {
6321         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6322            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6323     }
6324
6325     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6326
6327     // weed out mandatory Shogi promotions
6328     if(gameInfo.variant == VariantShogi) {
6329         if(piece >= BlackPawn) {
6330             if(toY == 0 && piece == BlackPawn ||
6331                toY == 0 && piece == BlackQueen ||
6332                toY <= 1 && piece == BlackKnight) {
6333                 *promoChoice = '+';
6334                 return FALSE;
6335             }
6336         } else {
6337             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6338                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6339                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6340                 *promoChoice = '+';
6341                 return FALSE;
6342             }
6343         }
6344     }
6345
6346     // weed out obviously illegal Pawn moves
6347     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6348         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6349         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6350         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6351         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6352         // note we are not allowed to test for valid (non-)capture, due to premove
6353     }
6354
6355     // we either have a choice what to promote to, or (in Shogi) whether to promote
6356     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6357         *promoChoice = PieceToChar(BlackFerz);  // no choice
6358         return FALSE;
6359     }
6360     // no sense asking what we must promote to if it is going to explode...
6361     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6362         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6363         return FALSE;
6364     }
6365     // give caller the default choice even if we will not make it
6366     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6367     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6368     if(        sweepSelect && gameInfo.variant != VariantGreat
6369                            && gameInfo.variant != VariantGrand
6370                            && gameInfo.variant != VariantSuper) return FALSE;
6371     if(autoQueen) return FALSE; // predetermined
6372
6373     // suppress promotion popup on illegal moves that are not premoves
6374     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6375               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6376     if(appData.testLegality && !premove) {
6377         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6378                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6379         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6380             return FALSE;
6381     }
6382
6383     return TRUE;
6384 }
6385
6386 int
6387 InPalace (int row, int column)
6388 {   /* [HGM] for Xiangqi */
6389     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6390          column < (BOARD_WIDTH + 4)/2 &&
6391          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6392     return FALSE;
6393 }
6394
6395 int
6396 PieceForSquare (int x, int y)
6397 {
6398   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6399      return -1;
6400   else
6401      return boards[currentMove][y][x];
6402 }
6403
6404 int
6405 OKToStartUserMove (int x, int y)
6406 {
6407     ChessSquare from_piece;
6408     int white_piece;
6409
6410     if (matchMode) return FALSE;
6411     if (gameMode == EditPosition) return TRUE;
6412
6413     if (x >= 0 && y >= 0)
6414       from_piece = boards[currentMove][y][x];
6415     else
6416       from_piece = EmptySquare;
6417
6418     if (from_piece == EmptySquare) return FALSE;
6419
6420     white_piece = (int)from_piece >= (int)WhitePawn &&
6421       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6422
6423     switch (gameMode) {
6424       case AnalyzeFile:
6425       case TwoMachinesPlay:
6426       case EndOfGame:
6427         return FALSE;
6428
6429       case IcsObserving:
6430       case IcsIdle:
6431         return FALSE;
6432
6433       case MachinePlaysWhite:
6434       case IcsPlayingBlack:
6435         if (appData.zippyPlay) return FALSE;
6436         if (white_piece) {
6437             DisplayMoveError(_("You are playing Black"));
6438             return FALSE;
6439         }
6440         break;
6441
6442       case MachinePlaysBlack:
6443       case IcsPlayingWhite:
6444         if (appData.zippyPlay) return FALSE;
6445         if (!white_piece) {
6446             DisplayMoveError(_("You are playing White"));
6447             return FALSE;
6448         }
6449         break;
6450
6451       case PlayFromGameFile:
6452             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6453       case EditGame:
6454         if (!white_piece && WhiteOnMove(currentMove)) {
6455             DisplayMoveError(_("It is White's turn"));
6456             return FALSE;
6457         }
6458         if (white_piece && !WhiteOnMove(currentMove)) {
6459             DisplayMoveError(_("It is Black's turn"));
6460             return FALSE;
6461         }
6462         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6463             /* Editing correspondence game history */
6464             /* Could disallow this or prompt for confirmation */
6465             cmailOldMove = -1;
6466         }
6467         break;
6468
6469       case BeginningOfGame:
6470         if (appData.icsActive) return FALSE;
6471         if (!appData.noChessProgram) {
6472             if (!white_piece) {
6473                 DisplayMoveError(_("You are playing White"));
6474                 return FALSE;
6475             }
6476         }
6477         break;
6478
6479       case Training:
6480         if (!white_piece && WhiteOnMove(currentMove)) {
6481             DisplayMoveError(_("It is White's turn"));
6482             return FALSE;
6483         }
6484         if (white_piece && !WhiteOnMove(currentMove)) {
6485             DisplayMoveError(_("It is Black's turn"));
6486             return FALSE;
6487         }
6488         break;
6489
6490       default:
6491       case IcsExamining:
6492         break;
6493     }
6494     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6495         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6496         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6497         && gameMode != AnalyzeFile && gameMode != Training) {
6498         DisplayMoveError(_("Displayed position is not current"));
6499         return FALSE;
6500     }
6501     return TRUE;
6502 }
6503
6504 Boolean
6505 OnlyMove (int *x, int *y, Boolean captures) 
6506 {
6507     DisambiguateClosure cl;
6508     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6509     switch(gameMode) {
6510       case MachinePlaysBlack:
6511       case IcsPlayingWhite:
6512       case BeginningOfGame:
6513         if(!WhiteOnMove(currentMove)) return FALSE;
6514         break;
6515       case MachinePlaysWhite:
6516       case IcsPlayingBlack:
6517         if(WhiteOnMove(currentMove)) return FALSE;
6518         break;
6519       case EditGame:
6520         break;
6521       default:
6522         return FALSE;
6523     }
6524     cl.pieceIn = EmptySquare;
6525     cl.rfIn = *y;
6526     cl.ffIn = *x;
6527     cl.rtIn = -1;
6528     cl.ftIn = -1;
6529     cl.promoCharIn = NULLCHAR;
6530     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6531     if( cl.kind == NormalMove ||
6532         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6533         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6534         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6535       fromX = cl.ff;
6536       fromY = cl.rf;
6537       *x = cl.ft;
6538       *y = cl.rt;
6539       return TRUE;
6540     }
6541     if(cl.kind != ImpossibleMove) return FALSE;
6542     cl.pieceIn = EmptySquare;
6543     cl.rfIn = -1;
6544     cl.ffIn = -1;
6545     cl.rtIn = *y;
6546     cl.ftIn = *x;
6547     cl.promoCharIn = NULLCHAR;
6548     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6549     if( cl.kind == NormalMove ||
6550         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6551         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6552         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6553       fromX = cl.ff;
6554       fromY = cl.rf;
6555       *x = cl.ft;
6556       *y = cl.rt;
6557       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6558       return TRUE;
6559     }
6560     return FALSE;
6561 }
6562
6563 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6564 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6565 int lastLoadGameUseList = FALSE;
6566 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6567 ChessMove lastLoadGameStart = EndOfFile;
6568 int doubleClick;
6569
6570 void
6571 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6572 {
6573     ChessMove moveType;
6574     ChessSquare pup;
6575     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6576
6577     /* Check if the user is playing in turn.  This is complicated because we
6578        let the user "pick up" a piece before it is his turn.  So the piece he
6579        tried to pick up may have been captured by the time he puts it down!
6580        Therefore we use the color the user is supposed to be playing in this
6581        test, not the color of the piece that is currently on the starting
6582        square---except in EditGame mode, where the user is playing both
6583        sides; fortunately there the capture race can't happen.  (It can
6584        now happen in IcsExamining mode, but that's just too bad.  The user
6585        will get a somewhat confusing message in that case.)
6586        */
6587
6588     switch (gameMode) {
6589       case AnalyzeFile:
6590       case TwoMachinesPlay:
6591       case EndOfGame:
6592       case IcsObserving:
6593       case IcsIdle:
6594         /* We switched into a game mode where moves are not accepted,
6595            perhaps while the mouse button was down. */
6596         return;
6597
6598       case MachinePlaysWhite:
6599         /* User is moving for Black */
6600         if (WhiteOnMove(currentMove)) {
6601             DisplayMoveError(_("It is White's turn"));
6602             return;
6603         }
6604         break;
6605
6606       case MachinePlaysBlack:
6607         /* User is moving for White */
6608         if (!WhiteOnMove(currentMove)) {
6609             DisplayMoveError(_("It is Black's turn"));
6610             return;
6611         }
6612         break;
6613
6614       case PlayFromGameFile:
6615             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6616       case EditGame:
6617       case IcsExamining:
6618       case BeginningOfGame:
6619       case AnalyzeMode:
6620       case Training:
6621         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6622         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6623             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6624             /* User is moving for Black */
6625             if (WhiteOnMove(currentMove)) {
6626                 DisplayMoveError(_("It is White's turn"));
6627                 return;
6628             }
6629         } else {
6630             /* User is moving for White */
6631             if (!WhiteOnMove(currentMove)) {
6632                 DisplayMoveError(_("It is Black's turn"));
6633                 return;
6634             }
6635         }
6636         break;
6637
6638       case IcsPlayingBlack:
6639         /* User is moving for Black */
6640         if (WhiteOnMove(currentMove)) {
6641             if (!appData.premove) {
6642                 DisplayMoveError(_("It is White's turn"));
6643             } else if (toX >= 0 && toY >= 0) {
6644                 premoveToX = toX;
6645                 premoveToY = toY;
6646                 premoveFromX = fromX;
6647                 premoveFromY = fromY;
6648                 premovePromoChar = promoChar;
6649                 gotPremove = 1;
6650                 if (appData.debugMode)
6651                     fprintf(debugFP, "Got premove: fromX %d,"
6652                             "fromY %d, toX %d, toY %d\n",
6653                             fromX, fromY, toX, toY);
6654             }
6655             return;
6656         }
6657         break;
6658
6659       case IcsPlayingWhite:
6660         /* User is moving for White */
6661         if (!WhiteOnMove(currentMove)) {
6662             if (!appData.premove) {
6663                 DisplayMoveError(_("It is Black's turn"));
6664             } else if (toX >= 0 && toY >= 0) {
6665                 premoveToX = toX;
6666                 premoveToY = toY;
6667                 premoveFromX = fromX;
6668                 premoveFromY = fromY;
6669                 premovePromoChar = promoChar;
6670                 gotPremove = 1;
6671                 if (appData.debugMode)
6672                     fprintf(debugFP, "Got premove: fromX %d,"
6673                             "fromY %d, toX %d, toY %d\n",
6674                             fromX, fromY, toX, toY);
6675             }
6676             return;
6677         }
6678         break;
6679
6680       default:
6681         break;
6682
6683       case EditPosition:
6684         /* EditPosition, empty square, or different color piece;
6685            click-click move is possible */
6686         if (toX == -2 || toY == -2) {
6687             boards[0][fromY][fromX] = EmptySquare;
6688             DrawPosition(FALSE, boards[currentMove]);
6689             return;
6690         } else if (toX >= 0 && toY >= 0) {
6691             boards[0][toY][toX] = boards[0][fromY][fromX];
6692             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6693                 if(boards[0][fromY][0] != EmptySquare) {
6694                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6695                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6696                 }
6697             } else
6698             if(fromX == BOARD_RGHT+1) {
6699                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6700                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6701                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6702                 }
6703             } else
6704             boards[0][fromY][fromX] = gatingPiece;
6705             DrawPosition(FALSE, boards[currentMove]);
6706             return;
6707         }
6708         return;
6709     }
6710
6711     if(toX < 0 || toY < 0) return;
6712     pup = boards[currentMove][toY][toX];
6713
6714     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6715     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6716          if( pup != EmptySquare ) return;
6717          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6718            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6719                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6720            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6721            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6722            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6723            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6724          fromY = DROP_RANK;
6725     }
6726
6727     /* [HGM] always test for legality, to get promotion info */
6728     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6729                                          fromY, fromX, toY, toX, promoChar);
6730
6731     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6732
6733     /* [HGM] but possibly ignore an IllegalMove result */
6734     if (appData.testLegality) {
6735         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6736             DisplayMoveError(_("Illegal move"));
6737             return;
6738         }
6739     }
6740
6741     if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6742         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6743              ClearPremoveHighlights(); // was included
6744         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6745         return;
6746     }
6747
6748     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6749 }
6750
6751 /* Common tail of UserMoveEvent and DropMenuEvent */
6752 int
6753 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6754 {
6755     char *bookHit = 0;
6756
6757     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6758         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6759         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6760         if(WhiteOnMove(currentMove)) {
6761             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6762         } else {
6763             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6764         }
6765     }
6766
6767     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6768        move type in caller when we know the move is a legal promotion */
6769     if(moveType == NormalMove && promoChar)
6770         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6771
6772     /* [HGM] <popupFix> The following if has been moved here from
6773        UserMoveEvent(). Because it seemed to belong here (why not allow
6774        piece drops in training games?), and because it can only be
6775        performed after it is known to what we promote. */
6776     if (gameMode == Training) {
6777       /* compare the move played on the board to the next move in the
6778        * game. If they match, display the move and the opponent's response.
6779        * If they don't match, display an error message.
6780        */
6781       int saveAnimate;
6782       Board testBoard;
6783       CopyBoard(testBoard, boards[currentMove]);
6784       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6785
6786       if (CompareBoards(testBoard, boards[currentMove+1])) {
6787         ForwardInner(currentMove+1);
6788
6789         /* Autoplay the opponent's response.
6790          * if appData.animate was TRUE when Training mode was entered,
6791          * the response will be animated.
6792          */
6793         saveAnimate = appData.animate;
6794         appData.animate = animateTraining;
6795         ForwardInner(currentMove+1);
6796         appData.animate = saveAnimate;
6797
6798         /* check for the end of the game */
6799         if (currentMove >= forwardMostMove) {
6800           gameMode = PlayFromGameFile;
6801           ModeHighlight();
6802           SetTrainingModeOff();
6803           DisplayInformation(_("End of game"));
6804         }
6805       } else {
6806         DisplayError(_("Incorrect move"), 0);
6807       }
6808       return 1;
6809     }
6810
6811   /* Ok, now we know that the move is good, so we can kill
6812      the previous line in Analysis Mode */
6813   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6814                                 && currentMove < forwardMostMove) {
6815     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6816     else forwardMostMove = currentMove;
6817   }
6818
6819   ClearMap();
6820
6821   /* If we need the chess program but it's dead, restart it */
6822   ResurrectChessProgram();
6823
6824   /* A user move restarts a paused game*/
6825   if (pausing)
6826     PauseEvent();
6827
6828   thinkOutput[0] = NULLCHAR;
6829
6830   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6831
6832   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6833     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6834     return 1;
6835   }
6836
6837   if (gameMode == BeginningOfGame) {
6838     if (appData.noChessProgram) {
6839       gameMode = EditGame;
6840       SetGameInfo();
6841     } else {
6842       char buf[MSG_SIZ];
6843       gameMode = MachinePlaysBlack;
6844       StartClocks();
6845       SetGameInfo();
6846       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6847       DisplayTitle(buf);
6848       if (first.sendName) {
6849         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6850         SendToProgram(buf, &first);
6851       }
6852       StartClocks();
6853     }
6854     ModeHighlight();
6855   }
6856
6857   /* Relay move to ICS or chess engine */
6858   if(second.analyzing) ToggleSecond(); // for now, we just stop second analyzing engine
6859   if (appData.icsActive) {
6860     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6861         gameMode == IcsExamining) {
6862       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6863         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6864         SendToICS("draw ");
6865         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6866       }
6867       // also send plain move, in case ICS does not understand atomic claims
6868       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6869       ics_user_moved = 1;
6870     }
6871   } else {
6872     if (first.sendTime && (gameMode == BeginningOfGame ||
6873                            gameMode == MachinePlaysWhite ||
6874                            gameMode == MachinePlaysBlack)) {
6875       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6876     }
6877     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6878          // [HGM] book: if program might be playing, let it use book
6879         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6880         first.maybeThinking = TRUE;
6881     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6882         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6883         SendBoard(&first, currentMove+1);
6884     } else SendMoveToProgram(forwardMostMove-1, &first);
6885     if (currentMove == cmailOldMove + 1) {
6886       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6887     }
6888   }
6889
6890   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6891
6892   switch (gameMode) {
6893   case EditGame:
6894     if(appData.testLegality)
6895     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6896     case MT_NONE:
6897     case MT_CHECK:
6898       break;
6899     case MT_CHECKMATE:
6900     case MT_STAINMATE:
6901       if (WhiteOnMove(currentMove)) {
6902         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6903       } else {
6904         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6905       }
6906       break;
6907     case MT_STALEMATE:
6908       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6909       break;
6910     }
6911     break;
6912
6913   case MachinePlaysBlack:
6914   case MachinePlaysWhite:
6915     /* disable certain menu options while machine is thinking */
6916     SetMachineThinkingEnables();
6917     break;
6918
6919   default:
6920     break;
6921   }
6922
6923   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6924   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6925
6926   if(bookHit) { // [HGM] book: simulate book reply
6927         static char bookMove[MSG_SIZ]; // a bit generous?
6928
6929         programStats.nodes = programStats.depth = programStats.time =
6930         programStats.score = programStats.got_only_move = 0;
6931         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6932
6933         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6934         strcat(bookMove, bookHit);
6935         HandleMachineMove(bookMove, &first);
6936   }
6937   return 1;
6938 }
6939
6940 void
6941 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6942 {
6943     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6944     Markers *m = (Markers *) closure;
6945     if(rf == fromY && ff == fromX)
6946         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6947                          || kind == WhiteCapturesEnPassant
6948                          || kind == BlackCapturesEnPassant);
6949     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6950 }
6951
6952 void
6953 MarkTargetSquares (int clear)
6954 {
6955   int x, y;
6956   if(clear) // no reason to ever suppress clearing
6957     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6958   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6959      !appData.testLegality || gameMode == EditPosition) return;
6960   if(!clear) {
6961     int capt = 0;
6962     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6963     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6964       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6965       if(capt)
6966       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6967     }
6968   }
6969   DrawPosition(FALSE, NULL);
6970 }
6971
6972 int
6973 Explode (Board board, int fromX, int fromY, int toX, int toY)
6974 {
6975     if(gameInfo.variant == VariantAtomic &&
6976        (board[toY][toX] != EmptySquare ||                     // capture?
6977         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6978                          board[fromY][fromX] == BlackPawn   )
6979       )) {
6980         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6981         return TRUE;
6982     }
6983     return FALSE;
6984 }
6985
6986 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6987
6988 int
6989 CanPromote (ChessSquare piece, int y)
6990 {
6991         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6992         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6993         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6994            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6995            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6996                                                   gameInfo.variant == VariantMakruk) return FALSE;
6997         return (piece == BlackPawn && y == 1 ||
6998                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6999                 piece == BlackLance && y == 1 ||
7000                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7001 }
7002
7003 void
7004 LeftClick (ClickType clickType, int xPix, int yPix)
7005 {
7006     int x, y;
7007     Boolean saveAnimate;
7008     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7009     char promoChoice = NULLCHAR;
7010     ChessSquare piece;
7011     static TimeMark lastClickTime, prevClickTime;
7012
7013     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7014
7015     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7016
7017     if (clickType == Press) ErrorPopDown();
7018
7019     x = EventToSquare(xPix, BOARD_WIDTH);
7020     y = EventToSquare(yPix, BOARD_HEIGHT);
7021     if (!flipView && y >= 0) {
7022         y = BOARD_HEIGHT - 1 - y;
7023     }
7024     if (flipView && x >= 0) {
7025         x = BOARD_WIDTH - 1 - x;
7026     }
7027
7028     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7029         defaultPromoChoice = promoSweep;
7030         promoSweep = EmptySquare;   // terminate sweep
7031         promoDefaultAltered = TRUE;
7032         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7033     }
7034
7035     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7036         if(clickType == Release) return; // ignore upclick of click-click destination
7037         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7038         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7039         if(gameInfo.holdingsWidth &&
7040                 (WhiteOnMove(currentMove)
7041                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7042                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7043             // click in right holdings, for determining promotion piece
7044             ChessSquare p = boards[currentMove][y][x];
7045             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7046             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7047             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7048                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7049                 fromX = fromY = -1;
7050                 return;
7051             }
7052         }
7053         DrawPosition(FALSE, boards[currentMove]);
7054         return;
7055     }
7056
7057     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7058     if(clickType == Press
7059             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7060               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7061               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7062         return;
7063
7064     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7065         // could be static click on premove from-square: abort premove
7066         gotPremove = 0;
7067         ClearPremoveHighlights();
7068     }
7069
7070     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7071         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7072
7073     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7074         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7075                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7076         defaultPromoChoice = DefaultPromoChoice(side);
7077     }
7078
7079     autoQueen = appData.alwaysPromoteToQueen;
7080
7081     if (fromX == -1) {
7082       int originalY = y;
7083       gatingPiece = EmptySquare;
7084       if (clickType != Press) {
7085         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7086             DragPieceEnd(xPix, yPix); dragging = 0;
7087             DrawPosition(FALSE, NULL);
7088         }
7089         return;
7090       }
7091       doubleClick = FALSE;
7092       fromX = x; fromY = y; toX = toY = -1;
7093       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7094          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7095          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7096             /* First square */
7097             if (OKToStartUserMove(fromX, fromY)) {
7098                 second = 0;
7099                 MarkTargetSquares(0);
7100                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7101                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7102                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7103                     promoSweep = defaultPromoChoice;
7104                     selectFlag = 0; lastX = xPix; lastY = yPix;
7105                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7106                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7107                 }
7108                 if (appData.highlightDragging) {
7109                     SetHighlights(fromX, fromY, -1, -1);
7110                 } else {
7111                     ClearHighlights();
7112                 }
7113             } else fromX = fromY = -1;
7114             return;
7115         }
7116     }
7117
7118     /* fromX != -1 */
7119     if (clickType == Press && gameMode != EditPosition) {
7120         ChessSquare fromP;
7121         ChessSquare toP;
7122         int frc;
7123
7124         // ignore off-board to clicks
7125         if(y < 0 || x < 0) return;
7126
7127         /* Check if clicking again on the same color piece */
7128         fromP = boards[currentMove][fromY][fromX];
7129         toP = boards[currentMove][y][x];
7130         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7131         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7132              WhitePawn <= toP && toP <= WhiteKing &&
7133              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7134              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7135             (BlackPawn <= fromP && fromP <= BlackKing &&
7136              BlackPawn <= toP && toP <= BlackKing &&
7137              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7138              !(fromP == BlackKing && toP == BlackRook && frc))) {
7139             /* Clicked again on same color piece -- changed his mind */
7140             second = (x == fromX && y == fromY);
7141             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7142                 second = FALSE; // first double-click rather than scond click
7143                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7144             }
7145             promoDefaultAltered = FALSE;
7146             MarkTargetSquares(1);
7147            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7148             if (appData.highlightDragging) {
7149                 SetHighlights(x, y, -1, -1);
7150             } else {
7151                 ClearHighlights();
7152             }
7153             if (OKToStartUserMove(x, y)) {
7154                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7155                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7156                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7157                  gatingPiece = boards[currentMove][fromY][fromX];
7158                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7159                 fromX = x;
7160                 fromY = y; dragging = 1;
7161                 MarkTargetSquares(0);
7162                 DragPieceBegin(xPix, yPix, FALSE);
7163                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7164                     promoSweep = defaultPromoChoice;
7165                     selectFlag = 0; lastX = xPix; lastY = yPix;
7166                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7167                 }
7168             }
7169            }
7170            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7171            second = FALSE; 
7172         }
7173         // ignore clicks on holdings
7174         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7175     }
7176
7177     if (clickType == Release && x == fromX && y == fromY) {
7178         DragPieceEnd(xPix, yPix); dragging = 0;
7179         if(clearFlag) {
7180             // a deferred attempt to click-click move an empty square on top of a piece
7181             boards[currentMove][y][x] = EmptySquare;
7182             ClearHighlights();
7183             DrawPosition(FALSE, boards[currentMove]);
7184             fromX = fromY = -1; clearFlag = 0;
7185             return;
7186         }
7187         if (appData.animateDragging) {
7188             /* Undo animation damage if any */
7189             DrawPosition(FALSE, NULL);
7190         }
7191         if (second || sweepSelecting) {
7192             /* Second up/down in same square; just abort move */
7193             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7194             second = sweepSelecting = 0;
7195             fromX = fromY = -1;
7196             gatingPiece = EmptySquare;
7197             ClearHighlights();
7198             gotPremove = 0;
7199             ClearPremoveHighlights();
7200         } else {
7201             /* First upclick in same square; start click-click mode */
7202             SetHighlights(x, y, -1, -1);
7203         }
7204         return;
7205     }
7206
7207     clearFlag = 0;
7208
7209     /* we now have a different from- and (possibly off-board) to-square */
7210     /* Completed move */
7211     if(!sweepSelecting) {
7212         toX = x;
7213         toY = y;
7214     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7215
7216     saveAnimate = appData.animate;
7217     if (clickType == Press) {
7218         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7219             // must be Edit Position mode with empty-square selected
7220             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7221             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7222             return;
7223         }
7224         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7225           if(appData.sweepSelect) {
7226             ChessSquare piece = boards[currentMove][fromY][fromX];
7227             promoSweep = defaultPromoChoice;
7228             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7229             selectFlag = 0; lastX = xPix; lastY = yPix;
7230             Sweep(0); // Pawn that is going to promote: preview promotion piece
7231             sweepSelecting = 1;
7232             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7233             MarkTargetSquares(1);
7234           }
7235           return; // promo popup appears on up-click
7236         }
7237         /* Finish clickclick move */
7238         if (appData.animate || appData.highlightLastMove) {
7239             SetHighlights(fromX, fromY, toX, toY);
7240         } else {
7241             ClearHighlights();
7242         }
7243     } else {
7244         /* Finish drag move */
7245         if (appData.highlightLastMove) {
7246             SetHighlights(fromX, fromY, toX, toY);
7247         } else {
7248             ClearHighlights();
7249         }
7250         DragPieceEnd(xPix, yPix); dragging = 0;
7251         /* Don't animate move and drag both */
7252         appData.animate = FALSE;
7253     }
7254     MarkTargetSquares(1);
7255
7256     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7257     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7258         ChessSquare piece = boards[currentMove][fromY][fromX];
7259         if(gameMode == EditPosition && piece != EmptySquare &&
7260            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7261             int n;
7262
7263             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7264                 n = PieceToNumber(piece - (int)BlackPawn);
7265                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7266                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7267                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7268             } else
7269             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7270                 n = PieceToNumber(piece);
7271                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7272                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7273                 boards[currentMove][n][BOARD_WIDTH-2]++;
7274             }
7275             boards[currentMove][fromY][fromX] = EmptySquare;
7276         }
7277         ClearHighlights();
7278         fromX = fromY = -1;
7279         DrawPosition(TRUE, boards[currentMove]);
7280         return;
7281     }
7282
7283     // off-board moves should not be highlighted
7284     if(x < 0 || y < 0) ClearHighlights();
7285
7286     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7287
7288     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7289         SetHighlights(fromX, fromY, toX, toY);
7290         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7291             // [HGM] super: promotion to captured piece selected from holdings
7292             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7293             promotionChoice = TRUE;
7294             // kludge follows to temporarily execute move on display, without promoting yet
7295             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7296             boards[currentMove][toY][toX] = p;
7297             DrawPosition(FALSE, boards[currentMove]);
7298             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7299             boards[currentMove][toY][toX] = q;
7300             DisplayMessage("Click in holdings to choose piece", "");
7301             return;
7302         }
7303         PromotionPopUp();
7304     } else {
7305         int oldMove = currentMove;
7306         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7307         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7308         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7309         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7310            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7311             DrawPosition(TRUE, boards[currentMove]);
7312         fromX = fromY = -1;
7313     }
7314     appData.animate = saveAnimate;
7315     if (appData.animate || appData.animateDragging) {
7316         /* Undo animation damage if needed */
7317         DrawPosition(FALSE, NULL);
7318     }
7319 }
7320
7321 int
7322 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7323 {   // front-end-free part taken out of PieceMenuPopup
7324     int whichMenu; int xSqr, ySqr;
7325
7326     if(seekGraphUp) { // [HGM] seekgraph
7327         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7328         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7329         return -2;
7330     }
7331
7332     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7333          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7334         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7335         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7336         if(action == Press)   {
7337             originalFlip = flipView;
7338             flipView = !flipView; // temporarily flip board to see game from partners perspective
7339             DrawPosition(TRUE, partnerBoard);
7340             DisplayMessage(partnerStatus, "");
7341             partnerUp = TRUE;
7342         } else if(action == Release) {
7343             flipView = originalFlip;
7344             DrawPosition(TRUE, boards[currentMove]);
7345             partnerUp = FALSE;
7346         }
7347         return -2;
7348     }
7349
7350     xSqr = EventToSquare(x, BOARD_WIDTH);
7351     ySqr = EventToSquare(y, BOARD_HEIGHT);
7352     if (action == Release) {
7353         if(pieceSweep != EmptySquare) {
7354             EditPositionMenuEvent(pieceSweep, toX, toY);
7355             pieceSweep = EmptySquare;
7356         } else UnLoadPV(); // [HGM] pv
7357     }
7358     if (action != Press) return -2; // return code to be ignored
7359     switch (gameMode) {
7360       case IcsExamining:
7361         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7362       case EditPosition:
7363         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7364         if (xSqr < 0 || ySqr < 0) return -1;
7365         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7366         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7367         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7368         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7369         NextPiece(0);
7370         return 2; // grab
7371       case IcsObserving:
7372         if(!appData.icsEngineAnalyze) return -1;
7373       case IcsPlayingWhite:
7374       case IcsPlayingBlack:
7375         if(!appData.zippyPlay) goto noZip;
7376       case AnalyzeMode:
7377       case AnalyzeFile:
7378       case MachinePlaysWhite:
7379       case MachinePlaysBlack:
7380       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7381         if (!appData.dropMenu) {
7382           LoadPV(x, y);
7383           return 2; // flag front-end to grab mouse events
7384         }
7385         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7386            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7387       case EditGame:
7388       noZip:
7389         if (xSqr < 0 || ySqr < 0) return -1;
7390         if (!appData.dropMenu || appData.testLegality &&
7391             gameInfo.variant != VariantBughouse &&
7392             gameInfo.variant != VariantCrazyhouse) return -1;
7393         whichMenu = 1; // drop menu
7394         break;
7395       default:
7396         return -1;
7397     }
7398
7399     if (((*fromX = xSqr) < 0) ||
7400         ((*fromY = ySqr) < 0)) {
7401         *fromX = *fromY = -1;
7402         return -1;
7403     }
7404     if (flipView)
7405       *fromX = BOARD_WIDTH - 1 - *fromX;
7406     else
7407       *fromY = BOARD_HEIGHT - 1 - *fromY;
7408
7409     return whichMenu;
7410 }
7411
7412 void
7413 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7414 {
7415 //    char * hint = lastHint;
7416     FrontEndProgramStats stats;
7417
7418     stats.which = cps == &first ? 0 : 1;
7419     stats.depth = cpstats->depth;
7420     stats.nodes = cpstats->nodes;
7421     stats.score = cpstats->score;
7422     stats.time = cpstats->time;
7423     stats.pv = cpstats->movelist;
7424     stats.hint = lastHint;
7425     stats.an_move_index = 0;
7426     stats.an_move_count = 0;
7427
7428     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7429         stats.hint = cpstats->move_name;
7430         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7431         stats.an_move_count = cpstats->nr_moves;
7432     }
7433
7434     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
7435
7436     SetProgramStats( &stats );
7437 }
7438
7439 void
7440 ClearEngineOutputPane (int which)
7441 {
7442     static FrontEndProgramStats dummyStats;
7443     dummyStats.which = which;
7444     dummyStats.pv = "#";
7445     SetProgramStats( &dummyStats );
7446 }
7447
7448 #define MAXPLAYERS 500
7449
7450 char *
7451 TourneyStandings (int display)
7452 {
7453     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7454     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7455     char result, *p, *names[MAXPLAYERS];
7456
7457     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7458         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7459     names[0] = p = strdup(appData.participants);
7460     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7461
7462     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7463
7464     while(result = appData.results[nr]) {
7465         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7466         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7467         wScore = bScore = 0;
7468         switch(result) {
7469           case '+': wScore = 2; break;
7470           case '-': bScore = 2; break;
7471           case '=': wScore = bScore = 1; break;
7472           case ' ':
7473           case '*': return strdup("busy"); // tourney not finished
7474         }
7475         score[w] += wScore;
7476         score[b] += bScore;
7477         games[w]++;
7478         games[b]++;
7479         nr++;
7480     }
7481     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7482     for(w=0; w<nPlayers; w++) {
7483         bScore = -1;
7484         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7485         ranking[w] = b; points[w] = bScore; score[b] = -2;
7486     }
7487     p = malloc(nPlayers*34+1);
7488     for(w=0; w<nPlayers && w<display; w++)
7489         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7490     free(names[0]);
7491     return p;
7492 }
7493
7494 void
7495 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7496 {       // count all piece types
7497         int p, f, r;
7498         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7499         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7500         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7501                 p = board[r][f];
7502                 pCnt[p]++;
7503                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7504                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7505                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7506                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7507                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7508                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7509         }
7510 }
7511
7512 int
7513 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7514 {
7515         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7516         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7517
7518         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7519         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7520         if(myPawns == 2 && nMine == 3) // KPP
7521             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7522         if(myPawns == 1 && nMine == 2) // KP
7523             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7524         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7525             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7526         if(myPawns) return FALSE;
7527         if(pCnt[WhiteRook+side])
7528             return pCnt[BlackRook-side] ||
7529                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7530                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7531                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7532         if(pCnt[WhiteCannon+side]) {
7533             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7534             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7535         }
7536         if(pCnt[WhiteKnight+side])
7537             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7538         return FALSE;
7539 }
7540
7541 int
7542 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7543 {
7544         VariantClass v = gameInfo.variant;
7545
7546         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7547         if(v == VariantShatranj) return TRUE; // always winnable through baring
7548         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7549         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7550
7551         if(v == VariantXiangqi) {
7552                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7553
7554                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7555                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7556                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7557                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7558                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7559                 if(stale) // we have at least one last-rank P plus perhaps C
7560                     return majors // KPKX
7561                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7562                 else // KCA*E*
7563                     return pCnt[WhiteFerz+side] // KCAK
7564                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7565                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7566                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7567
7568         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7569                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7570
7571                 if(nMine == 1) return FALSE; // bare King
7572                 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
7573                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7574                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7575                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7576                 if(pCnt[WhiteKnight+side])
7577                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7578                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7579                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7580                 if(nBishops)
7581                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7582                 if(pCnt[WhiteAlfil+side])
7583                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7584                 if(pCnt[WhiteWazir+side])
7585                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7586         }
7587
7588         return TRUE;
7589 }
7590
7591 int
7592 CompareWithRights (Board b1, Board b2)
7593 {
7594     int rights = 0;
7595     if(!CompareBoards(b1, b2)) return FALSE;
7596     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7597     /* compare castling rights */
7598     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7599            rights++; /* King lost rights, while rook still had them */
7600     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7601         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7602            rights++; /* but at least one rook lost them */
7603     }
7604     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7605            rights++;
7606     if( b1[CASTLING][5] != NoRights ) {
7607         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7608            rights++;
7609     }
7610     return rights == 0;
7611 }
7612
7613 int
7614 Adjudicate (ChessProgramState *cps)
7615 {       // [HGM] some adjudications useful with buggy engines
7616         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7617         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7618         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7619         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7620         int k, count = 0; static int bare = 1;
7621         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7622         Boolean canAdjudicate = !appData.icsActive;
7623
7624         // most tests only when we understand the game, i.e. legality-checking on
7625             if( appData.testLegality )
7626             {   /* [HGM] Some more adjudications for obstinate engines */
7627                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7628                 static int moveCount = 6;
7629                 ChessMove result;
7630                 char *reason = NULL;
7631
7632                 /* Count what is on board. */
7633                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7634
7635                 /* Some material-based adjudications that have to be made before stalemate test */
7636                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7637                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7638                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7639                      if(canAdjudicate && appData.checkMates) {
7640                          if(engineOpponent)
7641                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7642                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7643                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7644                          return 1;
7645                      }
7646                 }
7647
7648                 /* Bare King in Shatranj (loses) or Losers (wins) */
7649                 if( nrW == 1 || nrB == 1) {
7650                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7651                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7652                      if(canAdjudicate && appData.checkMates) {
7653                          if(engineOpponent)
7654                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7655                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7656                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7657                          return 1;
7658                      }
7659                   } else
7660                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7661                   {    /* bare King */
7662                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7663                         if(canAdjudicate && appData.checkMates) {
7664                             /* but only adjudicate if adjudication enabled */
7665                             if(engineOpponent)
7666                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7667                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7668                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7669                             return 1;
7670                         }
7671                   }
7672                 } else bare = 1;
7673
7674
7675             // don't wait for engine to announce game end if we can judge ourselves
7676             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7677               case MT_CHECK:
7678                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7679                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7680                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7681                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7682                             checkCnt++;
7683                         if(checkCnt >= 2) {
7684                             reason = "Xboard adjudication: 3rd check";
7685                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7686                             break;
7687                         }
7688                     }
7689                 }
7690               case MT_NONE:
7691               default:
7692                 break;
7693               case MT_STALEMATE:
7694               case MT_STAINMATE:
7695                 reason = "Xboard adjudication: Stalemate";
7696                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7697                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7698                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7699                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7700                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7701                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7702                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7703                                                                         EP_CHECKMATE : EP_WINS);
7704                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7705                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7706                 }
7707                 break;
7708               case MT_CHECKMATE:
7709                 reason = "Xboard adjudication: Checkmate";
7710                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7711                 break;
7712             }
7713
7714                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7715                     case EP_STALEMATE:
7716                         result = GameIsDrawn; break;
7717                     case EP_CHECKMATE:
7718                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7719                     case EP_WINS:
7720                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7721                     default:
7722                         result = EndOfFile;
7723                 }
7724                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7725                     if(engineOpponent)
7726                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7727                     GameEnds( result, reason, GE_XBOARD );
7728                     return 1;
7729                 }
7730
7731                 /* Next absolutely insufficient mating material. */
7732                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7733                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7734                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7735
7736                      /* always flag draws, for judging claims */
7737                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7738
7739                      if(canAdjudicate && appData.materialDraws) {
7740                          /* but only adjudicate them if adjudication enabled */
7741                          if(engineOpponent) {
7742                            SendToProgram("force\n", engineOpponent); // suppress reply
7743                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7744                          }
7745                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7746                          return 1;
7747                      }
7748                 }
7749
7750                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7751                 if(gameInfo.variant == VariantXiangqi ?
7752                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7753                  : nrW + nrB == 4 &&
7754                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7755                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7756                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7757                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7758                    ) ) {
7759                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7760                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7761                           if(engineOpponent) {
7762                             SendToProgram("force\n", engineOpponent); // suppress reply
7763                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7764                           }
7765                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7766                           return 1;
7767                      }
7768                 } else moveCount = 6;
7769             }
7770
7771         // Repetition draws and 50-move rule can be applied independently of legality testing
7772
7773                 /* Check for rep-draws */
7774                 count = 0;
7775                 for(k = forwardMostMove-2;
7776                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7777                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7778                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7779                     k-=2)
7780                 {   int rights=0;
7781                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7782                         /* compare castling rights */
7783                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7784                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7785                                 rights++; /* King lost rights, while rook still had them */
7786                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7787                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7788                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7789                                    rights++; /* but at least one rook lost them */
7790                         }
7791                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7792                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7793                                 rights++;
7794                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7795                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7796                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7797                                    rights++;
7798                         }
7799                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7800                             && appData.drawRepeats > 1) {
7801                              /* adjudicate after user-specified nr of repeats */
7802                              int result = GameIsDrawn;
7803                              char *details = "XBoard adjudication: repetition draw";
7804                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7805                                 // [HGM] xiangqi: check for forbidden perpetuals
7806                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7807                                 for(m=forwardMostMove; m>k; m-=2) {
7808                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7809                                         ourPerpetual = 0; // the current mover did not always check
7810                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7811                                         hisPerpetual = 0; // the opponent did not always check
7812                                 }
7813                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7814                                                                         ourPerpetual, hisPerpetual);
7815                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7816                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7817                                     details = "Xboard adjudication: perpetual checking";
7818                                 } else
7819                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7820                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7821                                 } else
7822                                 // Now check for perpetual chases
7823                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7824                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7825                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7826                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7827                                         static char resdet[MSG_SIZ];
7828                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7829                                         details = resdet;
7830                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7831                                     } else
7832                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7833                                         break; // Abort repetition-checking loop.
7834                                 }
7835                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7836                              }
7837                              if(engineOpponent) {
7838                                SendToProgram("force\n", engineOpponent); // suppress reply
7839                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7840                              }
7841                              GameEnds( result, details, GE_XBOARD );
7842                              return 1;
7843                         }
7844                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7845                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7846                     }
7847                 }
7848
7849                 /* Now we test for 50-move draws. Determine ply count */
7850                 count = forwardMostMove;
7851                 /* look for last irreversble move */
7852                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7853                     count--;
7854                 /* if we hit starting position, add initial plies */
7855                 if( count == backwardMostMove )
7856                     count -= initialRulePlies;
7857                 count = forwardMostMove - count;
7858                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7859                         // adjust reversible move counter for checks in Xiangqi
7860                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7861                         if(i < backwardMostMove) i = backwardMostMove;
7862                         while(i <= forwardMostMove) {
7863                                 lastCheck = inCheck; // check evasion does not count
7864                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7865                                 if(inCheck || lastCheck) count--; // check does not count
7866                                 i++;
7867                         }
7868                 }
7869                 if( count >= 100)
7870                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7871                          /* this is used to judge if draw claims are legal */
7872                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7873                          if(engineOpponent) {
7874                            SendToProgram("force\n", engineOpponent); // suppress reply
7875                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7876                          }
7877                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7878                          return 1;
7879                 }
7880
7881                 /* if draw offer is pending, treat it as a draw claim
7882                  * when draw condition present, to allow engines a way to
7883                  * claim draws before making their move to avoid a race
7884                  * condition occurring after their move
7885                  */
7886                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7887                          char *p = NULL;
7888                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7889                              p = "Draw claim: 50-move rule";
7890                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7891                              p = "Draw claim: 3-fold repetition";
7892                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7893                              p = "Draw claim: insufficient mating material";
7894                          if( p != NULL && canAdjudicate) {
7895                              if(engineOpponent) {
7896                                SendToProgram("force\n", engineOpponent); // suppress reply
7897                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7898                              }
7899                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7900                              return 1;
7901                          }
7902                 }
7903
7904                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7905                     if(engineOpponent) {
7906                       SendToProgram("force\n", engineOpponent); // suppress reply
7907                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7908                     }
7909                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7910                     return 1;
7911                 }
7912         return 0;
7913 }
7914
7915 char *
7916 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7917 {   // [HGM] book: this routine intercepts moves to simulate book replies
7918     char *bookHit = NULL;
7919
7920     //first determine if the incoming move brings opponent into his book
7921     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7922         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7923     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7924     if(bookHit != NULL && !cps->bookSuspend) {
7925         // make sure opponent is not going to reply after receiving move to book position
7926         SendToProgram("force\n", cps);
7927         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7928     }
7929     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7930     // now arrange restart after book miss
7931     if(bookHit) {
7932         // after a book hit we never send 'go', and the code after the call to this routine
7933         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7934         char buf[MSG_SIZ], *move = bookHit;
7935         if(cps->useSAN) {
7936             int fromX, fromY, toX, toY;
7937             char promoChar;
7938             ChessMove moveType;
7939             move = buf + 30;
7940             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7941                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7942                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7943                                     PosFlags(forwardMostMove),
7944                                     fromY, fromX, toY, toX, promoChar, move);
7945             } else {
7946                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7947                 bookHit = NULL;
7948             }
7949         }
7950         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7951         SendToProgram(buf, cps);
7952         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7953     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7954         SendToProgram("go\n", cps);
7955         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7956     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7957         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7958             SendToProgram("go\n", cps);
7959         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7960     }
7961     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7962 }
7963
7964 int
7965 LoadError (char *errmess, ChessProgramState *cps)
7966 {   // unloads engine and switches back to -ncp mode if it was first
7967     if(cps->initDone) return FALSE;
7968     cps->isr = NULL; // this should suppress further error popups from breaking pipes
7969     DestroyChildProcess(cps->pr, 9 ); // just to be sure
7970     cps->pr = NoProc; 
7971     if(cps == &first) {
7972         appData.noChessProgram = TRUE;
7973         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
7974         gameMode = BeginningOfGame; ModeHighlight();
7975         SetNCPMode();
7976     }
7977     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
7978     DisplayMessage("", ""); // erase waiting message
7979     if(errmess) DisplayError(errmess, 0); // announce reason, if given
7980     return TRUE;
7981 }
7982
7983 char *savedMessage;
7984 ChessProgramState *savedState;
7985 void
7986 DeferredBookMove (void)
7987 {
7988         if(savedState->lastPing != savedState->lastPong)
7989                     ScheduleDelayedEvent(DeferredBookMove, 10);
7990         else
7991         HandleMachineMove(savedMessage, savedState);
7992 }
7993
7994 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7995
7996 void
7997 HandleMachineMove (char *message, ChessProgramState *cps)
7998 {
7999     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8000     char realname[MSG_SIZ];
8001     int fromX, fromY, toX, toY;
8002     ChessMove moveType;
8003     char promoChar;
8004     char *p, *pv=buf1;
8005     int machineWhite, oldError;
8006     char *bookHit;
8007
8008     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8009         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8010         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8011             DisplayError(_("Invalid pairing from pairing engine"), 0);
8012             return;
8013         }
8014         pairingReceived = 1;
8015         NextMatchGame();
8016         return; // Skim the pairing messages here.
8017     }
8018
8019     oldError = cps->userError; cps->userError = 0;
8020
8021 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8022     /*
8023      * Kludge to ignore BEL characters
8024      */
8025     while (*message == '\007') message++;
8026
8027     /*
8028      * [HGM] engine debug message: ignore lines starting with '#' character
8029      */
8030     if(cps->debug && *message == '#') return;
8031
8032     /*
8033      * Look for book output
8034      */
8035     if (cps == &first && bookRequested) {
8036         if (message[0] == '\t' || message[0] == ' ') {
8037             /* Part of the book output is here; append it */
8038             strcat(bookOutput, message);
8039             strcat(bookOutput, "  \n");
8040             return;
8041         } else if (bookOutput[0] != NULLCHAR) {
8042             /* All of book output has arrived; display it */
8043             char *p = bookOutput;
8044             while (*p != NULLCHAR) {
8045                 if (*p == '\t') *p = ' ';
8046                 p++;
8047             }
8048             DisplayInformation(bookOutput);
8049             bookRequested = FALSE;
8050             /* Fall through to parse the current output */
8051         }
8052     }
8053
8054     /*
8055      * Look for machine move.
8056      */
8057     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8058         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8059     {
8060         /* This method is only useful on engines that support ping */
8061         if (cps->lastPing != cps->lastPong) {
8062           if (gameMode == BeginningOfGame) {
8063             /* Extra move from before last new; ignore */
8064             if (appData.debugMode) {
8065                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8066             }
8067           } else {
8068             if (appData.debugMode) {
8069                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8070                         cps->which, gameMode);
8071             }
8072
8073             SendToProgram("undo\n", cps);
8074           }
8075           return;
8076         }
8077
8078         switch (gameMode) {
8079           case BeginningOfGame:
8080             /* Extra move from before last reset; ignore */
8081             if (appData.debugMode) {
8082                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8083             }
8084             return;
8085
8086           case EndOfGame:
8087           case IcsIdle:
8088           default:
8089             /* Extra move after we tried to stop.  The mode test is
8090                not a reliable way of detecting this problem, but it's
8091                the best we can do on engines that don't support ping.
8092             */
8093             if (appData.debugMode) {
8094                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8095                         cps->which, gameMode);
8096             }
8097             SendToProgram("undo\n", cps);
8098             return;
8099
8100           case MachinePlaysWhite:
8101           case IcsPlayingWhite:
8102             machineWhite = TRUE;
8103             break;
8104
8105           case MachinePlaysBlack:
8106           case IcsPlayingBlack:
8107             machineWhite = FALSE;
8108             break;
8109
8110           case TwoMachinesPlay:
8111             machineWhite = (cps->twoMachinesColor[0] == 'w');
8112             break;
8113         }
8114         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8115             if (appData.debugMode) {
8116                 fprintf(debugFP,
8117                         "Ignoring move out of turn by %s, gameMode %d"
8118                         ", forwardMost %d\n",
8119                         cps->which, gameMode, forwardMostMove);
8120             }
8121             return;
8122         }
8123
8124         if(cps->alphaRank) AlphaRank(machineMove, 4);
8125         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8126                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8127             /* Machine move could not be parsed; ignore it. */
8128           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8129                     machineMove, _(cps->which));
8130             DisplayError(buf1, 0);
8131             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8132                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8133             if (gameMode == TwoMachinesPlay) {
8134               GameEnds(machineWhite ? BlackWins : WhiteWins,
8135                        buf1, GE_XBOARD);
8136             }
8137             return;
8138         }
8139
8140         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8141         /* So we have to redo legality test with true e.p. status here,  */
8142         /* to make sure an illegal e.p. capture does not slip through,   */
8143         /* to cause a forfeit on a justified illegal-move complaint      */
8144         /* of the opponent.                                              */
8145         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8146            ChessMove moveType;
8147            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8148                              fromY, fromX, toY, toX, promoChar);
8149             if(moveType == IllegalMove) {
8150               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8151                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8152                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8153                            buf1, GE_XBOARD);
8154                 return;
8155            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8156            /* [HGM] Kludge to handle engines that send FRC-style castling
8157               when they shouldn't (like TSCP-Gothic) */
8158            switch(moveType) {
8159              case WhiteASideCastleFR:
8160              case BlackASideCastleFR:
8161                toX+=2;
8162                currentMoveString[2]++;
8163                break;
8164              case WhiteHSideCastleFR:
8165              case BlackHSideCastleFR:
8166                toX--;
8167                currentMoveString[2]--;
8168                break;
8169              default: ; // nothing to do, but suppresses warning of pedantic compilers
8170            }
8171         }
8172         hintRequested = FALSE;
8173         lastHint[0] = NULLCHAR;
8174         bookRequested = FALSE;
8175         /* Program may be pondering now */
8176         cps->maybeThinking = TRUE;
8177         if (cps->sendTime == 2) cps->sendTime = 1;
8178         if (cps->offeredDraw) cps->offeredDraw--;
8179
8180         /* [AS] Save move info*/
8181         pvInfoList[ forwardMostMove ].score = programStats.score;
8182         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8183         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8184
8185         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8186
8187         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8188         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8189             int count = 0;
8190
8191             while( count < adjudicateLossPlies ) {
8192                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8193
8194                 if( count & 1 ) {
8195                     score = -score; /* Flip score for winning side */
8196                 }
8197
8198                 if( score > adjudicateLossThreshold ) {
8199                     break;
8200                 }
8201
8202                 count++;
8203             }
8204
8205             if( count >= adjudicateLossPlies ) {
8206                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8207
8208                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8209                     "Xboard adjudication",
8210                     GE_XBOARD );
8211
8212                 return;
8213             }
8214         }
8215
8216         if(Adjudicate(cps)) {
8217             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8218             return; // [HGM] adjudicate: for all automatic game ends
8219         }
8220
8221 #if ZIPPY
8222         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8223             first.initDone) {
8224           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8225                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8226                 SendToICS("draw ");
8227                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8228           }
8229           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8230           ics_user_moved = 1;
8231           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8232                 char buf[3*MSG_SIZ];
8233
8234                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8235                         programStats.score / 100.,
8236                         programStats.depth,
8237                         programStats.time / 100.,
8238                         (unsigned int)programStats.nodes,
8239                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8240                         programStats.movelist);
8241                 SendToICS(buf);
8242 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8243           }
8244         }
8245 #endif
8246
8247         /* [AS] Clear stats for next move */
8248         ClearProgramStats();
8249         thinkOutput[0] = NULLCHAR;
8250         hiddenThinkOutputState = 0;
8251
8252         bookHit = NULL;
8253         if (gameMode == TwoMachinesPlay) {
8254             /* [HGM] relaying draw offers moved to after reception of move */
8255             /* and interpreting offer as claim if it brings draw condition */
8256             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8257                 SendToProgram("draw\n", cps->other);
8258             }
8259             if (cps->other->sendTime) {
8260                 SendTimeRemaining(cps->other,
8261                                   cps->other->twoMachinesColor[0] == 'w');
8262             }
8263             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8264             if (firstMove && !bookHit) {
8265                 firstMove = FALSE;
8266                 if (cps->other->useColors) {
8267                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8268                 }
8269                 SendToProgram("go\n", cps->other);
8270             }
8271             cps->other->maybeThinking = TRUE;
8272         }
8273
8274         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8275
8276         if (!pausing && appData.ringBellAfterMoves) {
8277             RingBell();
8278         }
8279
8280         /*
8281          * Reenable menu items that were disabled while
8282          * machine was thinking
8283          */
8284         if (gameMode != TwoMachinesPlay)
8285             SetUserThinkingEnables();
8286
8287         // [HGM] book: after book hit opponent has received move and is now in force mode
8288         // force the book reply into it, and then fake that it outputted this move by jumping
8289         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8290         if(bookHit) {
8291                 static char bookMove[MSG_SIZ]; // a bit generous?
8292
8293                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8294                 strcat(bookMove, bookHit);
8295                 message = bookMove;
8296                 cps = cps->other;
8297                 programStats.nodes = programStats.depth = programStats.time =
8298                 programStats.score = programStats.got_only_move = 0;
8299                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8300
8301                 if(cps->lastPing != cps->lastPong) {
8302                     savedMessage = message; // args for deferred call
8303                     savedState = cps;
8304                     ScheduleDelayedEvent(DeferredBookMove, 10);
8305                     return;
8306                 }
8307                 goto FakeBookMove;
8308         }
8309
8310         return;
8311     }
8312
8313     /* Set special modes for chess engines.  Later something general
8314      *  could be added here; for now there is just one kludge feature,
8315      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8316      *  when "xboard" is given as an interactive command.
8317      */
8318     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8319         cps->useSigint = FALSE;
8320         cps->useSigterm = FALSE;
8321     }
8322     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8323       ParseFeatures(message+8, cps);
8324       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8325     }
8326
8327     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8328                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8329       int dummy, s=6; char buf[MSG_SIZ];
8330       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8331       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8332       if(startedFromSetupPosition) return;
8333       ParseFEN(boards[0], &dummy, message+s);
8334       DrawPosition(TRUE, boards[0]);
8335       startedFromSetupPosition = TRUE;
8336       return;
8337     }
8338     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8339      * want this, I was asked to put it in, and obliged.
8340      */
8341     if (!strncmp(message, "setboard ", 9)) {
8342         Board initial_position;
8343
8344         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8345
8346         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8347             DisplayError(_("Bad FEN received from engine"), 0);
8348             return ;
8349         } else {
8350            Reset(TRUE, FALSE);
8351            CopyBoard(boards[0], initial_position);
8352            initialRulePlies = FENrulePlies;
8353            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8354            else gameMode = MachinePlaysBlack;
8355            DrawPosition(FALSE, boards[currentMove]);
8356         }
8357         return;
8358     }
8359
8360     /*
8361      * Look for communication commands
8362      */
8363     if (!strncmp(message, "telluser ", 9)) {
8364         if(message[9] == '\\' && message[10] == '\\')
8365             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8366         PlayTellSound();
8367         DisplayNote(message + 9);
8368         return;
8369     }
8370     if (!strncmp(message, "tellusererror ", 14)) {
8371         cps->userError = 1;
8372         if(message[14] == '\\' && message[15] == '\\')
8373             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8374         PlayTellSound();
8375         DisplayError(message + 14, 0);
8376         return;
8377     }
8378     if (!strncmp(message, "tellopponent ", 13)) {
8379       if (appData.icsActive) {
8380         if (loggedOn) {
8381           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8382           SendToICS(buf1);
8383         }
8384       } else {
8385         DisplayNote(message + 13);
8386       }
8387       return;
8388     }
8389     if (!strncmp(message, "tellothers ", 11)) {
8390       if (appData.icsActive) {
8391         if (loggedOn) {
8392           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8393           SendToICS(buf1);
8394         }
8395       }
8396       return;
8397     }
8398     if (!strncmp(message, "tellall ", 8)) {
8399       if (appData.icsActive) {
8400         if (loggedOn) {
8401           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8402           SendToICS(buf1);
8403         }
8404       } else {
8405         DisplayNote(message + 8);
8406       }
8407       return;
8408     }
8409     if (strncmp(message, "warning", 7) == 0) {
8410         /* Undocumented feature, use tellusererror in new code */
8411         DisplayError(message, 0);
8412         return;
8413     }
8414     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8415         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8416         strcat(realname, " query");
8417         AskQuestion(realname, buf2, buf1, cps->pr);
8418         return;
8419     }
8420     /* Commands from the engine directly to ICS.  We don't allow these to be
8421      *  sent until we are logged on. Crafty kibitzes have been known to
8422      *  interfere with the login process.
8423      */
8424     if (loggedOn) {
8425         if (!strncmp(message, "tellics ", 8)) {
8426             SendToICS(message + 8);
8427             SendToICS("\n");
8428             return;
8429         }
8430         if (!strncmp(message, "tellicsnoalias ", 15)) {
8431             SendToICS(ics_prefix);
8432             SendToICS(message + 15);
8433             SendToICS("\n");
8434             return;
8435         }
8436         /* The following are for backward compatibility only */
8437         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8438             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8439             SendToICS(ics_prefix);
8440             SendToICS(message);
8441             SendToICS("\n");
8442             return;
8443         }
8444     }
8445     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8446         return;
8447     }
8448     /*
8449      * If the move is illegal, cancel it and redraw the board.
8450      * Also deal with other error cases.  Matching is rather loose
8451      * here to accommodate engines written before the spec.
8452      */
8453     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8454         strncmp(message, "Error", 5) == 0) {
8455         if (StrStr(message, "name") ||
8456             StrStr(message, "rating") || StrStr(message, "?") ||
8457             StrStr(message, "result") || StrStr(message, "board") ||
8458             StrStr(message, "bk") || StrStr(message, "computer") ||
8459             StrStr(message, "variant") || StrStr(message, "hint") ||
8460             StrStr(message, "random") || StrStr(message, "depth") ||
8461             StrStr(message, "accepted")) {
8462             return;
8463         }
8464         if (StrStr(message, "protover")) {
8465           /* Program is responding to input, so it's apparently done
8466              initializing, and this error message indicates it is
8467              protocol version 1.  So we don't need to wait any longer
8468              for it to initialize and send feature commands. */
8469           FeatureDone(cps, 1);
8470           cps->protocolVersion = 1;
8471           return;
8472         }
8473         cps->maybeThinking = FALSE;
8474
8475         if (StrStr(message, "draw")) {
8476             /* Program doesn't have "draw" command */
8477             cps->sendDrawOffers = 0;
8478             return;
8479         }
8480         if (cps->sendTime != 1 &&
8481             (StrStr(message, "time") || StrStr(message, "otim"))) {
8482           /* Program apparently doesn't have "time" or "otim" command */
8483           cps->sendTime = 0;
8484           return;
8485         }
8486         if (StrStr(message, "analyze")) {
8487             cps->analysisSupport = FALSE;
8488             cps->analyzing = FALSE;
8489 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8490             EditGameEvent(); // [HGM] try to preserve loaded game
8491             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8492             DisplayError(buf2, 0);
8493             return;
8494         }
8495         if (StrStr(message, "(no matching move)st")) {
8496           /* Special kludge for GNU Chess 4 only */
8497           cps->stKludge = TRUE;
8498           SendTimeControl(cps, movesPerSession, timeControl,
8499                           timeIncrement, appData.searchDepth,
8500                           searchTime);
8501           return;
8502         }
8503         if (StrStr(message, "(no matching move)sd")) {
8504           /* Special kludge for GNU Chess 4 only */
8505           cps->sdKludge = TRUE;
8506           SendTimeControl(cps, movesPerSession, timeControl,
8507                           timeIncrement, appData.searchDepth,
8508                           searchTime);
8509           return;
8510         }
8511         if (!StrStr(message, "llegal")) {
8512             return;
8513         }
8514         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8515             gameMode == IcsIdle) return;
8516         if (forwardMostMove <= backwardMostMove) return;
8517         if (pausing) PauseEvent();
8518       if(appData.forceIllegal) {
8519             // [HGM] illegal: machine refused move; force position after move into it
8520           SendToProgram("force\n", cps);
8521           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8522                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8523                 // when black is to move, while there might be nothing on a2 or black
8524                 // might already have the move. So send the board as if white has the move.
8525                 // But first we must change the stm of the engine, as it refused the last move
8526                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8527                 if(WhiteOnMove(forwardMostMove)) {
8528                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8529                     SendBoard(cps, forwardMostMove); // kludgeless board
8530                 } else {
8531                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8532                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8533                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8534                 }
8535           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8536             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8537                  gameMode == TwoMachinesPlay)
8538               SendToProgram("go\n", cps);
8539             return;
8540       } else
8541         if (gameMode == PlayFromGameFile) {
8542             /* Stop reading this game file */
8543             gameMode = EditGame;
8544             ModeHighlight();
8545         }
8546         /* [HGM] illegal-move claim should forfeit game when Xboard */
8547         /* only passes fully legal moves                            */
8548         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8549             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8550                                 "False illegal-move claim", GE_XBOARD );
8551             return; // do not take back move we tested as valid
8552         }
8553         currentMove = forwardMostMove-1;
8554         DisplayMove(currentMove-1); /* before DisplayMoveError */
8555         SwitchClocks(forwardMostMove-1); // [HGM] race
8556         DisplayBothClocks();
8557         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8558                 parseList[currentMove], _(cps->which));
8559         DisplayMoveError(buf1);
8560         DrawPosition(FALSE, boards[currentMove]);
8561
8562         SetUserThinkingEnables();
8563         return;
8564     }
8565     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8566         /* Program has a broken "time" command that
8567            outputs a string not ending in newline.
8568            Don't use it. */
8569         cps->sendTime = 0;
8570     }
8571
8572     /*
8573      * If chess program startup fails, exit with an error message.
8574      * Attempts to recover here are futile. [HGM] Well, we try anyway
8575      */
8576     if ((StrStr(message, "unknown host") != NULL)
8577         || (StrStr(message, "No remote directory") != NULL)
8578         || (StrStr(message, "not found") != NULL)
8579         || (StrStr(message, "No such file") != NULL)
8580         || (StrStr(message, "can't alloc") != NULL)
8581         || (StrStr(message, "Permission denied") != NULL)) {
8582
8583         cps->maybeThinking = FALSE;
8584         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8585                 _(cps->which), cps->program, cps->host, message);
8586         RemoveInputSource(cps->isr);
8587         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8588             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8589             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8590         }
8591         return;
8592     }
8593
8594     /*
8595      * Look for hint output
8596      */
8597     if (sscanf(message, "Hint: %s", buf1) == 1) {
8598         if (cps == &first && hintRequested) {
8599             hintRequested = FALSE;
8600             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8601                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8602                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8603                                     PosFlags(forwardMostMove),
8604                                     fromY, fromX, toY, toX, promoChar, buf1);
8605                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8606                 DisplayInformation(buf2);
8607             } else {
8608                 /* Hint move could not be parsed!? */
8609               snprintf(buf2, sizeof(buf2),
8610                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8611                         buf1, _(cps->which));
8612                 DisplayError(buf2, 0);
8613             }
8614         } else {
8615           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8616         }
8617         return;
8618     }
8619
8620     /*
8621      * Ignore other messages if game is not in progress
8622      */
8623     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8624         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8625
8626     /*
8627      * look for win, lose, draw, or draw offer
8628      */
8629     if (strncmp(message, "1-0", 3) == 0) {
8630         char *p, *q, *r = "";
8631         p = strchr(message, '{');
8632         if (p) {
8633             q = strchr(p, '}');
8634             if (q) {
8635                 *q = NULLCHAR;
8636                 r = p + 1;
8637             }
8638         }
8639         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8640         return;
8641     } else if (strncmp(message, "0-1", 3) == 0) {
8642         char *p, *q, *r = "";
8643         p = strchr(message, '{');
8644         if (p) {
8645             q = strchr(p, '}');
8646             if (q) {
8647                 *q = NULLCHAR;
8648                 r = p + 1;
8649             }
8650         }
8651         /* Kludge for Arasan 4.1 bug */
8652         if (strcmp(r, "Black resigns") == 0) {
8653             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8654             return;
8655         }
8656         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8657         return;
8658     } else if (strncmp(message, "1/2", 3) == 0) {
8659         char *p, *q, *r = "";
8660         p = strchr(message, '{');
8661         if (p) {
8662             q = strchr(p, '}');
8663             if (q) {
8664                 *q = NULLCHAR;
8665                 r = p + 1;
8666             }
8667         }
8668
8669         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8670         return;
8671
8672     } else if (strncmp(message, "White resign", 12) == 0) {
8673         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8674         return;
8675     } else if (strncmp(message, "Black resign", 12) == 0) {
8676         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8677         return;
8678     } else if (strncmp(message, "White matches", 13) == 0 ||
8679                strncmp(message, "Black matches", 13) == 0   ) {
8680         /* [HGM] ignore GNUShogi noises */
8681         return;
8682     } else if (strncmp(message, "White", 5) == 0 &&
8683                message[5] != '(' &&
8684                StrStr(message, "Black") == NULL) {
8685         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8686         return;
8687     } else if (strncmp(message, "Black", 5) == 0 &&
8688                message[5] != '(') {
8689         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8690         return;
8691     } else if (strcmp(message, "resign") == 0 ||
8692                strcmp(message, "computer resigns") == 0) {
8693         switch (gameMode) {
8694           case MachinePlaysBlack:
8695           case IcsPlayingBlack:
8696             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8697             break;
8698           case MachinePlaysWhite:
8699           case IcsPlayingWhite:
8700             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8701             break;
8702           case TwoMachinesPlay:
8703             if (cps->twoMachinesColor[0] == 'w')
8704               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8705             else
8706               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8707             break;
8708           default:
8709             /* can't happen */
8710             break;
8711         }
8712         return;
8713     } else if (strncmp(message, "opponent mates", 14) == 0) {
8714         switch (gameMode) {
8715           case MachinePlaysBlack:
8716           case IcsPlayingBlack:
8717             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8718             break;
8719           case MachinePlaysWhite:
8720           case IcsPlayingWhite:
8721             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8722             break;
8723           case TwoMachinesPlay:
8724             if (cps->twoMachinesColor[0] == 'w')
8725               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8726             else
8727               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8728             break;
8729           default:
8730             /* can't happen */
8731             break;
8732         }
8733         return;
8734     } else if (strncmp(message, "computer mates", 14) == 0) {
8735         switch (gameMode) {
8736           case MachinePlaysBlack:
8737           case IcsPlayingBlack:
8738             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8739             break;
8740           case MachinePlaysWhite:
8741           case IcsPlayingWhite:
8742             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8743             break;
8744           case TwoMachinesPlay:
8745             if (cps->twoMachinesColor[0] == 'w')
8746               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8747             else
8748               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8749             break;
8750           default:
8751             /* can't happen */
8752             break;
8753         }
8754         return;
8755     } else if (strncmp(message, "checkmate", 9) == 0) {
8756         if (WhiteOnMove(forwardMostMove)) {
8757             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8758         } else {
8759             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8760         }
8761         return;
8762     } else if (strstr(message, "Draw") != NULL ||
8763                strstr(message, "game is a draw") != NULL) {
8764         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8765         return;
8766     } else if (strstr(message, "offer") != NULL &&
8767                strstr(message, "draw") != NULL) {
8768 #if ZIPPY
8769         if (appData.zippyPlay && first.initDone) {
8770             /* Relay offer to ICS */
8771             SendToICS(ics_prefix);
8772             SendToICS("draw\n");
8773         }
8774 #endif
8775         cps->offeredDraw = 2; /* valid until this engine moves twice */
8776         if (gameMode == TwoMachinesPlay) {
8777             if (cps->other->offeredDraw) {
8778                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8779             /* [HGM] in two-machine mode we delay relaying draw offer      */
8780             /* until after we also have move, to see if it is really claim */
8781             }
8782         } else if (gameMode == MachinePlaysWhite ||
8783                    gameMode == MachinePlaysBlack) {
8784           if (userOfferedDraw) {
8785             DisplayInformation(_("Machine accepts your draw offer"));
8786             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8787           } else {
8788             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8789           }
8790         }
8791     }
8792
8793
8794     /*
8795      * Look for thinking output
8796      */
8797     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8798           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8799                                 ) {
8800         int plylev, mvleft, mvtot, curscore, time;
8801         char mvname[MOVE_LEN];
8802         u64 nodes; // [DM]
8803         char plyext;
8804         int ignore = FALSE;
8805         int prefixHint = FALSE;
8806         mvname[0] = NULLCHAR;
8807
8808         switch (gameMode) {
8809           case MachinePlaysBlack:
8810           case IcsPlayingBlack:
8811             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8812             break;
8813           case MachinePlaysWhite:
8814           case IcsPlayingWhite:
8815             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8816             break;
8817           case AnalyzeMode:
8818           case AnalyzeFile:
8819             break;
8820           case IcsObserving: /* [DM] icsEngineAnalyze */
8821             if (!appData.icsEngineAnalyze) ignore = TRUE;
8822             break;
8823           case TwoMachinesPlay:
8824             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8825                 ignore = TRUE;
8826             }
8827             break;
8828           default:
8829             ignore = TRUE;
8830             break;
8831         }
8832
8833         if (!ignore) {
8834             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8835             buf1[0] = NULLCHAR;
8836             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8837                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8838
8839                 if (plyext != ' ' && plyext != '\t') {
8840                     time *= 100;
8841                 }
8842
8843                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8844                 if( cps->scoreIsAbsolute &&
8845                     ( gameMode == MachinePlaysBlack ||
8846                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8847                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8848                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8849                      !WhiteOnMove(currentMove)
8850                     ) )
8851                 {
8852                     curscore = -curscore;
8853                 }
8854
8855                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8856
8857                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8858                         char buf[MSG_SIZ];
8859                         FILE *f;
8860                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8861                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8862                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8863                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8864                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8865                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8866                                 fclose(f);
8867                         } else DisplayError(_("failed writing PV"), 0);
8868                 }
8869
8870                 tempStats.depth = plylev;
8871                 tempStats.nodes = nodes;
8872                 tempStats.time = time;
8873                 tempStats.score = curscore;
8874                 tempStats.got_only_move = 0;
8875
8876                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8877                         int ticklen;
8878
8879                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8880                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8881                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8882                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8883                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8884                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8885                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8886                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8887                 }
8888
8889                 /* Buffer overflow protection */
8890                 if (pv[0] != NULLCHAR) {
8891                     if (strlen(pv) >= sizeof(tempStats.movelist)
8892                         && appData.debugMode) {
8893                         fprintf(debugFP,
8894                                 "PV is too long; using the first %u bytes.\n",
8895                                 (unsigned) sizeof(tempStats.movelist) - 1);
8896                     }
8897
8898                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8899                 } else {
8900                     sprintf(tempStats.movelist, " no PV\n");
8901                 }
8902
8903                 if (tempStats.seen_stat) {
8904                     tempStats.ok_to_send = 1;
8905                 }
8906
8907                 if (strchr(tempStats.movelist, '(') != NULL) {
8908                     tempStats.line_is_book = 1;
8909                     tempStats.nr_moves = 0;
8910                     tempStats.moves_left = 0;
8911                 } else {
8912                     tempStats.line_is_book = 0;
8913                 }
8914
8915                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8916                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8917
8918                 SendProgramStatsToFrontend( cps, &tempStats );
8919
8920                 /*
8921                     [AS] Protect the thinkOutput buffer from overflow... this
8922                     is only useful if buf1 hasn't overflowed first!
8923                 */
8924                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8925                          plylev,
8926                          (gameMode == TwoMachinesPlay ?
8927                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8928                          ((double) curscore) / 100.0,
8929                          prefixHint ? lastHint : "",
8930                          prefixHint ? " " : "" );
8931
8932                 if( buf1[0] != NULLCHAR ) {
8933                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8934
8935                     if( strlen(pv) > max_len ) {
8936                         if( appData.debugMode) {
8937                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8938                         }
8939                         pv[max_len+1] = '\0';
8940                     }
8941
8942                     strcat( thinkOutput, pv);
8943                 }
8944
8945                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8946                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8947                     DisplayMove(currentMove - 1);
8948                 }
8949                 return;
8950
8951             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8952                 /* crafty (9.25+) says "(only move) <move>"
8953                  * if there is only 1 legal move
8954                  */
8955                 sscanf(p, "(only move) %s", buf1);
8956                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8957                 sprintf(programStats.movelist, "%s (only move)", buf1);
8958                 programStats.depth = 1;
8959                 programStats.nr_moves = 1;
8960                 programStats.moves_left = 1;
8961                 programStats.nodes = 1;
8962                 programStats.time = 1;
8963                 programStats.got_only_move = 1;
8964
8965                 /* Not really, but we also use this member to
8966                    mean "line isn't going to change" (Crafty
8967                    isn't searching, so stats won't change) */
8968                 programStats.line_is_book = 1;
8969
8970                 SendProgramStatsToFrontend( cps, &programStats );
8971
8972                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8973                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8974                     DisplayMove(currentMove - 1);
8975                 }
8976                 return;
8977             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8978                               &time, &nodes, &plylev, &mvleft,
8979                               &mvtot, mvname) >= 5) {
8980                 /* The stat01: line is from Crafty (9.29+) in response
8981                    to the "." command */
8982                 programStats.seen_stat = 1;
8983                 cps->maybeThinking = TRUE;
8984
8985                 if (programStats.got_only_move || !appData.periodicUpdates)
8986                   return;
8987
8988                 programStats.depth = plylev;
8989                 programStats.time = time;
8990                 programStats.nodes = nodes;
8991                 programStats.moves_left = mvleft;
8992                 programStats.nr_moves = mvtot;
8993                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8994                 programStats.ok_to_send = 1;
8995                 programStats.movelist[0] = '\0';
8996
8997                 SendProgramStatsToFrontend( cps, &programStats );
8998
8999                 return;
9000
9001             } else if (strncmp(message,"++",2) == 0) {
9002                 /* Crafty 9.29+ outputs this */
9003                 programStats.got_fail = 2;
9004                 return;
9005
9006             } else if (strncmp(message,"--",2) == 0) {
9007                 /* Crafty 9.29+ outputs this */
9008                 programStats.got_fail = 1;
9009                 return;
9010
9011             } else if (thinkOutput[0] != NULLCHAR &&
9012                        strncmp(message, "    ", 4) == 0) {
9013                 unsigned message_len;
9014
9015                 p = message;
9016                 while (*p && *p == ' ') p++;
9017
9018                 message_len = strlen( p );
9019
9020                 /* [AS] Avoid buffer overflow */
9021                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9022                     strcat(thinkOutput, " ");
9023                     strcat(thinkOutput, p);
9024                 }
9025
9026                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9027                     strcat(programStats.movelist, " ");
9028                     strcat(programStats.movelist, p);
9029                 }
9030
9031                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9032                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9033                     DisplayMove(currentMove - 1);
9034                 }
9035                 return;
9036             }
9037         }
9038         else {
9039             buf1[0] = NULLCHAR;
9040
9041             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9042                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9043             {
9044                 ChessProgramStats cpstats;
9045
9046                 if (plyext != ' ' && plyext != '\t') {
9047                     time *= 100;
9048                 }
9049
9050                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9051                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9052                     curscore = -curscore;
9053                 }
9054
9055                 cpstats.depth = plylev;
9056                 cpstats.nodes = nodes;
9057                 cpstats.time = time;
9058                 cpstats.score = curscore;
9059                 cpstats.got_only_move = 0;
9060                 cpstats.movelist[0] = '\0';
9061
9062                 if (buf1[0] != NULLCHAR) {
9063                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9064                 }
9065
9066                 cpstats.ok_to_send = 0;
9067                 cpstats.line_is_book = 0;
9068                 cpstats.nr_moves = 0;
9069                 cpstats.moves_left = 0;
9070
9071                 SendProgramStatsToFrontend( cps, &cpstats );
9072             }
9073         }
9074     }
9075 }
9076
9077
9078 /* Parse a game score from the character string "game", and
9079    record it as the history of the current game.  The game
9080    score is NOT assumed to start from the standard position.
9081    The display is not updated in any way.
9082    */
9083 void
9084 ParseGameHistory (char *game)
9085 {
9086     ChessMove moveType;
9087     int fromX, fromY, toX, toY, boardIndex;
9088     char promoChar;
9089     char *p, *q;
9090     char buf[MSG_SIZ];
9091
9092     if (appData.debugMode)
9093       fprintf(debugFP, "Parsing game history: %s\n", game);
9094
9095     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9096     gameInfo.site = StrSave(appData.icsHost);
9097     gameInfo.date = PGNDate();
9098     gameInfo.round = StrSave("-");
9099
9100     /* Parse out names of players */
9101     while (*game == ' ') game++;
9102     p = buf;
9103     while (*game != ' ') *p++ = *game++;
9104     *p = NULLCHAR;
9105     gameInfo.white = StrSave(buf);
9106     while (*game == ' ') game++;
9107     p = buf;
9108     while (*game != ' ' && *game != '\n') *p++ = *game++;
9109     *p = NULLCHAR;
9110     gameInfo.black = StrSave(buf);
9111
9112     /* Parse moves */
9113     boardIndex = blackPlaysFirst ? 1 : 0;
9114     yynewstr(game);
9115     for (;;) {
9116         yyboardindex = boardIndex;
9117         moveType = (ChessMove) Myylex();
9118         switch (moveType) {
9119           case IllegalMove:             /* maybe suicide chess, etc. */
9120   if (appData.debugMode) {
9121     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9122     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9123     setbuf(debugFP, NULL);
9124   }
9125           case WhitePromotion:
9126           case BlackPromotion:
9127           case WhiteNonPromotion:
9128           case BlackNonPromotion:
9129           case NormalMove:
9130           case WhiteCapturesEnPassant:
9131           case BlackCapturesEnPassant:
9132           case WhiteKingSideCastle:
9133           case WhiteQueenSideCastle:
9134           case BlackKingSideCastle:
9135           case BlackQueenSideCastle:
9136           case WhiteKingSideCastleWild:
9137           case WhiteQueenSideCastleWild:
9138           case BlackKingSideCastleWild:
9139           case BlackQueenSideCastleWild:
9140           /* PUSH Fabien */
9141           case WhiteHSideCastleFR:
9142           case WhiteASideCastleFR:
9143           case BlackHSideCastleFR:
9144           case BlackASideCastleFR:
9145           /* POP Fabien */
9146             fromX = currentMoveString[0] - AAA;
9147             fromY = currentMoveString[1] - ONE;
9148             toX = currentMoveString[2] - AAA;
9149             toY = currentMoveString[3] - ONE;
9150             promoChar = currentMoveString[4];
9151             break;
9152           case WhiteDrop:
9153           case BlackDrop:
9154             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9155             fromX = moveType == WhiteDrop ?
9156               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9157             (int) CharToPiece(ToLower(currentMoveString[0]));
9158             fromY = DROP_RANK;
9159             toX = currentMoveString[2] - AAA;
9160             toY = currentMoveString[3] - ONE;
9161             promoChar = NULLCHAR;
9162             break;
9163           case AmbiguousMove:
9164             /* bug? */
9165             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9166   if (appData.debugMode) {
9167     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9168     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9169     setbuf(debugFP, NULL);
9170   }
9171             DisplayError(buf, 0);
9172             return;
9173           case ImpossibleMove:
9174             /* bug? */
9175             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9176   if (appData.debugMode) {
9177     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9178     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9179     setbuf(debugFP, NULL);
9180   }
9181             DisplayError(buf, 0);
9182             return;
9183           case EndOfFile:
9184             if (boardIndex < backwardMostMove) {
9185                 /* Oops, gap.  How did that happen? */
9186                 DisplayError(_("Gap in move list"), 0);
9187                 return;
9188             }
9189             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9190             if (boardIndex > forwardMostMove) {
9191                 forwardMostMove = boardIndex;
9192             }
9193             return;
9194           case ElapsedTime:
9195             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9196                 strcat(parseList[boardIndex-1], " ");
9197                 strcat(parseList[boardIndex-1], yy_text);
9198             }
9199             continue;
9200           case Comment:
9201           case PGNTag:
9202           case NAG:
9203           default:
9204             /* ignore */
9205             continue;
9206           case WhiteWins:
9207           case BlackWins:
9208           case GameIsDrawn:
9209           case GameUnfinished:
9210             if (gameMode == IcsExamining) {
9211                 if (boardIndex < backwardMostMove) {
9212                     /* Oops, gap.  How did that happen? */
9213                     return;
9214                 }
9215                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9216                 return;
9217             }
9218             gameInfo.result = moveType;
9219             p = strchr(yy_text, '{');
9220             if (p == NULL) p = strchr(yy_text, '(');
9221             if (p == NULL) {
9222                 p = yy_text;
9223                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9224             } else {
9225                 q = strchr(p, *p == '{' ? '}' : ')');
9226                 if (q != NULL) *q = NULLCHAR;
9227                 p++;
9228             }
9229             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9230             gameInfo.resultDetails = StrSave(p);
9231             continue;
9232         }
9233         if (boardIndex >= forwardMostMove &&
9234             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9235             backwardMostMove = blackPlaysFirst ? 1 : 0;
9236             return;
9237         }
9238         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9239                                  fromY, fromX, toY, toX, promoChar,
9240                                  parseList[boardIndex]);
9241         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9242         /* currentMoveString is set as a side-effect of yylex */
9243         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9244         strcat(moveList[boardIndex], "\n");
9245         boardIndex++;
9246         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9247         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9248           case MT_NONE:
9249           case MT_STALEMATE:
9250           default:
9251             break;
9252           case MT_CHECK:
9253             if(gameInfo.variant != VariantShogi)
9254                 strcat(parseList[boardIndex - 1], "+");
9255             break;
9256           case MT_CHECKMATE:
9257           case MT_STAINMATE:
9258             strcat(parseList[boardIndex - 1], "#");
9259             break;
9260         }
9261     }
9262 }
9263
9264
9265 /* Apply a move to the given board  */
9266 void
9267 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9268 {
9269   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9270   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9271
9272     /* [HGM] compute & store e.p. status and castling rights for new position */
9273     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9274
9275       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9276       oldEP = (signed char)board[EP_STATUS];
9277       board[EP_STATUS] = EP_NONE;
9278
9279   if (fromY == DROP_RANK) {
9280         /* must be first */
9281         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9282             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9283             return;
9284         }
9285         piece = board[toY][toX] = (ChessSquare) fromX;
9286   } else {
9287       int i;
9288
9289       if( board[toY][toX] != EmptySquare )
9290            board[EP_STATUS] = EP_CAPTURE;
9291
9292       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9293            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9294                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9295       } else
9296       if( board[fromY][fromX] == WhitePawn ) {
9297            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9298                board[EP_STATUS] = EP_PAWN_MOVE;
9299            if( toY-fromY==2) {
9300                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9301                         gameInfo.variant != VariantBerolina || toX < fromX)
9302                       board[EP_STATUS] = toX | berolina;
9303                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9304                         gameInfo.variant != VariantBerolina || toX > fromX)
9305                       board[EP_STATUS] = toX;
9306            }
9307       } else
9308       if( board[fromY][fromX] == BlackPawn ) {
9309            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9310                board[EP_STATUS] = EP_PAWN_MOVE;
9311            if( toY-fromY== -2) {
9312                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9313                         gameInfo.variant != VariantBerolina || toX < fromX)
9314                       board[EP_STATUS] = toX | berolina;
9315                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9316                         gameInfo.variant != VariantBerolina || toX > fromX)
9317                       board[EP_STATUS] = toX;
9318            }
9319        }
9320
9321        for(i=0; i<nrCastlingRights; i++) {
9322            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9323               board[CASTLING][i] == toX   && castlingRank[i] == toY
9324              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9325        }
9326
9327        if(gameInfo.variant == VariantSChess) { // update virginity
9328            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9329            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9330            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9331            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9332        }
9333
9334      if (fromX == toX && fromY == toY) return;
9335
9336      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9337      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9338      if(gameInfo.variant == VariantKnightmate)
9339          king += (int) WhiteUnicorn - (int) WhiteKing;
9340
9341     /* Code added by Tord: */
9342     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9343     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9344         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9345       board[fromY][fromX] = EmptySquare;
9346       board[toY][toX] = EmptySquare;
9347       if((toX > fromX) != (piece == WhiteRook)) {
9348         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9349       } else {
9350         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9351       }
9352     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9353                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9354       board[fromY][fromX] = EmptySquare;
9355       board[toY][toX] = EmptySquare;
9356       if((toX > fromX) != (piece == BlackRook)) {
9357         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9358       } else {
9359         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9360       }
9361     /* End of code added by Tord */
9362
9363     } else if (board[fromY][fromX] == king
9364         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9365         && toY == fromY && toX > fromX+1) {
9366         board[fromY][fromX] = EmptySquare;
9367         board[toY][toX] = king;
9368         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9369         board[fromY][BOARD_RGHT-1] = EmptySquare;
9370     } else if (board[fromY][fromX] == king
9371         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9372                && toY == fromY && toX < fromX-1) {
9373         board[fromY][fromX] = EmptySquare;
9374         board[toY][toX] = king;
9375         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9376         board[fromY][BOARD_LEFT] = EmptySquare;
9377     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9378                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9379                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9380                ) {
9381         /* white pawn promotion */
9382         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9383         if(gameInfo.variant==VariantBughouse ||
9384            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9385             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9386         board[fromY][fromX] = EmptySquare;
9387     } else if ((fromY >= BOARD_HEIGHT>>1)
9388                && (toX != fromX)
9389                && gameInfo.variant != VariantXiangqi
9390                && gameInfo.variant != VariantBerolina
9391                && (board[fromY][fromX] == WhitePawn)
9392                && (board[toY][toX] == EmptySquare)) {
9393         board[fromY][fromX] = EmptySquare;
9394         board[toY][toX] = WhitePawn;
9395         captured = board[toY - 1][toX];
9396         board[toY - 1][toX] = EmptySquare;
9397     } else if ((fromY == BOARD_HEIGHT-4)
9398                && (toX == fromX)
9399                && gameInfo.variant == VariantBerolina
9400                && (board[fromY][fromX] == WhitePawn)
9401                && (board[toY][toX] == EmptySquare)) {
9402         board[fromY][fromX] = EmptySquare;
9403         board[toY][toX] = WhitePawn;
9404         if(oldEP & EP_BEROLIN_A) {
9405                 captured = board[fromY][fromX-1];
9406                 board[fromY][fromX-1] = EmptySquare;
9407         }else{  captured = board[fromY][fromX+1];
9408                 board[fromY][fromX+1] = EmptySquare;
9409         }
9410     } else if (board[fromY][fromX] == king
9411         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9412                && toY == fromY && toX > fromX+1) {
9413         board[fromY][fromX] = EmptySquare;
9414         board[toY][toX] = king;
9415         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9416         board[fromY][BOARD_RGHT-1] = EmptySquare;
9417     } else if (board[fromY][fromX] == king
9418         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9419                && toY == fromY && toX < fromX-1) {
9420         board[fromY][fromX] = EmptySquare;
9421         board[toY][toX] = king;
9422         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9423         board[fromY][BOARD_LEFT] = EmptySquare;
9424     } else if (fromY == 7 && fromX == 3
9425                && board[fromY][fromX] == BlackKing
9426                && toY == 7 && toX == 5) {
9427         board[fromY][fromX] = EmptySquare;
9428         board[toY][toX] = BlackKing;
9429         board[fromY][7] = EmptySquare;
9430         board[toY][4] = BlackRook;
9431     } else if (fromY == 7 && fromX == 3
9432                && board[fromY][fromX] == BlackKing
9433                && toY == 7 && toX == 1) {
9434         board[fromY][fromX] = EmptySquare;
9435         board[toY][toX] = BlackKing;
9436         board[fromY][0] = EmptySquare;
9437         board[toY][2] = BlackRook;
9438     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9439                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9440                && toY < promoRank && promoChar
9441                ) {
9442         /* black pawn promotion */
9443         board[toY][toX] = CharToPiece(ToLower(promoChar));
9444         if(gameInfo.variant==VariantBughouse ||
9445            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9446             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9447         board[fromY][fromX] = EmptySquare;
9448     } else if ((fromY < BOARD_HEIGHT>>1)
9449                && (toX != fromX)
9450                && gameInfo.variant != VariantXiangqi
9451                && gameInfo.variant != VariantBerolina
9452                && (board[fromY][fromX] == BlackPawn)
9453                && (board[toY][toX] == EmptySquare)) {
9454         board[fromY][fromX] = EmptySquare;
9455         board[toY][toX] = BlackPawn;
9456         captured = board[toY + 1][toX];
9457         board[toY + 1][toX] = EmptySquare;
9458     } else if ((fromY == 3)
9459                && (toX == fromX)
9460                && gameInfo.variant == VariantBerolina
9461                && (board[fromY][fromX] == BlackPawn)
9462                && (board[toY][toX] == EmptySquare)) {
9463         board[fromY][fromX] = EmptySquare;
9464         board[toY][toX] = BlackPawn;
9465         if(oldEP & EP_BEROLIN_A) {
9466                 captured = board[fromY][fromX-1];
9467                 board[fromY][fromX-1] = EmptySquare;
9468         }else{  captured = board[fromY][fromX+1];
9469                 board[fromY][fromX+1] = EmptySquare;
9470         }
9471     } else {
9472         board[toY][toX] = board[fromY][fromX];
9473         board[fromY][fromX] = EmptySquare;
9474     }
9475   }
9476
9477     if (gameInfo.holdingsWidth != 0) {
9478
9479       /* !!A lot more code needs to be written to support holdings  */
9480       /* [HGM] OK, so I have written it. Holdings are stored in the */
9481       /* penultimate board files, so they are automaticlly stored   */
9482       /* in the game history.                                       */
9483       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9484                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9485         /* Delete from holdings, by decreasing count */
9486         /* and erasing image if necessary            */
9487         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9488         if(p < (int) BlackPawn) { /* white drop */
9489              p -= (int)WhitePawn;
9490                  p = PieceToNumber((ChessSquare)p);
9491              if(p >= gameInfo.holdingsSize) p = 0;
9492              if(--board[p][BOARD_WIDTH-2] <= 0)
9493                   board[p][BOARD_WIDTH-1] = EmptySquare;
9494              if((int)board[p][BOARD_WIDTH-2] < 0)
9495                         board[p][BOARD_WIDTH-2] = 0;
9496         } else {                  /* black drop */
9497              p -= (int)BlackPawn;
9498                  p = PieceToNumber((ChessSquare)p);
9499              if(p >= gameInfo.holdingsSize) p = 0;
9500              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9501                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9502              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9503                         board[BOARD_HEIGHT-1-p][1] = 0;
9504         }
9505       }
9506       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9507           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9508         /* [HGM] holdings: Add to holdings, if holdings exist */
9509         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9510                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9511                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9512         }
9513         p = (int) captured;
9514         if (p >= (int) BlackPawn) {
9515           p -= (int)BlackPawn;
9516           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9517                   /* in Shogi restore piece to its original  first */
9518                   captured = (ChessSquare) (DEMOTED captured);
9519                   p = DEMOTED p;
9520           }
9521           p = PieceToNumber((ChessSquare)p);
9522           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9523           board[p][BOARD_WIDTH-2]++;
9524           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9525         } else {
9526           p -= (int)WhitePawn;
9527           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9528                   captured = (ChessSquare) (DEMOTED captured);
9529                   p = DEMOTED p;
9530           }
9531           p = PieceToNumber((ChessSquare)p);
9532           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9533           board[BOARD_HEIGHT-1-p][1]++;
9534           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9535         }
9536       }
9537     } else if (gameInfo.variant == VariantAtomic) {
9538       if (captured != EmptySquare) {
9539         int y, x;
9540         for (y = toY-1; y <= toY+1; y++) {
9541           for (x = toX-1; x <= toX+1; x++) {
9542             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9543                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9544               board[y][x] = EmptySquare;
9545             }
9546           }
9547         }
9548         board[toY][toX] = EmptySquare;
9549       }
9550     }
9551     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9552         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9553     } else
9554     if(promoChar == '+') {
9555         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9556         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9557     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9558         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9559         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9560            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9561         board[toY][toX] = newPiece;
9562     }
9563     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9564                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9565         // [HGM] superchess: take promotion piece out of holdings
9566         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9567         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9568             if(!--board[k][BOARD_WIDTH-2])
9569                 board[k][BOARD_WIDTH-1] = EmptySquare;
9570         } else {
9571             if(!--board[BOARD_HEIGHT-1-k][1])
9572                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9573         }
9574     }
9575
9576 }
9577
9578 /* Updates forwardMostMove */
9579 void
9580 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9581 {
9582 //    forwardMostMove++; // [HGM] bare: moved downstream
9583
9584     (void) CoordsToAlgebraic(boards[forwardMostMove],
9585                              PosFlags(forwardMostMove),
9586                              fromY, fromX, toY, toX, promoChar,
9587                              parseList[forwardMostMove]);
9588
9589     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9590         int timeLeft; static int lastLoadFlag=0; int king, piece;
9591         piece = boards[forwardMostMove][fromY][fromX];
9592         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9593         if(gameInfo.variant == VariantKnightmate)
9594             king += (int) WhiteUnicorn - (int) WhiteKing;
9595         if(forwardMostMove == 0) {
9596             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9597                 fprintf(serverMoves, "%s;", UserName());
9598             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9599                 fprintf(serverMoves, "%s;", second.tidy);
9600             fprintf(serverMoves, "%s;", first.tidy);
9601             if(gameMode == MachinePlaysWhite)
9602                 fprintf(serverMoves, "%s;", UserName());
9603             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9604                 fprintf(serverMoves, "%s;", second.tidy);
9605         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9606         lastLoadFlag = loadFlag;
9607         // print base move
9608         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9609         // print castling suffix
9610         if( toY == fromY && piece == king ) {
9611             if(toX-fromX > 1)
9612                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9613             if(fromX-toX >1)
9614                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9615         }
9616         // e.p. suffix
9617         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9618              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9619              boards[forwardMostMove][toY][toX] == EmptySquare
9620              && fromX != toX && fromY != toY)
9621                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9622         // promotion suffix
9623         if(promoChar != NULLCHAR) {
9624             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9625                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9626                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9627             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9628         }
9629         if(!loadFlag) {
9630                 char buf[MOVE_LEN*2], *p; int len;
9631             fprintf(serverMoves, "/%d/%d",
9632                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9633             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9634             else                      timeLeft = blackTimeRemaining/1000;
9635             fprintf(serverMoves, "/%d", timeLeft);
9636                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9637                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9638                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9639                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9640             fprintf(serverMoves, "/%s", buf);
9641         }
9642         fflush(serverMoves);
9643     }
9644
9645     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9646         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9647       return;
9648     }
9649     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9650     if (commentList[forwardMostMove+1] != NULL) {
9651         free(commentList[forwardMostMove+1]);
9652         commentList[forwardMostMove+1] = NULL;
9653     }
9654     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9655     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9656     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9657     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9658     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9659     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9660     adjustedClock = FALSE;
9661     gameInfo.result = GameUnfinished;
9662     if (gameInfo.resultDetails != NULL) {
9663         free(gameInfo.resultDetails);
9664         gameInfo.resultDetails = NULL;
9665     }
9666     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9667                               moveList[forwardMostMove - 1]);
9668     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9669       case MT_NONE:
9670       case MT_STALEMATE:
9671       default:
9672         break;
9673       case MT_CHECK:
9674         if(gameInfo.variant != VariantShogi)
9675             strcat(parseList[forwardMostMove - 1], "+");
9676         break;
9677       case MT_CHECKMATE:
9678       case MT_STAINMATE:
9679         strcat(parseList[forwardMostMove - 1], "#");
9680         break;
9681     }
9682
9683 }
9684
9685 /* Updates currentMove if not pausing */
9686 void
9687 ShowMove (int fromX, int fromY, int toX, int toY)
9688 {
9689     int instant = (gameMode == PlayFromGameFile) ?
9690         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9691     if(appData.noGUI) return;
9692     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9693         if (!instant) {
9694             if (forwardMostMove == currentMove + 1) {
9695                 AnimateMove(boards[forwardMostMove - 1],
9696                             fromX, fromY, toX, toY);
9697             }
9698             if (appData.highlightLastMove) {
9699                 SetHighlights(fromX, fromY, toX, toY);
9700             }
9701         }
9702         currentMove = forwardMostMove;
9703     }
9704
9705     if (instant) return;
9706
9707     DisplayMove(currentMove - 1);
9708     DrawPosition(FALSE, boards[currentMove]);
9709     DisplayBothClocks();
9710     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9711 }
9712
9713 void
9714 SendEgtPath (ChessProgramState *cps)
9715 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9716         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9717
9718         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9719
9720         while(*p) {
9721             char c, *q = name+1, *r, *s;
9722
9723             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9724             while(*p && *p != ',') *q++ = *p++;
9725             *q++ = ':'; *q = 0;
9726             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9727                 strcmp(name, ",nalimov:") == 0 ) {
9728                 // take nalimov path from the menu-changeable option first, if it is defined
9729               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9730                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9731             } else
9732             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9733                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9734                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9735                 s = r = StrStr(s, ":") + 1; // beginning of path info
9736                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9737                 c = *r; *r = 0;             // temporarily null-terminate path info
9738                     *--q = 0;               // strip of trailig ':' from name
9739                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9740                 *r = c;
9741                 SendToProgram(buf,cps);     // send egtbpath command for this format
9742             }
9743             if(*p == ',') p++; // read away comma to position for next format name
9744         }
9745 }
9746
9747 void
9748 InitChessProgram (ChessProgramState *cps, int setup)
9749 /* setup needed to setup FRC opening position */
9750 {
9751     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9752     if (appData.noChessProgram) return;
9753     hintRequested = FALSE;
9754     bookRequested = FALSE;
9755
9756     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9757     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9758     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9759     if(cps->memSize) { /* [HGM] memory */
9760       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9761         SendToProgram(buf, cps);
9762     }
9763     SendEgtPath(cps); /* [HGM] EGT */
9764     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9765       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9766         SendToProgram(buf, cps);
9767     }
9768
9769     SendToProgram(cps->initString, cps);
9770     if (gameInfo.variant != VariantNormal &&
9771         gameInfo.variant != VariantLoadable
9772         /* [HGM] also send variant if board size non-standard */
9773         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9774                                             ) {
9775       char *v = VariantName(gameInfo.variant);
9776       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9777         /* [HGM] in protocol 1 we have to assume all variants valid */
9778         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9779         DisplayFatalError(buf, 0, 1);
9780         return;
9781       }
9782
9783       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9784       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9785       if( gameInfo.variant == VariantXiangqi )
9786            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9787       if( gameInfo.variant == VariantShogi )
9788            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9789       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9790            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9791       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9792           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9793            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9794       if( gameInfo.variant == VariantCourier )
9795            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9796       if( gameInfo.variant == VariantSuper )
9797            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9798       if( gameInfo.variant == VariantGreat )
9799            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9800       if( gameInfo.variant == VariantSChess )
9801            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9802       if( gameInfo.variant == VariantGrand )
9803            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9804
9805       if(overruled) {
9806         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9807                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9808            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9809            if(StrStr(cps->variants, b) == NULL) {
9810                // specific sized variant not known, check if general sizing allowed
9811                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9812                    if(StrStr(cps->variants, "boardsize") == NULL) {
9813                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9814                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9815                        DisplayFatalError(buf, 0, 1);
9816                        return;
9817                    }
9818                    /* [HGM] here we really should compare with the maximum supported board size */
9819                }
9820            }
9821       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9822       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9823       SendToProgram(buf, cps);
9824     }
9825     currentlyInitializedVariant = gameInfo.variant;
9826
9827     /* [HGM] send opening position in FRC to first engine */
9828     if(setup) {
9829           SendToProgram("force\n", cps);
9830           SendBoard(cps, 0);
9831           /* engine is now in force mode! Set flag to wake it up after first move. */
9832           setboardSpoiledMachineBlack = 1;
9833     }
9834
9835     if (cps->sendICS) {
9836       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9837       SendToProgram(buf, cps);
9838     }
9839     cps->maybeThinking = FALSE;
9840     cps->offeredDraw = 0;
9841     if (!appData.icsActive) {
9842         SendTimeControl(cps, movesPerSession, timeControl,
9843                         timeIncrement, appData.searchDepth,
9844                         searchTime);
9845     }
9846     if (appData.showThinking
9847         // [HGM] thinking: four options require thinking output to be sent
9848         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9849                                 ) {
9850         SendToProgram("post\n", cps);
9851     }
9852     SendToProgram("hard\n", cps);
9853     if (!appData.ponderNextMove) {
9854         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9855            it without being sure what state we are in first.  "hard"
9856            is not a toggle, so that one is OK.
9857          */
9858         SendToProgram("easy\n", cps);
9859     }
9860     if (cps->usePing) {
9861       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9862       SendToProgram(buf, cps);
9863     }
9864     cps->initDone = TRUE;
9865     ClearEngineOutputPane(cps == &second);
9866 }
9867
9868
9869 void
9870 StartChessProgram (ChessProgramState *cps)
9871 {
9872     char buf[MSG_SIZ];
9873     int err;
9874
9875     if (appData.noChessProgram) return;
9876     cps->initDone = FALSE;
9877
9878     if (strcmp(cps->host, "localhost") == 0) {
9879         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9880     } else if (*appData.remoteShell == NULLCHAR) {
9881         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9882     } else {
9883         if (*appData.remoteUser == NULLCHAR) {
9884           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9885                     cps->program);
9886         } else {
9887           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9888                     cps->host, appData.remoteUser, cps->program);
9889         }
9890         err = StartChildProcess(buf, "", &cps->pr);
9891     }
9892
9893     if (err != 0) {
9894       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9895         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9896         if(cps != &first) return;
9897         appData.noChessProgram = TRUE;
9898         ThawUI();
9899         SetNCPMode();
9900 //      DisplayFatalError(buf, err, 1);
9901 //      cps->pr = NoProc;
9902 //      cps->isr = NULL;
9903         return;
9904     }
9905
9906     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9907     if (cps->protocolVersion > 1) {
9908       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9909       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9910       cps->comboCnt = 0;  //                and values of combo boxes
9911       SendToProgram(buf, cps);
9912     } else {
9913       SendToProgram("xboard\n", cps);
9914     }
9915 }
9916
9917 void
9918 TwoMachinesEventIfReady P((void))
9919 {
9920   static int curMess = 0;
9921   if (first.lastPing != first.lastPong) {
9922     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9923     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9924     return;
9925   }
9926   if (second.lastPing != second.lastPong) {
9927     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9928     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9929     return;
9930   }
9931   DisplayMessage("", ""); curMess = 0;
9932   ThawUI();
9933   TwoMachinesEvent();
9934 }
9935
9936 char *
9937 MakeName (char *template)
9938 {
9939     time_t clock;
9940     struct tm *tm;
9941     static char buf[MSG_SIZ];
9942     char *p = buf;
9943     int i;
9944
9945     clock = time((time_t *)NULL);
9946     tm = localtime(&clock);
9947
9948     while(*p++ = *template++) if(p[-1] == '%') {
9949         switch(*template++) {
9950           case 0:   *p = 0; return buf;
9951           case 'Y': i = tm->tm_year+1900; break;
9952           case 'y': i = tm->tm_year-100; break;
9953           case 'M': i = tm->tm_mon+1; break;
9954           case 'd': i = tm->tm_mday; break;
9955           case 'h': i = tm->tm_hour; break;
9956           case 'm': i = tm->tm_min; break;
9957           case 's': i = tm->tm_sec; break;
9958           default:  i = 0;
9959         }
9960         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9961     }
9962     return buf;
9963 }
9964
9965 int
9966 CountPlayers (char *p)
9967 {
9968     int n = 0;
9969     while(p = strchr(p, '\n')) p++, n++; // count participants
9970     return n;
9971 }
9972
9973 FILE *
9974 WriteTourneyFile (char *results, FILE *f)
9975 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9976     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9977     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9978         // create a file with tournament description
9979         fprintf(f, "-participants {%s}\n", appData.participants);
9980         fprintf(f, "-seedBase %d\n", appData.seedBase);
9981         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9982         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9983         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9984         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9985         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9986         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9987         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9988         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9989         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9990         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9991         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9992         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9993         if(searchTime > 0)
9994                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9995         else {
9996                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9997                 fprintf(f, "-tc %s\n", appData.timeControl);
9998                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9999         }
10000         fprintf(f, "-results \"%s\"\n", results);
10001     }
10002     return f;
10003 }
10004
10005 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10006
10007 void
10008 Substitute (char *participants, int expunge)
10009 {
10010     int i, changed, changes=0, nPlayers=0;
10011     char *p, *q, *r, buf[MSG_SIZ];
10012     if(participants == NULL) return;
10013     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10014     r = p = participants; q = appData.participants;
10015     while(*p && *p == *q) {
10016         if(*p == '\n') r = p+1, nPlayers++;
10017         p++; q++;
10018     }
10019     if(*p) { // difference
10020         while(*p && *p++ != '\n');
10021         while(*q && *q++ != '\n');
10022       changed = nPlayers;
10023         changes = 1 + (strcmp(p, q) != 0);
10024     }
10025     if(changes == 1) { // a single engine mnemonic was changed
10026         q = r; while(*q) nPlayers += (*q++ == '\n');
10027         p = buf; while(*r && (*p = *r++) != '\n') p++;
10028         *p = NULLCHAR;
10029         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10030         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10031         if(mnemonic[i]) { // The substitute is valid
10032             FILE *f;
10033             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10034                 flock(fileno(f), LOCK_EX);
10035                 ParseArgsFromFile(f);
10036                 fseek(f, 0, SEEK_SET);
10037                 FREE(appData.participants); appData.participants = participants;
10038                 if(expunge) { // erase results of replaced engine
10039                     int len = strlen(appData.results), w, b, dummy;
10040                     for(i=0; i<len; i++) {
10041                         Pairing(i, nPlayers, &w, &b, &dummy);
10042                         if((w == changed || b == changed) && appData.results[i] == '*') {
10043                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10044                             fclose(f);
10045                             return;
10046                         }
10047                     }
10048                     for(i=0; i<len; i++) {
10049                         Pairing(i, nPlayers, &w, &b, &dummy);
10050                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10051                     }
10052                 }
10053                 WriteTourneyFile(appData.results, f);
10054                 fclose(f); // release lock
10055                 return;
10056             }
10057         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10058     }
10059     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10060     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10061     free(participants);
10062     return;
10063 }
10064
10065 int
10066 CheckPlayers (char *participants)
10067 {
10068         int i;
10069         char buf[MSG_SIZ], *p;
10070         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10071         while(p = strchr(participants, '\n')) {
10072             *p = NULLCHAR;
10073             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10074             if(!mnemonic[i]) {
10075                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10076                 *p = '\n';
10077                 DisplayError(buf, 0);
10078                 return 1;
10079             }
10080             *p = '\n';
10081             participants = p + 1;
10082         }
10083         return 0;
10084 }
10085
10086 int
10087 CreateTourney (char *name)
10088 {
10089         FILE *f;
10090         if(matchMode && strcmp(name, appData.tourneyFile)) {
10091              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10092         }
10093         if(name[0] == NULLCHAR) {
10094             if(appData.participants[0])
10095                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10096             return 0;
10097         }
10098         f = fopen(name, "r");
10099         if(f) { // file exists
10100             ASSIGN(appData.tourneyFile, name);
10101             ParseArgsFromFile(f); // parse it
10102         } else {
10103             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10104             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10105                 DisplayError(_("Not enough participants"), 0);
10106                 return 0;
10107             }
10108             if(CheckPlayers(appData.participants)) return 0;
10109             ASSIGN(appData.tourneyFile, name);
10110             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10111             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10112         }
10113         fclose(f);
10114         appData.noChessProgram = FALSE;
10115         appData.clockMode = TRUE;
10116         SetGNUMode();
10117         return 1;
10118 }
10119
10120 int
10121 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10122 {
10123     char buf[MSG_SIZ], *p, *q;
10124     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10125     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10126     skip = !all && group[0]; // if group requested, we start in skip mode
10127     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10128         p = names; q = buf; header = 0;
10129         while(*p && *p != '\n') *q++ = *p++;
10130         *q = 0;
10131         if(*p == '\n') p++;
10132         if(buf[0] == '#') {
10133             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10134             depth++; // we must be entering a new group
10135             if(all) continue; // suppress printing group headers when complete list requested
10136             header = 1;
10137             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10138         }
10139         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10140         if(engineList[i]) free(engineList[i]);
10141         engineList[i] = strdup(buf);
10142         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10143         if(engineMnemonic[i]) free(engineMnemonic[i]);
10144         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10145             strcat(buf, " (");
10146             sscanf(q + 8, "%s", buf + strlen(buf));
10147             strcat(buf, ")");
10148         }
10149         engineMnemonic[i] = strdup(buf);
10150         i++;
10151     }
10152     engineList[i] = engineMnemonic[i] = NULL;
10153     return i;
10154 }
10155
10156 // following implemented as macro to avoid type limitations
10157 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10158
10159 void
10160 SwapEngines (int n)
10161 {   // swap settings for first engine and other engine (so far only some selected options)
10162     int h;
10163     char *p;
10164     if(n == 0) return;
10165     SWAP(directory, p)
10166     SWAP(chessProgram, p)
10167     SWAP(isUCI, h)
10168     SWAP(hasOwnBookUCI, h)
10169     SWAP(protocolVersion, h)
10170     SWAP(reuse, h)
10171     SWAP(scoreIsAbsolute, h)
10172     SWAP(timeOdds, h)
10173     SWAP(logo, p)
10174     SWAP(pgnName, p)
10175     SWAP(pvSAN, h)
10176     SWAP(engOptions, p)
10177     SWAP(engInitString, p)
10178     SWAP(computerString, p)
10179     SWAP(features, p)
10180     SWAP(fenOverride, p)
10181     SWAP(NPS, h)
10182     SWAP(accumulateTC, h)
10183     SWAP(host, p)
10184 }
10185
10186 int
10187 GetEngineLine (char *s, int n)
10188 {
10189     int i;
10190     char buf[MSG_SIZ];
10191     extern char *icsNames;
10192     if(!s || !*s) return 0;
10193     NamesToList(n == 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10194     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10195     if(!mnemonic[i]) return 0;
10196     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10197     if(n == 1) SwapEngines(n);
10198     ParseArgsFromString(buf);
10199     if(n == 1) SwapEngines(n);
10200     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10201         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10202         ParseArgsFromString(buf);
10203     }
10204     return 1;
10205 }
10206
10207 int
10208 SetPlayer (int player, char *p)
10209 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10210     int i;
10211     char buf[MSG_SIZ], *engineName;
10212     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10213     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10214     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10215     if(mnemonic[i]) {
10216         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10217         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10218         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10219         ParseArgsFromString(buf);
10220     }
10221     free(engineName);
10222     return i;
10223 }
10224
10225 char *recentEngines;
10226
10227 void
10228 RecentEngineEvent (int nr)
10229 {
10230     int n;
10231 //    SwapEngines(1); // bump first to second
10232 //    ReplaceEngine(&second, 1); // and load it there
10233     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10234     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10235     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10236         ReplaceEngine(&first, 0);
10237         FloatToFront(&appData.recentEngineList, command[n]);
10238     }
10239 }
10240
10241 int
10242 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10243 {   // determine players from game number
10244     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10245
10246     if(appData.tourneyType == 0) {
10247         roundsPerCycle = (nPlayers - 1) | 1;
10248         pairingsPerRound = nPlayers / 2;
10249     } else if(appData.tourneyType > 0) {
10250         roundsPerCycle = nPlayers - appData.tourneyType;
10251         pairingsPerRound = appData.tourneyType;
10252     }
10253     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10254     gamesPerCycle = gamesPerRound * roundsPerCycle;
10255     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10256     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10257     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10258     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10259     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10260     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10261
10262     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10263     if(appData.roundSync) *syncInterval = gamesPerRound;
10264
10265     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10266
10267     if(appData.tourneyType == 0) {
10268         if(curPairing == (nPlayers-1)/2 ) {
10269             *whitePlayer = curRound;
10270             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10271         } else {
10272             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10273             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10274             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10275             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10276         }
10277     } else if(appData.tourneyType > 1) {
10278         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10279         *whitePlayer = curRound + appData.tourneyType;
10280     } else if(appData.tourneyType > 0) {
10281         *whitePlayer = curPairing;
10282         *blackPlayer = curRound + appData.tourneyType;
10283     }
10284
10285     // take care of white/black alternation per round. 
10286     // For cycles and games this is already taken care of by default, derived from matchGame!
10287     return curRound & 1;
10288 }
10289
10290 int
10291 NextTourneyGame (int nr, int *swapColors)
10292 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10293     char *p, *q;
10294     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10295     FILE *tf;
10296     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10297     tf = fopen(appData.tourneyFile, "r");
10298     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10299     ParseArgsFromFile(tf); fclose(tf);
10300     InitTimeControls(); // TC might be altered from tourney file
10301
10302     nPlayers = CountPlayers(appData.participants); // count participants
10303     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10304     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10305
10306     if(syncInterval) {
10307         p = q = appData.results;
10308         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10309         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10310             DisplayMessage(_("Waiting for other game(s)"),"");
10311             waitingForGame = TRUE;
10312             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10313             return 0;
10314         }
10315         waitingForGame = FALSE;
10316     }
10317
10318     if(appData.tourneyType < 0) {
10319         if(nr>=0 && !pairingReceived) {
10320             char buf[1<<16];
10321             if(pairing.pr == NoProc) {
10322                 if(!appData.pairingEngine[0]) {
10323                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10324                     return 0;
10325                 }
10326                 StartChessProgram(&pairing); // starts the pairing engine
10327             }
10328             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10329             SendToProgram(buf, &pairing);
10330             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10331             SendToProgram(buf, &pairing);
10332             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10333         }
10334         pairingReceived = 0;                              // ... so we continue here 
10335         *swapColors = 0;
10336         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10337         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10338         matchGame = 1; roundNr = nr / syncInterval + 1;
10339     }
10340
10341     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10342
10343     // redefine engines, engine dir, etc.
10344     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10345     if(first.pr == NoProc) {
10346       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10347       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10348     }
10349     if(second.pr == NoProc) {
10350       SwapEngines(1);
10351       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10352       SwapEngines(1);         // and make that valid for second engine by swapping
10353       InitEngine(&second, 1);
10354     }
10355     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10356     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10357     return 1;
10358 }
10359
10360 void
10361 NextMatchGame ()
10362 {   // performs game initialization that does not invoke engines, and then tries to start the game
10363     int res, firstWhite, swapColors = 0;
10364     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10365     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
10366         char buf[MSG_SIZ];
10367         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10368         if(strcmp(buf, currentDebugFile)) { // name has changed
10369             FILE *f = fopen(buf, "w");
10370             if(f) { // if opening the new file failed, just keep using the old one
10371                 ASSIGN(currentDebugFile, buf);
10372                 fclose(debugFP);
10373                 debugFP = f;
10374             }
10375             if(appData.serverFileName) {
10376                 if(serverFP) fclose(serverFP);
10377                 serverFP = fopen(appData.serverFileName, "w");
10378                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10379                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10380             }
10381         }
10382     }
10383     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10384     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10385     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10386     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10387     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10388     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10389     Reset(FALSE, first.pr != NoProc);
10390     res = LoadGameOrPosition(matchGame); // setup game
10391     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10392     if(!res) return; // abort when bad game/pos file
10393     TwoMachinesEvent();
10394 }
10395
10396 void
10397 UserAdjudicationEvent (int result)
10398 {
10399     ChessMove gameResult = GameIsDrawn;
10400
10401     if( result > 0 ) {
10402         gameResult = WhiteWins;
10403     }
10404     else if( result < 0 ) {
10405         gameResult = BlackWins;
10406     }
10407
10408     if( gameMode == TwoMachinesPlay ) {
10409         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10410     }
10411 }
10412
10413
10414 // [HGM] save: calculate checksum of game to make games easily identifiable
10415 int
10416 StringCheckSum (char *s)
10417 {
10418         int i = 0;
10419         if(s==NULL) return 0;
10420         while(*s) i = i*259 + *s++;
10421         return i;
10422 }
10423
10424 int
10425 GameCheckSum ()
10426 {
10427         int i, sum=0;
10428         for(i=backwardMostMove; i<forwardMostMove; i++) {
10429                 sum += pvInfoList[i].depth;
10430                 sum += StringCheckSum(parseList[i]);
10431                 sum += StringCheckSum(commentList[i]);
10432                 sum *= 261;
10433         }
10434         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10435         return sum + StringCheckSum(commentList[i]);
10436 } // end of save patch
10437
10438 void
10439 GameEnds (ChessMove result, char *resultDetails, int whosays)
10440 {
10441     GameMode nextGameMode;
10442     int isIcsGame;
10443     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10444
10445     if(endingGame) return; /* [HGM] crash: forbid recursion */
10446     endingGame = 1;
10447     if(twoBoards) { // [HGM] dual: switch back to one board
10448         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10449         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10450     }
10451     if (appData.debugMode) {
10452       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10453               result, resultDetails ? resultDetails : "(null)", whosays);
10454     }
10455
10456     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10457
10458     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10459         /* If we are playing on ICS, the server decides when the
10460            game is over, but the engine can offer to draw, claim
10461            a draw, or resign.
10462          */
10463 #if ZIPPY
10464         if (appData.zippyPlay && first.initDone) {
10465             if (result == GameIsDrawn) {
10466                 /* In case draw still needs to be claimed */
10467                 SendToICS(ics_prefix);
10468                 SendToICS("draw\n");
10469             } else if (StrCaseStr(resultDetails, "resign")) {
10470                 SendToICS(ics_prefix);
10471                 SendToICS("resign\n");
10472             }
10473         }
10474 #endif
10475         endingGame = 0; /* [HGM] crash */
10476         return;
10477     }
10478
10479     /* If we're loading the game from a file, stop */
10480     if (whosays == GE_FILE) {
10481       (void) StopLoadGameTimer();
10482       gameFileFP = NULL;
10483     }
10484
10485     /* Cancel draw offers */
10486     first.offeredDraw = second.offeredDraw = 0;
10487
10488     /* If this is an ICS game, only ICS can really say it's done;
10489        if not, anyone can. */
10490     isIcsGame = (gameMode == IcsPlayingWhite ||
10491                  gameMode == IcsPlayingBlack ||
10492                  gameMode == IcsObserving    ||
10493                  gameMode == IcsExamining);
10494
10495     if (!isIcsGame || whosays == GE_ICS) {
10496         /* OK -- not an ICS game, or ICS said it was done */
10497         StopClocks();
10498         if (!isIcsGame && !appData.noChessProgram)
10499           SetUserThinkingEnables();
10500
10501         /* [HGM] if a machine claims the game end we verify this claim */
10502         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10503             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10504                 char claimer;
10505                 ChessMove trueResult = (ChessMove) -1;
10506
10507                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10508                                             first.twoMachinesColor[0] :
10509                                             second.twoMachinesColor[0] ;
10510
10511                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10512                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10513                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10514                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10515                 } else
10516                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10517                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10518                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10519                 } else
10520                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10521                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10522                 }
10523
10524                 // now verify win claims, but not in drop games, as we don't understand those yet
10525                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10526                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10527                     (result == WhiteWins && claimer == 'w' ||
10528                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10529                       if (appData.debugMode) {
10530                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10531                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10532                       }
10533                       if(result != trueResult) {
10534                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10535                               result = claimer == 'w' ? BlackWins : WhiteWins;
10536                               resultDetails = buf;
10537                       }
10538                 } else
10539                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10540                     && (forwardMostMove <= backwardMostMove ||
10541                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10542                         (claimer=='b')==(forwardMostMove&1))
10543                                                                                   ) {
10544                       /* [HGM] verify: draws that were not flagged are false claims */
10545                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10546                       result = claimer == 'w' ? BlackWins : WhiteWins;
10547                       resultDetails = buf;
10548                 }
10549                 /* (Claiming a loss is accepted no questions asked!) */
10550             }
10551             /* [HGM] bare: don't allow bare King to win */
10552             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10553                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10554                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10555                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10556                && result != GameIsDrawn)
10557             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10558                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10559                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10560                         if(p >= 0 && p <= (int)WhiteKing) k++;
10561                 }
10562                 if (appData.debugMode) {
10563                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10564                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10565                 }
10566                 if(k <= 1) {
10567                         result = GameIsDrawn;
10568                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10569                         resultDetails = buf;
10570                 }
10571             }
10572         }
10573
10574
10575         if(serverMoves != NULL && !loadFlag) { char c = '=';
10576             if(result==WhiteWins) c = '+';
10577             if(result==BlackWins) c = '-';
10578             if(resultDetails != NULL)
10579                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10580         }
10581         if (resultDetails != NULL) {
10582             gameInfo.result = result;
10583             gameInfo.resultDetails = StrSave(resultDetails);
10584
10585             /* display last move only if game was not loaded from file */
10586             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10587                 DisplayMove(currentMove - 1);
10588
10589             if (forwardMostMove != 0) {
10590                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10591                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10592                                                                 ) {
10593                     if (*appData.saveGameFile != NULLCHAR) {
10594                         SaveGameToFile(appData.saveGameFile, TRUE);
10595                     } else if (appData.autoSaveGames) {
10596                         AutoSaveGame();
10597                     }
10598                     if (*appData.savePositionFile != NULLCHAR) {
10599                         SavePositionToFile(appData.savePositionFile);
10600                     }
10601                 }
10602             }
10603
10604             /* Tell program how game ended in case it is learning */
10605             /* [HGM] Moved this to after saving the PGN, just in case */
10606             /* engine died and we got here through time loss. In that */
10607             /* case we will get a fatal error writing the pipe, which */
10608             /* would otherwise lose us the PGN.                       */
10609             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10610             /* output during GameEnds should never be fatal anymore   */
10611             if (gameMode == MachinePlaysWhite ||
10612                 gameMode == MachinePlaysBlack ||
10613                 gameMode == TwoMachinesPlay ||
10614                 gameMode == IcsPlayingWhite ||
10615                 gameMode == IcsPlayingBlack ||
10616                 gameMode == BeginningOfGame) {
10617                 char buf[MSG_SIZ];
10618                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10619                         resultDetails);
10620                 if (first.pr != NoProc) {
10621                     SendToProgram(buf, &first);
10622                 }
10623                 if (second.pr != NoProc &&
10624                     gameMode == TwoMachinesPlay) {
10625                     SendToProgram(buf, &second);
10626                 }
10627             }
10628         }
10629
10630         if (appData.icsActive) {
10631             if (appData.quietPlay &&
10632                 (gameMode == IcsPlayingWhite ||
10633                  gameMode == IcsPlayingBlack)) {
10634                 SendToICS(ics_prefix);
10635                 SendToICS("set shout 1\n");
10636             }
10637             nextGameMode = IcsIdle;
10638             ics_user_moved = FALSE;
10639             /* clean up premove.  It's ugly when the game has ended and the
10640              * premove highlights are still on the board.
10641              */
10642             if (gotPremove) {
10643               gotPremove = FALSE;
10644               ClearPremoveHighlights();
10645               DrawPosition(FALSE, boards[currentMove]);
10646             }
10647             if (whosays == GE_ICS) {
10648                 switch (result) {
10649                 case WhiteWins:
10650                     if (gameMode == IcsPlayingWhite)
10651                         PlayIcsWinSound();
10652                     else if(gameMode == IcsPlayingBlack)
10653                         PlayIcsLossSound();
10654                     break;
10655                 case BlackWins:
10656                     if (gameMode == IcsPlayingBlack)
10657                         PlayIcsWinSound();
10658                     else if(gameMode == IcsPlayingWhite)
10659                         PlayIcsLossSound();
10660                     break;
10661                 case GameIsDrawn:
10662                     PlayIcsDrawSound();
10663                     break;
10664                 default:
10665                     PlayIcsUnfinishedSound();
10666                 }
10667             }
10668         } else if (gameMode == EditGame ||
10669                    gameMode == PlayFromGameFile ||
10670                    gameMode == AnalyzeMode ||
10671                    gameMode == AnalyzeFile) {
10672             nextGameMode = gameMode;
10673         } else {
10674             nextGameMode = EndOfGame;
10675         }
10676         pausing = FALSE;
10677         ModeHighlight();
10678     } else {
10679         nextGameMode = gameMode;
10680     }
10681
10682     if (appData.noChessProgram) {
10683         gameMode = nextGameMode;
10684         ModeHighlight();
10685         endingGame = 0; /* [HGM] crash */
10686         return;
10687     }
10688
10689     if (first.reuse) {
10690         /* Put first chess program into idle state */
10691         if (first.pr != NoProc &&
10692             (gameMode == MachinePlaysWhite ||
10693              gameMode == MachinePlaysBlack ||
10694              gameMode == TwoMachinesPlay ||
10695              gameMode == IcsPlayingWhite ||
10696              gameMode == IcsPlayingBlack ||
10697              gameMode == BeginningOfGame)) {
10698             SendToProgram("force\n", &first);
10699             if (first.usePing) {
10700               char buf[MSG_SIZ];
10701               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10702               SendToProgram(buf, &first);
10703             }
10704         }
10705     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10706         /* Kill off first chess program */
10707         if (first.isr != NULL)
10708           RemoveInputSource(first.isr);
10709         first.isr = NULL;
10710
10711         if (first.pr != NoProc) {
10712             ExitAnalyzeMode();
10713             DoSleep( appData.delayBeforeQuit );
10714             SendToProgram("quit\n", &first);
10715             DoSleep( appData.delayAfterQuit );
10716             DestroyChildProcess(first.pr, first.useSigterm);
10717         }
10718         first.pr = NoProc;
10719     }
10720     if (second.reuse) {
10721         /* Put second chess program into idle state */
10722         if (second.pr != NoProc &&
10723             gameMode == TwoMachinesPlay) {
10724             SendToProgram("force\n", &second);
10725             if (second.usePing) {
10726               char buf[MSG_SIZ];
10727               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10728               SendToProgram(buf, &second);
10729             }
10730         }
10731     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10732         /* Kill off second chess program */
10733         if (second.isr != NULL)
10734           RemoveInputSource(second.isr);
10735         second.isr = NULL;
10736
10737         if (second.pr != NoProc) {
10738             DoSleep( appData.delayBeforeQuit );
10739             SendToProgram("quit\n", &second);
10740             DoSleep( appData.delayAfterQuit );
10741             DestroyChildProcess(second.pr, second.useSigterm);
10742         }
10743         second.pr = NoProc;
10744     }
10745
10746     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10747         char resChar = '=';
10748         switch (result) {
10749         case WhiteWins:
10750           resChar = '+';
10751           if (first.twoMachinesColor[0] == 'w') {
10752             first.matchWins++;
10753           } else {
10754             second.matchWins++;
10755           }
10756           break;
10757         case BlackWins:
10758           resChar = '-';
10759           if (first.twoMachinesColor[0] == 'b') {
10760             first.matchWins++;
10761           } else {
10762             second.matchWins++;
10763           }
10764           break;
10765         case GameUnfinished:
10766           resChar = ' ';
10767         default:
10768           break;
10769         }
10770
10771         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10772         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10773             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10774             ReserveGame(nextGame, resChar); // sets nextGame
10775             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10776             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10777         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10778
10779         if (nextGame <= appData.matchGames && !abortMatch) {
10780             gameMode = nextGameMode;
10781             matchGame = nextGame; // this will be overruled in tourney mode!
10782             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10783             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10784             endingGame = 0; /* [HGM] crash */
10785             return;
10786         } else {
10787             gameMode = nextGameMode;
10788             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10789                      first.tidy, second.tidy,
10790                      first.matchWins, second.matchWins,
10791                      appData.matchGames - (first.matchWins + second.matchWins));
10792             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10793             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10794             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10795             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10796                 first.twoMachinesColor = "black\n";
10797                 second.twoMachinesColor = "white\n";
10798             } else {
10799                 first.twoMachinesColor = "white\n";
10800                 second.twoMachinesColor = "black\n";
10801             }
10802         }
10803     }
10804     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10805         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10806       ExitAnalyzeMode();
10807     gameMode = nextGameMode;
10808     ModeHighlight();
10809     endingGame = 0;  /* [HGM] crash */
10810     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10811         if(matchMode == TRUE) { // match through command line: exit with or without popup
10812             if(ranking) {
10813                 ToNrEvent(forwardMostMove);
10814                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10815                 else ExitEvent(0);
10816             } else DisplayFatalError(buf, 0, 0);
10817         } else { // match through menu; just stop, with or without popup
10818             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10819             ModeHighlight();
10820             if(ranking){
10821                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10822             } else DisplayNote(buf);
10823       }
10824       if(ranking) free(ranking);
10825     }
10826 }
10827
10828 /* Assumes program was just initialized (initString sent).
10829    Leaves program in force mode. */
10830 void
10831 FeedMovesToProgram (ChessProgramState *cps, int upto)
10832 {
10833     int i;
10834
10835     if (appData.debugMode)
10836       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10837               startedFromSetupPosition ? "position and " : "",
10838               backwardMostMove, upto, cps->which);
10839     if(currentlyInitializedVariant != gameInfo.variant) {
10840       char buf[MSG_SIZ];
10841         // [HGM] variantswitch: make engine aware of new variant
10842         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10843                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10844         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10845         SendToProgram(buf, cps);
10846         currentlyInitializedVariant = gameInfo.variant;
10847     }
10848     SendToProgram("force\n", cps);
10849     if (startedFromSetupPosition) {
10850         SendBoard(cps, backwardMostMove);
10851     if (appData.debugMode) {
10852         fprintf(debugFP, "feedMoves\n");
10853     }
10854     }
10855     for (i = backwardMostMove; i < upto; i++) {
10856         SendMoveToProgram(i, cps);
10857     }
10858 }
10859
10860
10861 int
10862 ResurrectChessProgram ()
10863 {
10864      /* The chess program may have exited.
10865         If so, restart it and feed it all the moves made so far. */
10866     static int doInit = 0;
10867
10868     if (appData.noChessProgram) return 1;
10869
10870     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10871         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10872         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10873         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10874     } else {
10875         if (first.pr != NoProc) return 1;
10876         StartChessProgram(&first);
10877     }
10878     InitChessProgram(&first, FALSE);
10879     FeedMovesToProgram(&first, currentMove);
10880
10881     if (!first.sendTime) {
10882         /* can't tell gnuchess what its clock should read,
10883            so we bow to its notion. */
10884         ResetClocks();
10885         timeRemaining[0][currentMove] = whiteTimeRemaining;
10886         timeRemaining[1][currentMove] = blackTimeRemaining;
10887     }
10888
10889     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10890                 appData.icsEngineAnalyze) && first.analysisSupport) {
10891       SendToProgram("analyze\n", &first);
10892       first.analyzing = TRUE;
10893     }
10894     return 1;
10895 }
10896
10897 /*
10898  * Button procedures
10899  */
10900 void
10901 Reset (int redraw, int init)
10902 {
10903     int i;
10904
10905     if (appData.debugMode) {
10906         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10907                 redraw, init, gameMode);
10908     }
10909     CleanupTail(); // [HGM] vari: delete any stored variations
10910     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10911     pausing = pauseExamInvalid = FALSE;
10912     startedFromSetupPosition = blackPlaysFirst = FALSE;
10913     firstMove = TRUE;
10914     whiteFlag = blackFlag = FALSE;
10915     userOfferedDraw = FALSE;
10916     hintRequested = bookRequested = FALSE;
10917     first.maybeThinking = FALSE;
10918     second.maybeThinking = FALSE;
10919     first.bookSuspend = FALSE; // [HGM] book
10920     second.bookSuspend = FALSE;
10921     thinkOutput[0] = NULLCHAR;
10922     lastHint[0] = NULLCHAR;
10923     ClearGameInfo(&gameInfo);
10924     gameInfo.variant = StringToVariant(appData.variant);
10925     ics_user_moved = ics_clock_paused = FALSE;
10926     ics_getting_history = H_FALSE;
10927     ics_gamenum = -1;
10928     white_holding[0] = black_holding[0] = NULLCHAR;
10929     ClearProgramStats();
10930     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10931
10932     ResetFrontEnd();
10933     ClearHighlights();
10934     flipView = appData.flipView;
10935     ClearPremoveHighlights();
10936     gotPremove = FALSE;
10937     alarmSounded = FALSE;
10938
10939     GameEnds(EndOfFile, NULL, GE_PLAYER);
10940     if(appData.serverMovesName != NULL) {
10941         /* [HGM] prepare to make moves file for broadcasting */
10942         clock_t t = clock();
10943         if(serverMoves != NULL) fclose(serverMoves);
10944         serverMoves = fopen(appData.serverMovesName, "r");
10945         if(serverMoves != NULL) {
10946             fclose(serverMoves);
10947             /* delay 15 sec before overwriting, so all clients can see end */
10948             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10949         }
10950         serverMoves = fopen(appData.serverMovesName, "w");
10951     }
10952
10953     ExitAnalyzeMode();
10954     gameMode = BeginningOfGame;
10955     ModeHighlight();
10956     if(appData.icsActive) gameInfo.variant = VariantNormal;
10957     currentMove = forwardMostMove = backwardMostMove = 0;
10958     MarkTargetSquares(1);
10959     InitPosition(redraw);
10960     for (i = 0; i < MAX_MOVES; i++) {
10961         if (commentList[i] != NULL) {
10962             free(commentList[i]);
10963             commentList[i] = NULL;
10964         }
10965     }
10966     ResetClocks();
10967     timeRemaining[0][0] = whiteTimeRemaining;
10968     timeRemaining[1][0] = blackTimeRemaining;
10969
10970     if (first.pr == NoProc) {
10971         StartChessProgram(&first);
10972     }
10973     if (init) {
10974             InitChessProgram(&first, startedFromSetupPosition);
10975     }
10976     DisplayTitle("");
10977     DisplayMessage("", "");
10978     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10979     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10980     ClearMap();        // [HGM] exclude: invalidate map
10981 }
10982
10983 void
10984 AutoPlayGameLoop ()
10985 {
10986     for (;;) {
10987         if (!AutoPlayOneMove())
10988           return;
10989         if (matchMode || appData.timeDelay == 0)
10990           continue;
10991         if (appData.timeDelay < 0)
10992           return;
10993         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
10994         break;
10995     }
10996 }
10997
10998
10999 int
11000 AutoPlayOneMove ()
11001 {
11002     int fromX, fromY, toX, toY;
11003
11004     if (appData.debugMode) {
11005       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11006     }
11007
11008     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11009       return FALSE;
11010
11011     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11012       pvInfoList[currentMove].depth = programStats.depth;
11013       pvInfoList[currentMove].score = programStats.score;
11014       pvInfoList[currentMove].time  = 0;
11015       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11016     }
11017
11018     if (currentMove >= forwardMostMove) {
11019       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
11020 //      gameMode = EndOfGame;
11021 //      ModeHighlight();
11022
11023       /* [AS] Clear current move marker at the end of a game */
11024       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11025
11026       return FALSE;
11027     }
11028
11029     toX = moveList[currentMove][2] - AAA;
11030     toY = moveList[currentMove][3] - ONE;
11031
11032     if (moveList[currentMove][1] == '@') {
11033         if (appData.highlightLastMove) {
11034             SetHighlights(-1, -1, toX, toY);
11035         }
11036     } else {
11037         fromX = moveList[currentMove][0] - AAA;
11038         fromY = moveList[currentMove][1] - ONE;
11039
11040         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11041
11042         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11043
11044         if (appData.highlightLastMove) {
11045             SetHighlights(fromX, fromY, toX, toY);
11046         }
11047     }
11048     DisplayMove(currentMove);
11049     SendMoveToProgram(currentMove++, &first);
11050     DisplayBothClocks();
11051     DrawPosition(FALSE, boards[currentMove]);
11052     // [HGM] PV info: always display, routine tests if empty
11053     DisplayComment(currentMove - 1, commentList[currentMove]);
11054     return TRUE;
11055 }
11056
11057
11058 int
11059 LoadGameOneMove (ChessMove readAhead)
11060 {
11061     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11062     char promoChar = NULLCHAR;
11063     ChessMove moveType;
11064     char move[MSG_SIZ];
11065     char *p, *q;
11066
11067     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11068         gameMode != AnalyzeMode && gameMode != Training) {
11069         gameFileFP = NULL;
11070         return FALSE;
11071     }
11072
11073     yyboardindex = forwardMostMove;
11074     if (readAhead != EndOfFile) {
11075       moveType = readAhead;
11076     } else {
11077       if (gameFileFP == NULL)
11078           return FALSE;
11079       moveType = (ChessMove) Myylex();
11080     }
11081
11082     done = FALSE;
11083     switch (moveType) {
11084       case Comment:
11085         if (appData.debugMode)
11086           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11087         p = yy_text;
11088
11089         /* append the comment but don't display it */
11090         AppendComment(currentMove, p, FALSE);
11091         return TRUE;
11092
11093       case WhiteCapturesEnPassant:
11094       case BlackCapturesEnPassant:
11095       case WhitePromotion:
11096       case BlackPromotion:
11097       case WhiteNonPromotion:
11098       case BlackNonPromotion:
11099       case NormalMove:
11100       case WhiteKingSideCastle:
11101       case WhiteQueenSideCastle:
11102       case BlackKingSideCastle:
11103       case BlackQueenSideCastle:
11104       case WhiteKingSideCastleWild:
11105       case WhiteQueenSideCastleWild:
11106       case BlackKingSideCastleWild:
11107       case BlackQueenSideCastleWild:
11108       /* PUSH Fabien */
11109       case WhiteHSideCastleFR:
11110       case WhiteASideCastleFR:
11111       case BlackHSideCastleFR:
11112       case BlackASideCastleFR:
11113       /* POP Fabien */
11114         if (appData.debugMode)
11115           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11116         fromX = currentMoveString[0] - AAA;
11117         fromY = currentMoveString[1] - ONE;
11118         toX = currentMoveString[2] - AAA;
11119         toY = currentMoveString[3] - ONE;
11120         promoChar = currentMoveString[4];
11121         break;
11122
11123       case WhiteDrop:
11124       case BlackDrop:
11125         if (appData.debugMode)
11126           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11127         fromX = moveType == WhiteDrop ?
11128           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11129         (int) CharToPiece(ToLower(currentMoveString[0]));
11130         fromY = DROP_RANK;
11131         toX = currentMoveString[2] - AAA;
11132         toY = currentMoveString[3] - ONE;
11133         break;
11134
11135       case WhiteWins:
11136       case BlackWins:
11137       case GameIsDrawn:
11138       case GameUnfinished:
11139         if (appData.debugMode)
11140           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11141         p = strchr(yy_text, '{');
11142         if (p == NULL) p = strchr(yy_text, '(');
11143         if (p == NULL) {
11144             p = yy_text;
11145             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11146         } else {
11147             q = strchr(p, *p == '{' ? '}' : ')');
11148             if (q != NULL) *q = NULLCHAR;
11149             p++;
11150         }
11151         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11152         GameEnds(moveType, p, GE_FILE);
11153         done = TRUE;
11154         if (cmailMsgLoaded) {
11155             ClearHighlights();
11156             flipView = WhiteOnMove(currentMove);
11157             if (moveType == GameUnfinished) flipView = !flipView;
11158             if (appData.debugMode)
11159               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11160         }
11161         break;
11162
11163       case EndOfFile:
11164         if (appData.debugMode)
11165           fprintf(debugFP, "Parser hit end of file\n");
11166         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11167           case MT_NONE:
11168           case MT_CHECK:
11169             break;
11170           case MT_CHECKMATE:
11171           case MT_STAINMATE:
11172             if (WhiteOnMove(currentMove)) {
11173                 GameEnds(BlackWins, "Black mates", GE_FILE);
11174             } else {
11175                 GameEnds(WhiteWins, "White mates", GE_FILE);
11176             }
11177             break;
11178           case MT_STALEMATE:
11179             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11180             break;
11181         }
11182         done = TRUE;
11183         break;
11184
11185       case MoveNumberOne:
11186         if (lastLoadGameStart == GNUChessGame) {
11187             /* GNUChessGames have numbers, but they aren't move numbers */
11188             if (appData.debugMode)
11189               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11190                       yy_text, (int) moveType);
11191             return LoadGameOneMove(EndOfFile); /* tail recursion */
11192         }
11193         /* else fall thru */
11194
11195       case XBoardGame:
11196       case GNUChessGame:
11197       case PGNTag:
11198         /* Reached start of next game in file */
11199         if (appData.debugMode)
11200           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11201         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11202           case MT_NONE:
11203           case MT_CHECK:
11204             break;
11205           case MT_CHECKMATE:
11206           case MT_STAINMATE:
11207             if (WhiteOnMove(currentMove)) {
11208                 GameEnds(BlackWins, "Black mates", GE_FILE);
11209             } else {
11210                 GameEnds(WhiteWins, "White mates", GE_FILE);
11211             }
11212             break;
11213           case MT_STALEMATE:
11214             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11215             break;
11216         }
11217         done = TRUE;
11218         break;
11219
11220       case PositionDiagram:     /* should not happen; ignore */
11221       case ElapsedTime:         /* ignore */
11222       case NAG:                 /* ignore */
11223         if (appData.debugMode)
11224           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11225                   yy_text, (int) moveType);
11226         return LoadGameOneMove(EndOfFile); /* tail recursion */
11227
11228       case IllegalMove:
11229         if (appData.testLegality) {
11230             if (appData.debugMode)
11231               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11232             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11233                     (forwardMostMove / 2) + 1,
11234                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11235             DisplayError(move, 0);
11236             done = TRUE;
11237         } else {
11238             if (appData.debugMode)
11239               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11240                       yy_text, currentMoveString);
11241             fromX = currentMoveString[0] - AAA;
11242             fromY = currentMoveString[1] - ONE;
11243             toX = currentMoveString[2] - AAA;
11244             toY = currentMoveString[3] - ONE;
11245             promoChar = currentMoveString[4];
11246         }
11247         break;
11248
11249       case AmbiguousMove:
11250         if (appData.debugMode)
11251           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11252         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11253                 (forwardMostMove / 2) + 1,
11254                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11255         DisplayError(move, 0);
11256         done = TRUE;
11257         break;
11258
11259       default:
11260       case ImpossibleMove:
11261         if (appData.debugMode)
11262           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11263         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11264                 (forwardMostMove / 2) + 1,
11265                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11266         DisplayError(move, 0);
11267         done = TRUE;
11268         break;
11269     }
11270
11271     if (done) {
11272         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11273             DrawPosition(FALSE, boards[currentMove]);
11274             DisplayBothClocks();
11275             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11276               DisplayComment(currentMove - 1, commentList[currentMove]);
11277         }
11278         (void) StopLoadGameTimer();
11279         gameFileFP = NULL;
11280         cmailOldMove = forwardMostMove;
11281         return FALSE;
11282     } else {
11283         /* currentMoveString is set as a side-effect of yylex */
11284
11285         thinkOutput[0] = NULLCHAR;
11286         MakeMove(fromX, fromY, toX, toY, promoChar);
11287         currentMove = forwardMostMove;
11288         return TRUE;
11289     }
11290 }
11291
11292 /* Load the nth game from the given file */
11293 int
11294 LoadGameFromFile (char *filename, int n, char *title, int useList)
11295 {
11296     FILE *f;
11297     char buf[MSG_SIZ];
11298
11299     if (strcmp(filename, "-") == 0) {
11300         f = stdin;
11301         title = "stdin";
11302     } else {
11303         f = fopen(filename, "rb");
11304         if (f == NULL) {
11305           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11306             DisplayError(buf, errno);
11307             return FALSE;
11308         }
11309     }
11310     if (fseek(f, 0, 0) == -1) {
11311         /* f is not seekable; probably a pipe */
11312         useList = FALSE;
11313     }
11314     if (useList && n == 0) {
11315         int error = GameListBuild(f);
11316         if (error) {
11317             DisplayError(_("Cannot build game list"), error);
11318         } else if (!ListEmpty(&gameList) &&
11319                    ((ListGame *) gameList.tailPred)->number > 1) {
11320             GameListPopUp(f, title);
11321             return TRUE;
11322         }
11323         GameListDestroy();
11324         n = 1;
11325     }
11326     if (n == 0) n = 1;
11327     return LoadGame(f, n, title, FALSE);
11328 }
11329
11330
11331 void
11332 MakeRegisteredMove ()
11333 {
11334     int fromX, fromY, toX, toY;
11335     char promoChar;
11336     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11337         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11338           case CMAIL_MOVE:
11339           case CMAIL_DRAW:
11340             if (appData.debugMode)
11341               fprintf(debugFP, "Restoring %s for game %d\n",
11342                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11343
11344             thinkOutput[0] = NULLCHAR;
11345             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11346             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11347             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11348             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11349             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11350             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11351             MakeMove(fromX, fromY, toX, toY, promoChar);
11352             ShowMove(fromX, fromY, toX, toY);
11353
11354             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11355               case MT_NONE:
11356               case MT_CHECK:
11357                 break;
11358
11359               case MT_CHECKMATE:
11360               case MT_STAINMATE:
11361                 if (WhiteOnMove(currentMove)) {
11362                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11363                 } else {
11364                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11365                 }
11366                 break;
11367
11368               case MT_STALEMATE:
11369                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11370                 break;
11371             }
11372
11373             break;
11374
11375           case CMAIL_RESIGN:
11376             if (WhiteOnMove(currentMove)) {
11377                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11378             } else {
11379                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11380             }
11381             break;
11382
11383           case CMAIL_ACCEPT:
11384             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11385             break;
11386
11387           default:
11388             break;
11389         }
11390     }
11391
11392     return;
11393 }
11394
11395 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11396 int
11397 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11398 {
11399     int retVal;
11400
11401     if (gameNumber > nCmailGames) {
11402         DisplayError(_("No more games in this message"), 0);
11403         return FALSE;
11404     }
11405     if (f == lastLoadGameFP) {
11406         int offset = gameNumber - lastLoadGameNumber;
11407         if (offset == 0) {
11408             cmailMsg[0] = NULLCHAR;
11409             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11410                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11411                 nCmailMovesRegistered--;
11412             }
11413             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11414             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11415                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11416             }
11417         } else {
11418             if (! RegisterMove()) return FALSE;
11419         }
11420     }
11421
11422     retVal = LoadGame(f, gameNumber, title, useList);
11423
11424     /* Make move registered during previous look at this game, if any */
11425     MakeRegisteredMove();
11426
11427     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11428         commentList[currentMove]
11429           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11430         DisplayComment(currentMove - 1, commentList[currentMove]);
11431     }
11432
11433     return retVal;
11434 }
11435
11436 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11437 int
11438 ReloadGame (int offset)
11439 {
11440     int gameNumber = lastLoadGameNumber + offset;
11441     if (lastLoadGameFP == NULL) {
11442         DisplayError(_("No game has been loaded yet"), 0);
11443         return FALSE;
11444     }
11445     if (gameNumber <= 0) {
11446         DisplayError(_("Can't back up any further"), 0);
11447         return FALSE;
11448     }
11449     if (cmailMsgLoaded) {
11450         return CmailLoadGame(lastLoadGameFP, gameNumber,
11451                              lastLoadGameTitle, lastLoadGameUseList);
11452     } else {
11453         return LoadGame(lastLoadGameFP, gameNumber,
11454                         lastLoadGameTitle, lastLoadGameUseList);
11455     }
11456 }
11457
11458 int keys[EmptySquare+1];
11459
11460 int
11461 PositionMatches (Board b1, Board b2)
11462 {
11463     int r, f, sum=0;
11464     switch(appData.searchMode) {
11465         case 1: return CompareWithRights(b1, b2);
11466         case 2:
11467             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11468                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11469             }
11470             return TRUE;
11471         case 3:
11472             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11473               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11474                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11475             }
11476             return sum==0;
11477         case 4:
11478             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11479                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11480             }
11481             return sum==0;
11482     }
11483     return TRUE;
11484 }
11485
11486 #define Q_PROMO  4
11487 #define Q_EP     3
11488 #define Q_BCASTL 2
11489 #define Q_WCASTL 1
11490
11491 int pieceList[256], quickBoard[256];
11492 ChessSquare pieceType[256] = { EmptySquare };
11493 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11494 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11495 int soughtTotal, turn;
11496 Boolean epOK, flipSearch;
11497
11498 typedef struct {
11499     unsigned char piece, to;
11500 } Move;
11501
11502 #define DSIZE (250000)
11503
11504 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11505 Move *moveDatabase = initialSpace;
11506 unsigned int movePtr, dataSize = DSIZE;
11507
11508 int
11509 MakePieceList (Board board, int *counts)
11510 {
11511     int r, f, n=Q_PROMO, total=0;
11512     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11513     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11514         int sq = f + (r<<4);
11515         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11516             quickBoard[sq] = ++n;
11517             pieceList[n] = sq;
11518             pieceType[n] = board[r][f];
11519             counts[board[r][f]]++;
11520             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11521             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11522             total++;
11523         }
11524     }
11525     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11526     return total;
11527 }
11528
11529 void
11530 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11531 {
11532     int sq = fromX + (fromY<<4);
11533     int piece = quickBoard[sq];
11534     quickBoard[sq] = 0;
11535     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11536     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11537         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11538         moveDatabase[movePtr++].piece = Q_WCASTL;
11539         quickBoard[sq] = piece;
11540         piece = quickBoard[from]; quickBoard[from] = 0;
11541         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11542     } else
11543     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11544         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11545         moveDatabase[movePtr++].piece = Q_BCASTL;
11546         quickBoard[sq] = piece;
11547         piece = quickBoard[from]; quickBoard[from] = 0;
11548         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11549     } else
11550     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11551         quickBoard[(fromY<<4)+toX] = 0;
11552         moveDatabase[movePtr].piece = Q_EP;
11553         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11554         moveDatabase[movePtr].to = sq;
11555     } else
11556     if(promoPiece != pieceType[piece]) {
11557         moveDatabase[movePtr++].piece = Q_PROMO;
11558         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11559     }
11560     moveDatabase[movePtr].piece = piece;
11561     quickBoard[sq] = piece;
11562     movePtr++;
11563 }
11564
11565 int
11566 PackGame (Board board)
11567 {
11568     Move *newSpace = NULL;
11569     moveDatabase[movePtr].piece = 0; // terminate previous game
11570     if(movePtr > dataSize) {
11571         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11572         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11573         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11574         if(newSpace) {
11575             int i;
11576             Move *p = moveDatabase, *q = newSpace;
11577             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11578             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11579             moveDatabase = newSpace;
11580         } else { // calloc failed, we must be out of memory. Too bad...
11581             dataSize = 0; // prevent calloc events for all subsequent games
11582             return 0;     // and signal this one isn't cached
11583         }
11584     }
11585     movePtr++;
11586     MakePieceList(board, counts);
11587     return movePtr;
11588 }
11589
11590 int
11591 QuickCompare (Board board, int *minCounts, int *maxCounts)
11592 {   // compare according to search mode
11593     int r, f;
11594     switch(appData.searchMode)
11595     {
11596       case 1: // exact position match
11597         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11598         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11599             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11600         }
11601         break;
11602       case 2: // can have extra material on empty squares
11603         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11604             if(board[r][f] == EmptySquare) continue;
11605             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11606         }
11607         break;
11608       case 3: // material with exact Pawn structure
11609         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11610             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11611             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11612         } // fall through to material comparison
11613       case 4: // exact material
11614         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11615         break;
11616       case 6: // material range with given imbalance
11617         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11618         // fall through to range comparison
11619       case 5: // material range
11620         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11621     }
11622     return TRUE;
11623 }
11624
11625 int
11626 QuickScan (Board board, Move *move)
11627 {   // reconstruct game,and compare all positions in it
11628     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11629     do {
11630         int piece = move->piece;
11631         int to = move->to, from = pieceList[piece];
11632         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11633           if(!piece) return -1;
11634           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11635             piece = (++move)->piece;
11636             from = pieceList[piece];
11637             counts[pieceType[piece]]--;
11638             pieceType[piece] = (ChessSquare) move->to;
11639             counts[move->to]++;
11640           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11641             counts[pieceType[quickBoard[to]]]--;
11642             quickBoard[to] = 0; total--;
11643             move++;
11644             continue;
11645           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11646             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11647             from  = pieceList[piece]; // so this must be King
11648             quickBoard[from] = 0;
11649             pieceList[piece] = to;
11650             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11651             quickBoard[from] = 0; // rook
11652             quickBoard[to] = piece;
11653             to = move->to; piece = move->piece;
11654             goto aftercastle;
11655           }
11656         }
11657         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11658         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11659         quickBoard[from] = 0;
11660       aftercastle:
11661         quickBoard[to] = piece;
11662         pieceList[piece] = to;
11663         cnt++; turn ^= 3;
11664         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11665            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11666            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11667                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11668           ) {
11669             static int lastCounts[EmptySquare+1];
11670             int i;
11671             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11672             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11673         } else stretch = 0;
11674         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11675         move++;
11676     } while(1);
11677 }
11678
11679 void
11680 InitSearch ()
11681 {
11682     int r, f;
11683     flipSearch = FALSE;
11684     CopyBoard(soughtBoard, boards[currentMove]);
11685     soughtTotal = MakePieceList(soughtBoard, maxSought);
11686     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11687     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11688     CopyBoard(reverseBoard, boards[currentMove]);
11689     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11690         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11691         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11692         reverseBoard[r][f] = piece;
11693     }
11694     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11695     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11696     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11697                  || (boards[currentMove][CASTLING][2] == NoRights || 
11698                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11699                  && (boards[currentMove][CASTLING][5] == NoRights || 
11700                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11701       ) {
11702         flipSearch = TRUE;
11703         CopyBoard(flipBoard, soughtBoard);
11704         CopyBoard(rotateBoard, reverseBoard);
11705         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11706             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11707             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11708         }
11709     }
11710     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11711     if(appData.searchMode >= 5) {
11712         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11713         MakePieceList(soughtBoard, minSought);
11714         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11715     }
11716     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11717         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11718 }
11719
11720 GameInfo dummyInfo;
11721
11722 int
11723 GameContainsPosition (FILE *f, ListGame *lg)
11724 {
11725     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11726     int fromX, fromY, toX, toY;
11727     char promoChar;
11728     static int initDone=FALSE;
11729
11730     // weed out games based on numerical tag comparison
11731     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11732     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11733     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11734     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11735     if(!initDone) {
11736         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11737         initDone = TRUE;
11738     }
11739     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11740     else CopyBoard(boards[scratch], initialPosition); // default start position
11741     if(lg->moves) {
11742         turn = btm + 1;
11743         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11744         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11745     }
11746     if(btm) plyNr++;
11747     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11748     fseek(f, lg->offset, 0);
11749     yynewfile(f);
11750     while(1) {
11751         yyboardindex = scratch;
11752         quickFlag = plyNr+1;
11753         next = Myylex();
11754         quickFlag = 0;
11755         switch(next) {
11756             case PGNTag:
11757                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11758             default:
11759                 continue;
11760
11761             case XBoardGame:
11762             case GNUChessGame:
11763                 if(plyNr) return -1; // after we have seen moves, this is for new game
11764               continue;
11765
11766             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11767             case ImpossibleMove:
11768             case WhiteWins: // game ends here with these four
11769             case BlackWins:
11770             case GameIsDrawn:
11771             case GameUnfinished:
11772                 return -1;
11773
11774             case IllegalMove:
11775                 if(appData.testLegality) return -1;
11776             case WhiteCapturesEnPassant:
11777             case BlackCapturesEnPassant:
11778             case WhitePromotion:
11779             case BlackPromotion:
11780             case WhiteNonPromotion:
11781             case BlackNonPromotion:
11782             case NormalMove:
11783             case WhiteKingSideCastle:
11784             case WhiteQueenSideCastle:
11785             case BlackKingSideCastle:
11786             case BlackQueenSideCastle:
11787             case WhiteKingSideCastleWild:
11788             case WhiteQueenSideCastleWild:
11789             case BlackKingSideCastleWild:
11790             case BlackQueenSideCastleWild:
11791             case WhiteHSideCastleFR:
11792             case WhiteASideCastleFR:
11793             case BlackHSideCastleFR:
11794             case BlackASideCastleFR:
11795                 fromX = currentMoveString[0] - AAA;
11796                 fromY = currentMoveString[1] - ONE;
11797                 toX = currentMoveString[2] - AAA;
11798                 toY = currentMoveString[3] - ONE;
11799                 promoChar = currentMoveString[4];
11800                 break;
11801             case WhiteDrop:
11802             case BlackDrop:
11803                 fromX = next == WhiteDrop ?
11804                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11805                   (int) CharToPiece(ToLower(currentMoveString[0]));
11806                 fromY = DROP_RANK;
11807                 toX = currentMoveString[2] - AAA;
11808                 toY = currentMoveString[3] - ONE;
11809                 promoChar = 0;
11810                 break;
11811         }
11812         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11813         plyNr++;
11814         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11815         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11816         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11817         if(appData.findMirror) {
11818             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11819             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11820         }
11821     }
11822 }
11823
11824 /* Load the nth game from open file f */
11825 int
11826 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11827 {
11828     ChessMove cm;
11829     char buf[MSG_SIZ];
11830     int gn = gameNumber;
11831     ListGame *lg = NULL;
11832     int numPGNTags = 0;
11833     int err, pos = -1;
11834     GameMode oldGameMode;
11835     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11836
11837     if (appData.debugMode)
11838         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11839
11840     if (gameMode == Training )
11841         SetTrainingModeOff();
11842
11843     oldGameMode = gameMode;
11844     if (gameMode != BeginningOfGame) {
11845       Reset(FALSE, TRUE);
11846     }
11847
11848     gameFileFP = f;
11849     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11850         fclose(lastLoadGameFP);
11851     }
11852
11853     if (useList) {
11854         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11855
11856         if (lg) {
11857             fseek(f, lg->offset, 0);
11858             GameListHighlight(gameNumber);
11859             pos = lg->position;
11860             gn = 1;
11861         }
11862         else {
11863             DisplayError(_("Game number out of range"), 0);
11864             return FALSE;
11865         }
11866     } else {
11867         GameListDestroy();
11868         if (fseek(f, 0, 0) == -1) {
11869             if (f == lastLoadGameFP ?
11870                 gameNumber == lastLoadGameNumber + 1 :
11871                 gameNumber == 1) {
11872                 gn = 1;
11873             } else {
11874                 DisplayError(_("Can't seek on game file"), 0);
11875                 return FALSE;
11876             }
11877         }
11878     }
11879     lastLoadGameFP = f;
11880     lastLoadGameNumber = gameNumber;
11881     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11882     lastLoadGameUseList = useList;
11883
11884     yynewfile(f);
11885
11886     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11887       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11888                 lg->gameInfo.black);
11889             DisplayTitle(buf);
11890     } else if (*title != NULLCHAR) {
11891         if (gameNumber > 1) {
11892           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11893             DisplayTitle(buf);
11894         } else {
11895             DisplayTitle(title);
11896         }
11897     }
11898
11899     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11900         gameMode = PlayFromGameFile;
11901         ModeHighlight();
11902     }
11903
11904     currentMove = forwardMostMove = backwardMostMove = 0;
11905     CopyBoard(boards[0], initialPosition);
11906     StopClocks();
11907
11908     /*
11909      * Skip the first gn-1 games in the file.
11910      * Also skip over anything that precedes an identifiable
11911      * start of game marker, to avoid being confused by
11912      * garbage at the start of the file.  Currently
11913      * recognized start of game markers are the move number "1",
11914      * the pattern "gnuchess .* game", the pattern
11915      * "^[#;%] [^ ]* game file", and a PGN tag block.
11916      * A game that starts with one of the latter two patterns
11917      * will also have a move number 1, possibly
11918      * following a position diagram.
11919      * 5-4-02: Let's try being more lenient and allowing a game to
11920      * start with an unnumbered move.  Does that break anything?
11921      */
11922     cm = lastLoadGameStart = EndOfFile;
11923     while (gn > 0) {
11924         yyboardindex = forwardMostMove;
11925         cm = (ChessMove) Myylex();
11926         switch (cm) {
11927           case EndOfFile:
11928             if (cmailMsgLoaded) {
11929                 nCmailGames = CMAIL_MAX_GAMES - gn;
11930             } else {
11931                 Reset(TRUE, TRUE);
11932                 DisplayError(_("Game not found in file"), 0);
11933             }
11934             return FALSE;
11935
11936           case GNUChessGame:
11937           case XBoardGame:
11938             gn--;
11939             lastLoadGameStart = cm;
11940             break;
11941
11942           case MoveNumberOne:
11943             switch (lastLoadGameStart) {
11944               case GNUChessGame:
11945               case XBoardGame:
11946               case PGNTag:
11947                 break;
11948               case MoveNumberOne:
11949               case EndOfFile:
11950                 gn--;           /* count this game */
11951                 lastLoadGameStart = cm;
11952                 break;
11953               default:
11954                 /* impossible */
11955                 break;
11956             }
11957             break;
11958
11959           case PGNTag:
11960             switch (lastLoadGameStart) {
11961               case GNUChessGame:
11962               case PGNTag:
11963               case MoveNumberOne:
11964               case EndOfFile:
11965                 gn--;           /* count this game */
11966                 lastLoadGameStart = cm;
11967                 break;
11968               case XBoardGame:
11969                 lastLoadGameStart = cm; /* game counted already */
11970                 break;
11971               default:
11972                 /* impossible */
11973                 break;
11974             }
11975             if (gn > 0) {
11976                 do {
11977                     yyboardindex = forwardMostMove;
11978                     cm = (ChessMove) Myylex();
11979                 } while (cm == PGNTag || cm == Comment);
11980             }
11981             break;
11982
11983           case WhiteWins:
11984           case BlackWins:
11985           case GameIsDrawn:
11986             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11987                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11988                     != CMAIL_OLD_RESULT) {
11989                     nCmailResults ++ ;
11990                     cmailResult[  CMAIL_MAX_GAMES
11991                                 - gn - 1] = CMAIL_OLD_RESULT;
11992                 }
11993             }
11994             break;
11995
11996           case NormalMove:
11997             /* Only a NormalMove can be at the start of a game
11998              * without a position diagram. */
11999             if (lastLoadGameStart == EndOfFile ) {
12000               gn--;
12001               lastLoadGameStart = MoveNumberOne;
12002             }
12003             break;
12004
12005           default:
12006             break;
12007         }
12008     }
12009
12010     if (appData.debugMode)
12011       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12012
12013     if (cm == XBoardGame) {
12014         /* Skip any header junk before position diagram and/or move 1 */
12015         for (;;) {
12016             yyboardindex = forwardMostMove;
12017             cm = (ChessMove) Myylex();
12018
12019             if (cm == EndOfFile ||
12020                 cm == GNUChessGame || cm == XBoardGame) {
12021                 /* Empty game; pretend end-of-file and handle later */
12022                 cm = EndOfFile;
12023                 break;
12024             }
12025
12026             if (cm == MoveNumberOne || cm == PositionDiagram ||
12027                 cm == PGNTag || cm == Comment)
12028               break;
12029         }
12030     } else if (cm == GNUChessGame) {
12031         if (gameInfo.event != NULL) {
12032             free(gameInfo.event);
12033         }
12034         gameInfo.event = StrSave(yy_text);
12035     }
12036
12037     startedFromSetupPosition = FALSE;
12038     while (cm == PGNTag) {
12039         if (appData.debugMode)
12040           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12041         err = ParsePGNTag(yy_text, &gameInfo);
12042         if (!err) numPGNTags++;
12043
12044         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12045         if(gameInfo.variant != oldVariant) {
12046             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12047             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12048             InitPosition(TRUE);
12049             oldVariant = gameInfo.variant;
12050             if (appData.debugMode)
12051               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12052         }
12053
12054
12055         if (gameInfo.fen != NULL) {
12056           Board initial_position;
12057           startedFromSetupPosition = TRUE;
12058           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12059             Reset(TRUE, TRUE);
12060             DisplayError(_("Bad FEN position in file"), 0);
12061             return FALSE;
12062           }
12063           CopyBoard(boards[0], initial_position);
12064           if (blackPlaysFirst) {
12065             currentMove = forwardMostMove = backwardMostMove = 1;
12066             CopyBoard(boards[1], initial_position);
12067             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12068             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12069             timeRemaining[0][1] = whiteTimeRemaining;
12070             timeRemaining[1][1] = blackTimeRemaining;
12071             if (commentList[0] != NULL) {
12072               commentList[1] = commentList[0];
12073               commentList[0] = NULL;
12074             }
12075           } else {
12076             currentMove = forwardMostMove = backwardMostMove = 0;
12077           }
12078           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12079           {   int i;
12080               initialRulePlies = FENrulePlies;
12081               for( i=0; i< nrCastlingRights; i++ )
12082                   initialRights[i] = initial_position[CASTLING][i];
12083           }
12084           yyboardindex = forwardMostMove;
12085           free(gameInfo.fen);
12086           gameInfo.fen = NULL;
12087         }
12088
12089         yyboardindex = forwardMostMove;
12090         cm = (ChessMove) Myylex();
12091
12092         /* Handle comments interspersed among the tags */
12093         while (cm == Comment) {
12094             char *p;
12095             if (appData.debugMode)
12096               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12097             p = yy_text;
12098             AppendComment(currentMove, p, FALSE);
12099             yyboardindex = forwardMostMove;
12100             cm = (ChessMove) Myylex();
12101         }
12102     }
12103
12104     /* don't rely on existence of Event tag since if game was
12105      * pasted from clipboard the Event tag may not exist
12106      */
12107     if (numPGNTags > 0){
12108         char *tags;
12109         if (gameInfo.variant == VariantNormal) {
12110           VariantClass v = StringToVariant(gameInfo.event);
12111           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12112           if(v < VariantShogi) gameInfo.variant = v;
12113         }
12114         if (!matchMode) {
12115           if( appData.autoDisplayTags ) {
12116             tags = PGNTags(&gameInfo);
12117             TagsPopUp(tags, CmailMsg());
12118             free(tags);
12119           }
12120         }
12121     } else {
12122         /* Make something up, but don't display it now */
12123         SetGameInfo();
12124         TagsPopDown();
12125     }
12126
12127     if (cm == PositionDiagram) {
12128         int i, j;
12129         char *p;
12130         Board initial_position;
12131
12132         if (appData.debugMode)
12133           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12134
12135         if (!startedFromSetupPosition) {
12136             p = yy_text;
12137             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12138               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12139                 switch (*p) {
12140                   case '{':
12141                   case '[':
12142                   case '-':
12143                   case ' ':
12144                   case '\t':
12145                   case '\n':
12146                   case '\r':
12147                     break;
12148                   default:
12149                     initial_position[i][j++] = CharToPiece(*p);
12150                     break;
12151                 }
12152             while (*p == ' ' || *p == '\t' ||
12153                    *p == '\n' || *p == '\r') p++;
12154
12155             if (strncmp(p, "black", strlen("black"))==0)
12156               blackPlaysFirst = TRUE;
12157             else
12158               blackPlaysFirst = FALSE;
12159             startedFromSetupPosition = TRUE;
12160
12161             CopyBoard(boards[0], initial_position);
12162             if (blackPlaysFirst) {
12163                 currentMove = forwardMostMove = backwardMostMove = 1;
12164                 CopyBoard(boards[1], initial_position);
12165                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12166                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12167                 timeRemaining[0][1] = whiteTimeRemaining;
12168                 timeRemaining[1][1] = blackTimeRemaining;
12169                 if (commentList[0] != NULL) {
12170                     commentList[1] = commentList[0];
12171                     commentList[0] = NULL;
12172                 }
12173             } else {
12174                 currentMove = forwardMostMove = backwardMostMove = 0;
12175             }
12176         }
12177         yyboardindex = forwardMostMove;
12178         cm = (ChessMove) Myylex();
12179     }
12180
12181     if (first.pr == NoProc) {
12182         StartChessProgram(&first);
12183     }
12184     InitChessProgram(&first, FALSE);
12185     SendToProgram("force\n", &first);
12186     if (startedFromSetupPosition) {
12187         SendBoard(&first, forwardMostMove);
12188     if (appData.debugMode) {
12189         fprintf(debugFP, "Load Game\n");
12190     }
12191         DisplayBothClocks();
12192     }
12193
12194     /* [HGM] server: flag to write setup moves in broadcast file as one */
12195     loadFlag = appData.suppressLoadMoves;
12196
12197     while (cm == Comment) {
12198         char *p;
12199         if (appData.debugMode)
12200           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12201         p = yy_text;
12202         AppendComment(currentMove, p, FALSE);
12203         yyboardindex = forwardMostMove;
12204         cm = (ChessMove) Myylex();
12205     }
12206
12207     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12208         cm == WhiteWins || cm == BlackWins ||
12209         cm == GameIsDrawn || cm == GameUnfinished) {
12210         DisplayMessage("", _("No moves in game"));
12211         if (cmailMsgLoaded) {
12212             if (appData.debugMode)
12213               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12214             ClearHighlights();
12215             flipView = FALSE;
12216         }
12217         DrawPosition(FALSE, boards[currentMove]);
12218         DisplayBothClocks();
12219         gameMode = EditGame;
12220         ModeHighlight();
12221         gameFileFP = NULL;
12222         cmailOldMove = 0;
12223         return TRUE;
12224     }
12225
12226     // [HGM] PV info: routine tests if comment empty
12227     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12228         DisplayComment(currentMove - 1, commentList[currentMove]);
12229     }
12230     if (!matchMode && appData.timeDelay != 0)
12231       DrawPosition(FALSE, boards[currentMove]);
12232
12233     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12234       programStats.ok_to_send = 1;
12235     }
12236
12237     /* if the first token after the PGN tags is a move
12238      * and not move number 1, retrieve it from the parser
12239      */
12240     if (cm != MoveNumberOne)
12241         LoadGameOneMove(cm);
12242
12243     /* load the remaining moves from the file */
12244     while (LoadGameOneMove(EndOfFile)) {
12245       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12246       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12247     }
12248
12249     /* rewind to the start of the game */
12250     currentMove = backwardMostMove;
12251
12252     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12253
12254     if (oldGameMode == AnalyzeFile ||
12255         oldGameMode == AnalyzeMode) {
12256       AnalyzeFileEvent();
12257     }
12258
12259     if (!matchMode && pos > 0) {
12260         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12261     } else
12262     if (matchMode || appData.timeDelay == 0) {
12263       ToEndEvent();
12264     } else if (appData.timeDelay > 0) {
12265       AutoPlayGameLoop();
12266     }
12267
12268     if (appData.debugMode)
12269         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12270
12271     loadFlag = 0; /* [HGM] true game starts */
12272     return TRUE;
12273 }
12274
12275 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12276 int
12277 ReloadPosition (int offset)
12278 {
12279     int positionNumber = lastLoadPositionNumber + offset;
12280     if (lastLoadPositionFP == NULL) {
12281         DisplayError(_("No position has been loaded yet"), 0);
12282         return FALSE;
12283     }
12284     if (positionNumber <= 0) {
12285         DisplayError(_("Can't back up any further"), 0);
12286         return FALSE;
12287     }
12288     return LoadPosition(lastLoadPositionFP, positionNumber,
12289                         lastLoadPositionTitle);
12290 }
12291
12292 /* Load the nth position from the given file */
12293 int
12294 LoadPositionFromFile (char *filename, int n, char *title)
12295 {
12296     FILE *f;
12297     char buf[MSG_SIZ];
12298
12299     if (strcmp(filename, "-") == 0) {
12300         return LoadPosition(stdin, n, "stdin");
12301     } else {
12302         f = fopen(filename, "rb");
12303         if (f == NULL) {
12304             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12305             DisplayError(buf, errno);
12306             return FALSE;
12307         } else {
12308             return LoadPosition(f, n, title);
12309         }
12310     }
12311 }
12312
12313 /* Load the nth position from the given open file, and close it */
12314 int
12315 LoadPosition (FILE *f, int positionNumber, char *title)
12316 {
12317     char *p, line[MSG_SIZ];
12318     Board initial_position;
12319     int i, j, fenMode, pn;
12320
12321     if (gameMode == Training )
12322         SetTrainingModeOff();
12323
12324     if (gameMode != BeginningOfGame) {
12325         Reset(FALSE, TRUE);
12326     }
12327     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12328         fclose(lastLoadPositionFP);
12329     }
12330     if (positionNumber == 0) positionNumber = 1;
12331     lastLoadPositionFP = f;
12332     lastLoadPositionNumber = positionNumber;
12333     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12334     if (first.pr == NoProc && !appData.noChessProgram) {
12335       StartChessProgram(&first);
12336       InitChessProgram(&first, FALSE);
12337     }
12338     pn = positionNumber;
12339     if (positionNumber < 0) {
12340         /* Negative position number means to seek to that byte offset */
12341         if (fseek(f, -positionNumber, 0) == -1) {
12342             DisplayError(_("Can't seek on position file"), 0);
12343             return FALSE;
12344         };
12345         pn = 1;
12346     } else {
12347         if (fseek(f, 0, 0) == -1) {
12348             if (f == lastLoadPositionFP ?
12349                 positionNumber == lastLoadPositionNumber + 1 :
12350                 positionNumber == 1) {
12351                 pn = 1;
12352             } else {
12353                 DisplayError(_("Can't seek on position file"), 0);
12354                 return FALSE;
12355             }
12356         }
12357     }
12358     /* See if this file is FEN or old-style xboard */
12359     if (fgets(line, MSG_SIZ, f) == NULL) {
12360         DisplayError(_("Position not found in file"), 0);
12361         return FALSE;
12362     }
12363     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12364     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12365
12366     if (pn >= 2) {
12367         if (fenMode || line[0] == '#') pn--;
12368         while (pn > 0) {
12369             /* skip positions before number pn */
12370             if (fgets(line, MSG_SIZ, f) == NULL) {
12371                 Reset(TRUE, TRUE);
12372                 DisplayError(_("Position not found in file"), 0);
12373                 return FALSE;
12374             }
12375             if (fenMode || line[0] == '#') pn--;
12376         }
12377     }
12378
12379     if (fenMode) {
12380         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12381             DisplayError(_("Bad FEN position in file"), 0);
12382             return FALSE;
12383         }
12384     } else {
12385         (void) fgets(line, MSG_SIZ, f);
12386         (void) fgets(line, MSG_SIZ, f);
12387
12388         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12389             (void) fgets(line, MSG_SIZ, f);
12390             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12391                 if (*p == ' ')
12392                   continue;
12393                 initial_position[i][j++] = CharToPiece(*p);
12394             }
12395         }
12396
12397         blackPlaysFirst = FALSE;
12398         if (!feof(f)) {
12399             (void) fgets(line, MSG_SIZ, f);
12400             if (strncmp(line, "black", strlen("black"))==0)
12401               blackPlaysFirst = TRUE;
12402         }
12403     }
12404     startedFromSetupPosition = TRUE;
12405
12406     CopyBoard(boards[0], initial_position);
12407     if (blackPlaysFirst) {
12408         currentMove = forwardMostMove = backwardMostMove = 1;
12409         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12410         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12411         CopyBoard(boards[1], initial_position);
12412         DisplayMessage("", _("Black to play"));
12413     } else {
12414         currentMove = forwardMostMove = backwardMostMove = 0;
12415         DisplayMessage("", _("White to play"));
12416     }
12417     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12418     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12419         SendToProgram("force\n", &first);
12420         SendBoard(&first, forwardMostMove);
12421     }
12422     if (appData.debugMode) {
12423 int i, j;
12424   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12425   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12426         fprintf(debugFP, "Load Position\n");
12427     }
12428
12429     if (positionNumber > 1) {
12430       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12431         DisplayTitle(line);
12432     } else {
12433         DisplayTitle(title);
12434     }
12435     gameMode = EditGame;
12436     ModeHighlight();
12437     ResetClocks();
12438     timeRemaining[0][1] = whiteTimeRemaining;
12439     timeRemaining[1][1] = blackTimeRemaining;
12440     DrawPosition(FALSE, boards[currentMove]);
12441
12442     return TRUE;
12443 }
12444
12445
12446 void
12447 CopyPlayerNameIntoFileName (char **dest, char *src)
12448 {
12449     while (*src != NULLCHAR && *src != ',') {
12450         if (*src == ' ') {
12451             *(*dest)++ = '_';
12452             src++;
12453         } else {
12454             *(*dest)++ = *src++;
12455         }
12456     }
12457 }
12458
12459 char *
12460 DefaultFileName (char *ext)
12461 {
12462     static char def[MSG_SIZ];
12463     char *p;
12464
12465     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12466         p = def;
12467         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12468         *p++ = '-';
12469         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12470         *p++ = '.';
12471         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12472     } else {
12473         def[0] = NULLCHAR;
12474     }
12475     return def;
12476 }
12477
12478 /* Save the current game to the given file */
12479 int
12480 SaveGameToFile (char *filename, int append)
12481 {
12482     FILE *f;
12483     char buf[MSG_SIZ];
12484     int result, i, t,tot=0;
12485
12486     if (strcmp(filename, "-") == 0) {
12487         return SaveGame(stdout, 0, NULL);
12488     } else {
12489         for(i=0; i<10; i++) { // upto 10 tries
12490              f = fopen(filename, append ? "a" : "w");
12491              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12492              if(f || errno != 13) break;
12493              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12494              tot += t;
12495         }
12496         if (f == NULL) {
12497             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12498             DisplayError(buf, errno);
12499             return FALSE;
12500         } else {
12501             safeStrCpy(buf, lastMsg, MSG_SIZ);
12502             DisplayMessage(_("Waiting for access to save file"), "");
12503             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12504             DisplayMessage(_("Saving game"), "");
12505             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12506             result = SaveGame(f, 0, NULL);
12507             DisplayMessage(buf, "");
12508             return result;
12509         }
12510     }
12511 }
12512
12513 char *
12514 SavePart (char *str)
12515 {
12516     static char buf[MSG_SIZ];
12517     char *p;
12518
12519     p = strchr(str, ' ');
12520     if (p == NULL) return str;
12521     strncpy(buf, str, p - str);
12522     buf[p - str] = NULLCHAR;
12523     return buf;
12524 }
12525
12526 #define PGN_MAX_LINE 75
12527
12528 #define PGN_SIDE_WHITE  0
12529 #define PGN_SIDE_BLACK  1
12530
12531 static int
12532 FindFirstMoveOutOfBook (int side)
12533 {
12534     int result = -1;
12535
12536     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12537         int index = backwardMostMove;
12538         int has_book_hit = 0;
12539
12540         if( (index % 2) != side ) {
12541             index++;
12542         }
12543
12544         while( index < forwardMostMove ) {
12545             /* Check to see if engine is in book */
12546             int depth = pvInfoList[index].depth;
12547             int score = pvInfoList[index].score;
12548             int in_book = 0;
12549
12550             if( depth <= 2 ) {
12551                 in_book = 1;
12552             }
12553             else if( score == 0 && depth == 63 ) {
12554                 in_book = 1; /* Zappa */
12555             }
12556             else if( score == 2 && depth == 99 ) {
12557                 in_book = 1; /* Abrok */
12558             }
12559
12560             has_book_hit += in_book;
12561
12562             if( ! in_book ) {
12563                 result = index;
12564
12565                 break;
12566             }
12567
12568             index += 2;
12569         }
12570     }
12571
12572     return result;
12573 }
12574
12575 void
12576 GetOutOfBookInfo (char * buf)
12577 {
12578     int oob[2];
12579     int i;
12580     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12581
12582     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12583     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12584
12585     *buf = '\0';
12586
12587     if( oob[0] >= 0 || oob[1] >= 0 ) {
12588         for( i=0; i<2; i++ ) {
12589             int idx = oob[i];
12590
12591             if( idx >= 0 ) {
12592                 if( i > 0 && oob[0] >= 0 ) {
12593                     strcat( buf, "   " );
12594                 }
12595
12596                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12597                 sprintf( buf+strlen(buf), "%s%.2f",
12598                     pvInfoList[idx].score >= 0 ? "+" : "",
12599                     pvInfoList[idx].score / 100.0 );
12600             }
12601         }
12602     }
12603 }
12604
12605 /* Save game in PGN style and close the file */
12606 int
12607 SaveGamePGN (FILE *f)
12608 {
12609     int i, offset, linelen, newblock;
12610 //    char *movetext;
12611     char numtext[32];
12612     int movelen, numlen, blank;
12613     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12614
12615     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12616
12617     PrintPGNTags(f, &gameInfo);
12618
12619     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12620
12621     if (backwardMostMove > 0 || startedFromSetupPosition) {
12622         char *fen = PositionToFEN(backwardMostMove, NULL);
12623         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12624         fprintf(f, "\n{--------------\n");
12625         PrintPosition(f, backwardMostMove);
12626         fprintf(f, "--------------}\n");
12627         free(fen);
12628     }
12629     else {
12630         /* [AS] Out of book annotation */
12631         if( appData.saveOutOfBookInfo ) {
12632             char buf[64];
12633
12634             GetOutOfBookInfo( buf );
12635
12636             if( buf[0] != '\0' ) {
12637                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12638             }
12639         }
12640
12641         fprintf(f, "\n");
12642     }
12643
12644     i = backwardMostMove;
12645     linelen = 0;
12646     newblock = TRUE;
12647
12648     while (i < forwardMostMove) {
12649         /* Print comments preceding this move */
12650         if (commentList[i] != NULL) {
12651             if (linelen > 0) fprintf(f, "\n");
12652             fprintf(f, "%s", commentList[i]);
12653             linelen = 0;
12654             newblock = TRUE;
12655         }
12656
12657         /* Format move number */
12658         if ((i % 2) == 0)
12659           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12660         else
12661           if (newblock)
12662             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12663           else
12664             numtext[0] = NULLCHAR;
12665
12666         numlen = strlen(numtext);
12667         newblock = FALSE;
12668
12669         /* Print move number */
12670         blank = linelen > 0 && numlen > 0;
12671         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12672             fprintf(f, "\n");
12673             linelen = 0;
12674             blank = 0;
12675         }
12676         if (blank) {
12677             fprintf(f, " ");
12678             linelen++;
12679         }
12680         fprintf(f, "%s", numtext);
12681         linelen += numlen;
12682
12683         /* Get move */
12684         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12685         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12686
12687         /* Print move */
12688         blank = linelen > 0 && movelen > 0;
12689         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12690             fprintf(f, "\n");
12691             linelen = 0;
12692             blank = 0;
12693         }
12694         if (blank) {
12695             fprintf(f, " ");
12696             linelen++;
12697         }
12698         fprintf(f, "%s", move_buffer);
12699         linelen += movelen;
12700
12701         /* [AS] Add PV info if present */
12702         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12703             /* [HGM] add time */
12704             char buf[MSG_SIZ]; int seconds;
12705
12706             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12707
12708             if( seconds <= 0)
12709               buf[0] = 0;
12710             else
12711               if( seconds < 30 )
12712                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12713               else
12714                 {
12715                   seconds = (seconds + 4)/10; // round to full seconds
12716                   if( seconds < 60 )
12717                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12718                   else
12719                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12720                 }
12721
12722             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12723                       pvInfoList[i].score >= 0 ? "+" : "",
12724                       pvInfoList[i].score / 100.0,
12725                       pvInfoList[i].depth,
12726                       buf );
12727
12728             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12729
12730             /* Print score/depth */
12731             blank = linelen > 0 && movelen > 0;
12732             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12733                 fprintf(f, "\n");
12734                 linelen = 0;
12735                 blank = 0;
12736             }
12737             if (blank) {
12738                 fprintf(f, " ");
12739                 linelen++;
12740             }
12741             fprintf(f, "%s", move_buffer);
12742             linelen += movelen;
12743         }
12744
12745         i++;
12746     }
12747
12748     /* Start a new line */
12749     if (linelen > 0) fprintf(f, "\n");
12750
12751     /* Print comments after last move */
12752     if (commentList[i] != NULL) {
12753         fprintf(f, "%s\n", commentList[i]);
12754     }
12755
12756     /* Print result */
12757     if (gameInfo.resultDetails != NULL &&
12758         gameInfo.resultDetails[0] != NULLCHAR) {
12759         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12760                 PGNResult(gameInfo.result));
12761     } else {
12762         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12763     }
12764
12765     fclose(f);
12766     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12767     return TRUE;
12768 }
12769
12770 /* Save game in old style and close the file */
12771 int
12772 SaveGameOldStyle (FILE *f)
12773 {
12774     int i, offset;
12775     time_t tm;
12776
12777     tm = time((time_t *) NULL);
12778
12779     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12780     PrintOpponents(f);
12781
12782     if (backwardMostMove > 0 || startedFromSetupPosition) {
12783         fprintf(f, "\n[--------------\n");
12784         PrintPosition(f, backwardMostMove);
12785         fprintf(f, "--------------]\n");
12786     } else {
12787         fprintf(f, "\n");
12788     }
12789
12790     i = backwardMostMove;
12791     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12792
12793     while (i < forwardMostMove) {
12794         if (commentList[i] != NULL) {
12795             fprintf(f, "[%s]\n", commentList[i]);
12796         }
12797
12798         if ((i % 2) == 1) {
12799             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12800             i++;
12801         } else {
12802             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12803             i++;
12804             if (commentList[i] != NULL) {
12805                 fprintf(f, "\n");
12806                 continue;
12807             }
12808             if (i >= forwardMostMove) {
12809                 fprintf(f, "\n");
12810                 break;
12811             }
12812             fprintf(f, "%s\n", parseList[i]);
12813             i++;
12814         }
12815     }
12816
12817     if (commentList[i] != NULL) {
12818         fprintf(f, "[%s]\n", commentList[i]);
12819     }
12820
12821     /* This isn't really the old style, but it's close enough */
12822     if (gameInfo.resultDetails != NULL &&
12823         gameInfo.resultDetails[0] != NULLCHAR) {
12824         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12825                 gameInfo.resultDetails);
12826     } else {
12827         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12828     }
12829
12830     fclose(f);
12831     return TRUE;
12832 }
12833
12834 /* Save the current game to open file f and close the file */
12835 int
12836 SaveGame (FILE *f, int dummy, char *dummy2)
12837 {
12838     if (gameMode == EditPosition) EditPositionDone(TRUE);
12839     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12840     if (appData.oldSaveStyle)
12841       return SaveGameOldStyle(f);
12842     else
12843       return SaveGamePGN(f);
12844 }
12845
12846 /* Save the current position to the given file */
12847 int
12848 SavePositionToFile (char *filename)
12849 {
12850     FILE *f;
12851     char buf[MSG_SIZ];
12852
12853     if (strcmp(filename, "-") == 0) {
12854         return SavePosition(stdout, 0, NULL);
12855     } else {
12856         f = fopen(filename, "a");
12857         if (f == NULL) {
12858             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12859             DisplayError(buf, errno);
12860             return FALSE;
12861         } else {
12862             safeStrCpy(buf, lastMsg, MSG_SIZ);
12863             DisplayMessage(_("Waiting for access to save file"), "");
12864             flock(fileno(f), LOCK_EX); // [HGM] lock
12865             DisplayMessage(_("Saving position"), "");
12866             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12867             SavePosition(f, 0, NULL);
12868             DisplayMessage(buf, "");
12869             return TRUE;
12870         }
12871     }
12872 }
12873
12874 /* Save the current position to the given open file and close the file */
12875 int
12876 SavePosition (FILE *f, int dummy, char *dummy2)
12877 {
12878     time_t tm;
12879     char *fen;
12880
12881     if (gameMode == EditPosition) EditPositionDone(TRUE);
12882     if (appData.oldSaveStyle) {
12883         tm = time((time_t *) NULL);
12884
12885         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12886         PrintOpponents(f);
12887         fprintf(f, "[--------------\n");
12888         PrintPosition(f, currentMove);
12889         fprintf(f, "--------------]\n");
12890     } else {
12891         fen = PositionToFEN(currentMove, NULL);
12892         fprintf(f, "%s\n", fen);
12893         free(fen);
12894     }
12895     fclose(f);
12896     return TRUE;
12897 }
12898
12899 void
12900 ReloadCmailMsgEvent (int unregister)
12901 {
12902 #if !WIN32
12903     static char *inFilename = NULL;
12904     static char *outFilename;
12905     int i;
12906     struct stat inbuf, outbuf;
12907     int status;
12908
12909     /* Any registered moves are unregistered if unregister is set, */
12910     /* i.e. invoked by the signal handler */
12911     if (unregister) {
12912         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12913             cmailMoveRegistered[i] = FALSE;
12914             if (cmailCommentList[i] != NULL) {
12915                 free(cmailCommentList[i]);
12916                 cmailCommentList[i] = NULL;
12917             }
12918         }
12919         nCmailMovesRegistered = 0;
12920     }
12921
12922     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12923         cmailResult[i] = CMAIL_NOT_RESULT;
12924     }
12925     nCmailResults = 0;
12926
12927     if (inFilename == NULL) {
12928         /* Because the filenames are static they only get malloced once  */
12929         /* and they never get freed                                      */
12930         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12931         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12932
12933         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12934         sprintf(outFilename, "%s.out", appData.cmailGameName);
12935     }
12936
12937     status = stat(outFilename, &outbuf);
12938     if (status < 0) {
12939         cmailMailedMove = FALSE;
12940     } else {
12941         status = stat(inFilename, &inbuf);
12942         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12943     }
12944
12945     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12946        counts the games, notes how each one terminated, etc.
12947
12948        It would be nice to remove this kludge and instead gather all
12949        the information while building the game list.  (And to keep it
12950        in the game list nodes instead of having a bunch of fixed-size
12951        parallel arrays.)  Note this will require getting each game's
12952        termination from the PGN tags, as the game list builder does
12953        not process the game moves.  --mann
12954        */
12955     cmailMsgLoaded = TRUE;
12956     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12957
12958     /* Load first game in the file or popup game menu */
12959     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12960
12961 #endif /* !WIN32 */
12962     return;
12963 }
12964
12965 int
12966 RegisterMove ()
12967 {
12968     FILE *f;
12969     char string[MSG_SIZ];
12970
12971     if (   cmailMailedMove
12972         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12973         return TRUE;            /* Allow free viewing  */
12974     }
12975
12976     /* Unregister move to ensure that we don't leave RegisterMove        */
12977     /* with the move registered when the conditions for registering no   */
12978     /* longer hold                                                       */
12979     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12980         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12981         nCmailMovesRegistered --;
12982
12983         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12984           {
12985               free(cmailCommentList[lastLoadGameNumber - 1]);
12986               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12987           }
12988     }
12989
12990     if (cmailOldMove == -1) {
12991         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12992         return FALSE;
12993     }
12994
12995     if (currentMove > cmailOldMove + 1) {
12996         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12997         return FALSE;
12998     }
12999
13000     if (currentMove < cmailOldMove) {
13001         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13002         return FALSE;
13003     }
13004
13005     if (forwardMostMove > currentMove) {
13006         /* Silently truncate extra moves */
13007         TruncateGame();
13008     }
13009
13010     if (   (currentMove == cmailOldMove + 1)
13011         || (   (currentMove == cmailOldMove)
13012             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13013                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13014         if (gameInfo.result != GameUnfinished) {
13015             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13016         }
13017
13018         if (commentList[currentMove] != NULL) {
13019             cmailCommentList[lastLoadGameNumber - 1]
13020               = StrSave(commentList[currentMove]);
13021         }
13022         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13023
13024         if (appData.debugMode)
13025           fprintf(debugFP, "Saving %s for game %d\n",
13026                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13027
13028         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13029
13030         f = fopen(string, "w");
13031         if (appData.oldSaveStyle) {
13032             SaveGameOldStyle(f); /* also closes the file */
13033
13034             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13035             f = fopen(string, "w");
13036             SavePosition(f, 0, NULL); /* also closes the file */
13037         } else {
13038             fprintf(f, "{--------------\n");
13039             PrintPosition(f, currentMove);
13040             fprintf(f, "--------------}\n\n");
13041
13042             SaveGame(f, 0, NULL); /* also closes the file*/
13043         }
13044
13045         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13046         nCmailMovesRegistered ++;
13047     } else if (nCmailGames == 1) {
13048         DisplayError(_("You have not made a move yet"), 0);
13049         return FALSE;
13050     }
13051
13052     return TRUE;
13053 }
13054
13055 void
13056 MailMoveEvent ()
13057 {
13058 #if !WIN32
13059     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13060     FILE *commandOutput;
13061     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13062     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13063     int nBuffers;
13064     int i;
13065     int archived;
13066     char *arcDir;
13067
13068     if (! cmailMsgLoaded) {
13069         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13070         return;
13071     }
13072
13073     if (nCmailGames == nCmailResults) {
13074         DisplayError(_("No unfinished games"), 0);
13075         return;
13076     }
13077
13078 #if CMAIL_PROHIBIT_REMAIL
13079     if (cmailMailedMove) {
13080       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);
13081         DisplayError(msg, 0);
13082         return;
13083     }
13084 #endif
13085
13086     if (! (cmailMailedMove || RegisterMove())) return;
13087
13088     if (   cmailMailedMove
13089         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13090       snprintf(string, MSG_SIZ, partCommandString,
13091                appData.debugMode ? " -v" : "", appData.cmailGameName);
13092         commandOutput = popen(string, "r");
13093
13094         if (commandOutput == NULL) {
13095             DisplayError(_("Failed to invoke cmail"), 0);
13096         } else {
13097             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13098                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13099             }
13100             if (nBuffers > 1) {
13101                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13102                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13103                 nBytes = MSG_SIZ - 1;
13104             } else {
13105                 (void) memcpy(msg, buffer, nBytes);
13106             }
13107             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13108
13109             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13110                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13111
13112                 archived = TRUE;
13113                 for (i = 0; i < nCmailGames; i ++) {
13114                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13115                         archived = FALSE;
13116                     }
13117                 }
13118                 if (   archived
13119                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13120                         != NULL)) {
13121                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13122                            arcDir,
13123                            appData.cmailGameName,
13124                            gameInfo.date);
13125                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13126                     cmailMsgLoaded = FALSE;
13127                 }
13128             }
13129
13130             DisplayInformation(msg);
13131             pclose(commandOutput);
13132         }
13133     } else {
13134         if ((*cmailMsg) != '\0') {
13135             DisplayInformation(cmailMsg);
13136         }
13137     }
13138
13139     return;
13140 #endif /* !WIN32 */
13141 }
13142
13143 char *
13144 CmailMsg ()
13145 {
13146 #if WIN32
13147     return NULL;
13148 #else
13149     int  prependComma = 0;
13150     char number[5];
13151     char string[MSG_SIZ];       /* Space for game-list */
13152     int  i;
13153
13154     if (!cmailMsgLoaded) return "";
13155
13156     if (cmailMailedMove) {
13157       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13158     } else {
13159         /* Create a list of games left */
13160       snprintf(string, MSG_SIZ, "[");
13161         for (i = 0; i < nCmailGames; i ++) {
13162             if (! (   cmailMoveRegistered[i]
13163                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13164                 if (prependComma) {
13165                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13166                 } else {
13167                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13168                     prependComma = 1;
13169                 }
13170
13171                 strcat(string, number);
13172             }
13173         }
13174         strcat(string, "]");
13175
13176         if (nCmailMovesRegistered + nCmailResults == 0) {
13177             switch (nCmailGames) {
13178               case 1:
13179                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13180                 break;
13181
13182               case 2:
13183                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13184                 break;
13185
13186               default:
13187                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13188                          nCmailGames);
13189                 break;
13190             }
13191         } else {
13192             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13193               case 1:
13194                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13195                          string);
13196                 break;
13197
13198               case 0:
13199                 if (nCmailResults == nCmailGames) {
13200                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13201                 } else {
13202                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13203                 }
13204                 break;
13205
13206               default:
13207                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13208                          string);
13209             }
13210         }
13211     }
13212     return cmailMsg;
13213 #endif /* WIN32 */
13214 }
13215
13216 void
13217 ResetGameEvent ()
13218 {
13219     if (gameMode == Training)
13220       SetTrainingModeOff();
13221
13222     Reset(TRUE, TRUE);
13223     cmailMsgLoaded = FALSE;
13224     if (appData.icsActive) {
13225       SendToICS(ics_prefix);
13226       SendToICS("refresh\n");
13227     }
13228 }
13229
13230 void
13231 ExitEvent (int status)
13232 {
13233     exiting++;
13234     if (exiting > 2) {
13235       /* Give up on clean exit */
13236       exit(status);
13237     }
13238     if (exiting > 1) {
13239       /* Keep trying for clean exit */
13240       return;
13241     }
13242
13243     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13244
13245     if (telnetISR != NULL) {
13246       RemoveInputSource(telnetISR);
13247     }
13248     if (icsPR != NoProc) {
13249       DestroyChildProcess(icsPR, TRUE);
13250     }
13251
13252     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13253     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13254
13255     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13256     /* make sure this other one finishes before killing it!                  */
13257     if(endingGame) { int count = 0;
13258         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13259         while(endingGame && count++ < 10) DoSleep(1);
13260         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13261     }
13262
13263     /* Kill off chess programs */
13264     if (first.pr != NoProc) {
13265         ExitAnalyzeMode();
13266
13267         DoSleep( appData.delayBeforeQuit );
13268         SendToProgram("quit\n", &first);
13269         DoSleep( appData.delayAfterQuit );
13270         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13271     }
13272     if (second.pr != NoProc) {
13273         DoSleep( appData.delayBeforeQuit );
13274         SendToProgram("quit\n", &second);
13275         DoSleep( appData.delayAfterQuit );
13276         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13277     }
13278     if (first.isr != NULL) {
13279         RemoveInputSource(first.isr);
13280     }
13281     if (second.isr != NULL) {
13282         RemoveInputSource(second.isr);
13283     }
13284
13285     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13286     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13287
13288     ShutDownFrontEnd();
13289     exit(status);
13290 }
13291
13292 void
13293 PauseEvent ()
13294 {
13295     if (appData.debugMode)
13296         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13297     if (pausing) {
13298         pausing = FALSE;
13299         ModeHighlight();
13300         if (gameMode == MachinePlaysWhite ||
13301             gameMode == MachinePlaysBlack) {
13302             StartClocks();
13303         } else {
13304             DisplayBothClocks();
13305         }
13306         if (gameMode == PlayFromGameFile) {
13307             if (appData.timeDelay >= 0)
13308                 AutoPlayGameLoop();
13309         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13310             Reset(FALSE, TRUE);
13311             SendToICS(ics_prefix);
13312             SendToICS("refresh\n");
13313         } else if (currentMove < forwardMostMove) {
13314             ForwardInner(forwardMostMove);
13315         }
13316         pauseExamInvalid = FALSE;
13317     } else {
13318         switch (gameMode) {
13319           default:
13320             return;
13321           case IcsExamining:
13322             pauseExamForwardMostMove = forwardMostMove;
13323             pauseExamInvalid = FALSE;
13324             /* fall through */
13325           case IcsObserving:
13326           case IcsPlayingWhite:
13327           case IcsPlayingBlack:
13328             pausing = TRUE;
13329             ModeHighlight();
13330             return;
13331           case PlayFromGameFile:
13332             (void) StopLoadGameTimer();
13333             pausing = TRUE;
13334             ModeHighlight();
13335             break;
13336           case BeginningOfGame:
13337             if (appData.icsActive) return;
13338             /* else fall through */
13339           case MachinePlaysWhite:
13340           case MachinePlaysBlack:
13341           case TwoMachinesPlay:
13342             if (forwardMostMove == 0)
13343               return;           /* don't pause if no one has moved */
13344             if ((gameMode == MachinePlaysWhite &&
13345                  !WhiteOnMove(forwardMostMove)) ||
13346                 (gameMode == MachinePlaysBlack &&
13347                  WhiteOnMove(forwardMostMove))) {
13348                 StopClocks();
13349             }
13350             pausing = TRUE;
13351             ModeHighlight();
13352             break;
13353         }
13354     }
13355 }
13356
13357 void
13358 EditCommentEvent ()
13359 {
13360     char title[MSG_SIZ];
13361
13362     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13363       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13364     } else {
13365       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13366                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13367                parseList[currentMove - 1]);
13368     }
13369
13370     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13371 }
13372
13373
13374 void
13375 EditTagsEvent ()
13376 {
13377     char *tags = PGNTags(&gameInfo);
13378     bookUp = FALSE;
13379     EditTagsPopUp(tags, NULL);
13380     free(tags);
13381 }
13382
13383 void
13384 ToggleSecond ()
13385 {
13386   if(second.analyzing) {
13387     SendToProgram("exit\n", &second);
13388     second.analyzing = FALSE;
13389   } else {
13390     if (second.pr == NoProc) StartChessProgram(&second);
13391     InitChessProgram(&second, FALSE);
13392     FeedMovesToProgram(&second, currentMove);
13393
13394     SendToProgram("analyze\n", &second);
13395     second.analyzing = TRUE;
13396   }
13397 }
13398
13399 void
13400 AnalyzeModeEvent ()
13401 {
13402     if (gameMode == AnalyzeMode) { ToggleSecond(); return; }
13403     if (appData.noChessProgram || gameMode == AnalyzeMode)
13404       return;
13405
13406     if (gameMode != AnalyzeFile) {
13407         if (!appData.icsEngineAnalyze) {
13408                EditGameEvent();
13409                if (gameMode != EditGame) return;
13410         }
13411         ResurrectChessProgram();
13412         SendToProgram("analyze\n", &first);
13413         first.analyzing = TRUE;
13414         /*first.maybeThinking = TRUE;*/
13415         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13416         EngineOutputPopUp();
13417     }
13418     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13419     pausing = FALSE;
13420     ModeHighlight();
13421     SetGameInfo();
13422
13423     StartAnalysisClock();
13424     GetTimeMark(&lastNodeCountTime);
13425     lastNodeCount = 0;
13426 }
13427
13428 void
13429 AnalyzeFileEvent ()
13430 {
13431     if (appData.noChessProgram || gameMode == AnalyzeFile)
13432       return;
13433
13434     if (gameMode != AnalyzeMode) {
13435         EditGameEvent();
13436         if (gameMode != EditGame) return;
13437         ResurrectChessProgram();
13438         SendToProgram("analyze\n", &first);
13439         first.analyzing = TRUE;
13440         /*first.maybeThinking = TRUE;*/
13441         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13442         EngineOutputPopUp();
13443     }
13444     gameMode = AnalyzeFile;
13445     pausing = FALSE;
13446     ModeHighlight();
13447     SetGameInfo();
13448
13449     StartAnalysisClock();
13450     GetTimeMark(&lastNodeCountTime);
13451     lastNodeCount = 0;
13452     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13453 }
13454
13455 void
13456 MachineWhiteEvent ()
13457 {
13458     char buf[MSG_SIZ];
13459     char *bookHit = NULL;
13460
13461     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13462       return;
13463
13464
13465     if (gameMode == PlayFromGameFile ||
13466         gameMode == TwoMachinesPlay  ||
13467         gameMode == Training         ||
13468         gameMode == AnalyzeMode      ||
13469         gameMode == EndOfGame)
13470         EditGameEvent();
13471
13472     if (gameMode == EditPosition)
13473         EditPositionDone(TRUE);
13474
13475     if (!WhiteOnMove(currentMove)) {
13476         DisplayError(_("It is not White's turn"), 0);
13477         return;
13478     }
13479
13480     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13481       ExitAnalyzeMode();
13482
13483     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13484         gameMode == AnalyzeFile)
13485         TruncateGame();
13486
13487     ResurrectChessProgram();    /* in case it isn't running */
13488     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13489         gameMode = MachinePlaysWhite;
13490         ResetClocks();
13491     } else
13492     gameMode = MachinePlaysWhite;
13493     pausing = FALSE;
13494     ModeHighlight();
13495     SetGameInfo();
13496     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13497     DisplayTitle(buf);
13498     if (first.sendName) {
13499       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13500       SendToProgram(buf, &first);
13501     }
13502     if (first.sendTime) {
13503       if (first.useColors) {
13504         SendToProgram("black\n", &first); /*gnu kludge*/
13505       }
13506       SendTimeRemaining(&first, TRUE);
13507     }
13508     if (first.useColors) {
13509       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13510     }
13511     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13512     SetMachineThinkingEnables();
13513     first.maybeThinking = TRUE;
13514     StartClocks();
13515     firstMove = FALSE;
13516
13517     if (appData.autoFlipView && !flipView) {
13518       flipView = !flipView;
13519       DrawPosition(FALSE, NULL);
13520       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13521     }
13522
13523     if(bookHit) { // [HGM] book: simulate book reply
13524         static char bookMove[MSG_SIZ]; // a bit generous?
13525
13526         programStats.nodes = programStats.depth = programStats.time =
13527         programStats.score = programStats.got_only_move = 0;
13528         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13529
13530         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13531         strcat(bookMove, bookHit);
13532         HandleMachineMove(bookMove, &first);
13533     }
13534 }
13535
13536 void
13537 MachineBlackEvent ()
13538 {
13539   char buf[MSG_SIZ];
13540   char *bookHit = NULL;
13541
13542     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13543         return;
13544
13545
13546     if (gameMode == PlayFromGameFile ||
13547         gameMode == TwoMachinesPlay  ||
13548         gameMode == Training         ||
13549         gameMode == AnalyzeMode      ||
13550         gameMode == EndOfGame)
13551         EditGameEvent();
13552
13553     if (gameMode == EditPosition)
13554         EditPositionDone(TRUE);
13555
13556     if (WhiteOnMove(currentMove)) {
13557         DisplayError(_("It is not Black's turn"), 0);
13558         return;
13559     }
13560
13561     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13562       ExitAnalyzeMode();
13563
13564     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13565         gameMode == AnalyzeFile)
13566         TruncateGame();
13567
13568     ResurrectChessProgram();    /* in case it isn't running */
13569     gameMode = MachinePlaysBlack;
13570     pausing = FALSE;
13571     ModeHighlight();
13572     SetGameInfo();
13573     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13574     DisplayTitle(buf);
13575     if (first.sendName) {
13576       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13577       SendToProgram(buf, &first);
13578     }
13579     if (first.sendTime) {
13580       if (first.useColors) {
13581         SendToProgram("white\n", &first); /*gnu kludge*/
13582       }
13583       SendTimeRemaining(&first, FALSE);
13584     }
13585     if (first.useColors) {
13586       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13587     }
13588     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13589     SetMachineThinkingEnables();
13590     first.maybeThinking = TRUE;
13591     StartClocks();
13592
13593     if (appData.autoFlipView && flipView) {
13594       flipView = !flipView;
13595       DrawPosition(FALSE, NULL);
13596       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13597     }
13598     if(bookHit) { // [HGM] book: simulate book reply
13599         static char bookMove[MSG_SIZ]; // a bit generous?
13600
13601         programStats.nodes = programStats.depth = programStats.time =
13602         programStats.score = programStats.got_only_move = 0;
13603         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13604
13605         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13606         strcat(bookMove, bookHit);
13607         HandleMachineMove(bookMove, &first);
13608     }
13609 }
13610
13611
13612 void
13613 DisplayTwoMachinesTitle ()
13614 {
13615     char buf[MSG_SIZ];
13616     if (appData.matchGames > 0) {
13617         if(appData.tourneyFile[0]) {
13618           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13619                    gameInfo.white, _("vs."), gameInfo.black,
13620                    nextGame+1, appData.matchGames+1,
13621                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13622         } else 
13623         if (first.twoMachinesColor[0] == 'w') {
13624           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13625                    gameInfo.white, _("vs."),  gameInfo.black,
13626                    first.matchWins, second.matchWins,
13627                    matchGame - 1 - (first.matchWins + second.matchWins));
13628         } else {
13629           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13630                    gameInfo.white, _("vs."), gameInfo.black,
13631                    second.matchWins, first.matchWins,
13632                    matchGame - 1 - (first.matchWins + second.matchWins));
13633         }
13634     } else {
13635       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13636     }
13637     DisplayTitle(buf);
13638 }
13639
13640 void
13641 SettingsMenuIfReady ()
13642 {
13643   if (second.lastPing != second.lastPong) {
13644     DisplayMessage("", _("Waiting for second chess program"));
13645     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13646     return;
13647   }
13648   ThawUI();
13649   DisplayMessage("", "");
13650   SettingsPopUp(&second);
13651 }
13652
13653 int
13654 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13655 {
13656     char buf[MSG_SIZ];
13657     if (cps->pr == NoProc) {
13658         StartChessProgram(cps);
13659         if (cps->protocolVersion == 1) {
13660           retry();
13661         } else {
13662           /* kludge: allow timeout for initial "feature" command */
13663           FreezeUI();
13664           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13665           DisplayMessage("", buf);
13666           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13667         }
13668         return 1;
13669     }
13670     return 0;
13671 }
13672
13673 void
13674 TwoMachinesEvent P((void))
13675 {
13676     int i;
13677     char buf[MSG_SIZ];
13678     ChessProgramState *onmove;
13679     char *bookHit = NULL;
13680     static int stalling = 0;
13681     TimeMark now;
13682     long wait;
13683
13684     if (appData.noChessProgram) return;
13685
13686     switch (gameMode) {
13687       case TwoMachinesPlay:
13688         return;
13689       case MachinePlaysWhite:
13690       case MachinePlaysBlack:
13691         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13692             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13693             return;
13694         }
13695         /* fall through */
13696       case BeginningOfGame:
13697       case PlayFromGameFile:
13698       case EndOfGame:
13699         EditGameEvent();
13700         if (gameMode != EditGame) return;
13701         break;
13702       case EditPosition:
13703         EditPositionDone(TRUE);
13704         break;
13705       case AnalyzeMode:
13706       case AnalyzeFile:
13707         ExitAnalyzeMode();
13708         break;
13709       case EditGame:
13710       default:
13711         break;
13712     }
13713
13714 //    forwardMostMove = currentMove;
13715     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13716
13717     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13718
13719     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13720     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13721       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13722       return;
13723     }
13724
13725     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13726         DisplayError("second engine does not play this", 0);
13727         return;
13728     }
13729
13730     if(!stalling) {
13731       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13732       SendToProgram("force\n", &second);
13733       stalling = 1;
13734       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13735       return;
13736     }
13737     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13738     if(appData.matchPause>10000 || appData.matchPause<10)
13739                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13740     wait = SubtractTimeMarks(&now, &pauseStart);
13741     if(wait < appData.matchPause) {
13742         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13743         return;
13744     }
13745     // we are now committed to starting the game
13746     stalling = 0;
13747     DisplayMessage("", "");
13748     if (startedFromSetupPosition) {
13749         SendBoard(&second, backwardMostMove);
13750     if (appData.debugMode) {
13751         fprintf(debugFP, "Two Machines\n");
13752     }
13753     }
13754     for (i = backwardMostMove; i < forwardMostMove; i++) {
13755         SendMoveToProgram(i, &second);
13756     }
13757
13758     gameMode = TwoMachinesPlay;
13759     pausing = FALSE;
13760     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13761     SetGameInfo();
13762     DisplayTwoMachinesTitle();
13763     firstMove = TRUE;
13764     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13765         onmove = &first;
13766     } else {
13767         onmove = &second;
13768     }
13769     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13770     SendToProgram(first.computerString, &first);
13771     if (first.sendName) {
13772       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13773       SendToProgram(buf, &first);
13774     }
13775     SendToProgram(second.computerString, &second);
13776     if (second.sendName) {
13777       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13778       SendToProgram(buf, &second);
13779     }
13780
13781     ResetClocks();
13782     if (!first.sendTime || !second.sendTime) {
13783         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13784         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13785     }
13786     if (onmove->sendTime) {
13787       if (onmove->useColors) {
13788         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13789       }
13790       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13791     }
13792     if (onmove->useColors) {
13793       SendToProgram(onmove->twoMachinesColor, onmove);
13794     }
13795     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13796 //    SendToProgram("go\n", onmove);
13797     onmove->maybeThinking = TRUE;
13798     SetMachineThinkingEnables();
13799
13800     StartClocks();
13801
13802     if(bookHit) { // [HGM] book: simulate book reply
13803         static char bookMove[MSG_SIZ]; // a bit generous?
13804
13805         programStats.nodes = programStats.depth = programStats.time =
13806         programStats.score = programStats.got_only_move = 0;
13807         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13808
13809         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13810         strcat(bookMove, bookHit);
13811         savedMessage = bookMove; // args for deferred call
13812         savedState = onmove;
13813         ScheduleDelayedEvent(DeferredBookMove, 1);
13814     }
13815 }
13816
13817 void
13818 TrainingEvent ()
13819 {
13820     if (gameMode == Training) {
13821       SetTrainingModeOff();
13822       gameMode = PlayFromGameFile;
13823       DisplayMessage("", _("Training mode off"));
13824     } else {
13825       gameMode = Training;
13826       animateTraining = appData.animate;
13827
13828       /* make sure we are not already at the end of the game */
13829       if (currentMove < forwardMostMove) {
13830         SetTrainingModeOn();
13831         DisplayMessage("", _("Training mode on"));
13832       } else {
13833         gameMode = PlayFromGameFile;
13834         DisplayError(_("Already at end of game"), 0);
13835       }
13836     }
13837     ModeHighlight();
13838 }
13839
13840 void
13841 IcsClientEvent ()
13842 {
13843     if (!appData.icsActive) return;
13844     switch (gameMode) {
13845       case IcsPlayingWhite:
13846       case IcsPlayingBlack:
13847       case IcsObserving:
13848       case IcsIdle:
13849       case BeginningOfGame:
13850       case IcsExamining:
13851         return;
13852
13853       case EditGame:
13854         break;
13855
13856       case EditPosition:
13857         EditPositionDone(TRUE);
13858         break;
13859
13860       case AnalyzeMode:
13861       case AnalyzeFile:
13862         ExitAnalyzeMode();
13863         break;
13864
13865       default:
13866         EditGameEvent();
13867         break;
13868     }
13869
13870     gameMode = IcsIdle;
13871     ModeHighlight();
13872     return;
13873 }
13874
13875 void
13876 EditGameEvent ()
13877 {
13878     int i;
13879
13880     switch (gameMode) {
13881       case Training:
13882         SetTrainingModeOff();
13883         break;
13884       case MachinePlaysWhite:
13885       case MachinePlaysBlack:
13886       case BeginningOfGame:
13887         SendToProgram("force\n", &first);
13888         SetUserThinkingEnables();
13889         break;
13890       case PlayFromGameFile:
13891         (void) StopLoadGameTimer();
13892         if (gameFileFP != NULL) {
13893             gameFileFP = NULL;
13894         }
13895         break;
13896       case EditPosition:
13897         EditPositionDone(TRUE);
13898         break;
13899       case AnalyzeMode:
13900       case AnalyzeFile:
13901         ExitAnalyzeMode();
13902         SendToProgram("force\n", &first);
13903         break;
13904       case TwoMachinesPlay:
13905         GameEnds(EndOfFile, NULL, GE_PLAYER);
13906         ResurrectChessProgram();
13907         SetUserThinkingEnables();
13908         break;
13909       case EndOfGame:
13910         ResurrectChessProgram();
13911         break;
13912       case IcsPlayingBlack:
13913       case IcsPlayingWhite:
13914         DisplayError(_("Warning: You are still playing a game"), 0);
13915         break;
13916       case IcsObserving:
13917         DisplayError(_("Warning: You are still observing a game"), 0);
13918         break;
13919       case IcsExamining:
13920         DisplayError(_("Warning: You are still examining a game"), 0);
13921         break;
13922       case IcsIdle:
13923         break;
13924       case EditGame:
13925       default:
13926         return;
13927     }
13928
13929     pausing = FALSE;
13930     StopClocks();
13931     first.offeredDraw = second.offeredDraw = 0;
13932
13933     if (gameMode == PlayFromGameFile) {
13934         whiteTimeRemaining = timeRemaining[0][currentMove];
13935         blackTimeRemaining = timeRemaining[1][currentMove];
13936         DisplayTitle("");
13937     }
13938
13939     if (gameMode == MachinePlaysWhite ||
13940         gameMode == MachinePlaysBlack ||
13941         gameMode == TwoMachinesPlay ||
13942         gameMode == EndOfGame) {
13943         i = forwardMostMove;
13944         while (i > currentMove) {
13945             SendToProgram("undo\n", &first);
13946             i--;
13947         }
13948         if(!adjustedClock) {
13949         whiteTimeRemaining = timeRemaining[0][currentMove];
13950         blackTimeRemaining = timeRemaining[1][currentMove];
13951         DisplayBothClocks();
13952         }
13953         if (whiteFlag || blackFlag) {
13954             whiteFlag = blackFlag = 0;
13955         }
13956         DisplayTitle("");
13957     }
13958
13959     gameMode = EditGame;
13960     ModeHighlight();
13961     SetGameInfo();
13962 }
13963
13964
13965 void
13966 EditPositionEvent ()
13967 {
13968     if (gameMode == EditPosition) {
13969         EditGameEvent();
13970         return;
13971     }
13972
13973     EditGameEvent();
13974     if (gameMode != EditGame) return;
13975
13976     gameMode = EditPosition;
13977     ModeHighlight();
13978     SetGameInfo();
13979     if (currentMove > 0)
13980       CopyBoard(boards[0], boards[currentMove]);
13981
13982     blackPlaysFirst = !WhiteOnMove(currentMove);
13983     ResetClocks();
13984     currentMove = forwardMostMove = backwardMostMove = 0;
13985     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13986     DisplayMove(-1);
13987     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13988 }
13989
13990 void
13991 ExitAnalyzeMode ()
13992 {
13993     /* [DM] icsEngineAnalyze - possible call from other functions */
13994     if (appData.icsEngineAnalyze) {
13995         appData.icsEngineAnalyze = FALSE;
13996
13997         DisplayMessage("",_("Close ICS engine analyze..."));
13998     }
13999     if (first.analysisSupport && first.analyzing) {
14000       SendToProgram("exit\n", &first);
14001       first.analyzing = FALSE;
14002     }
14003     if (second.analyzing) {
14004       SendToProgram("exit\n", &second);
14005       second.analyzing = FALSE;
14006     }
14007     thinkOutput[0] = NULLCHAR;
14008 }
14009
14010 void
14011 EditPositionDone (Boolean fakeRights)
14012 {
14013     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14014
14015     startedFromSetupPosition = TRUE;
14016     InitChessProgram(&first, FALSE);
14017     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14018       boards[0][EP_STATUS] = EP_NONE;
14019       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14020       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14021         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14022         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14023       } else boards[0][CASTLING][2] = NoRights;
14024       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14025         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14026         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14027       } else boards[0][CASTLING][5] = NoRights;
14028       if(gameInfo.variant == VariantSChess) {
14029         int i;
14030         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14031           boards[0][VIRGIN][i] = 0;
14032           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14033           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14034         }
14035       }
14036     }
14037     SendToProgram("force\n", &first);
14038     if (blackPlaysFirst) {
14039         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14040         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14041         currentMove = forwardMostMove = backwardMostMove = 1;
14042         CopyBoard(boards[1], boards[0]);
14043     } else {
14044         currentMove = forwardMostMove = backwardMostMove = 0;
14045     }
14046     SendBoard(&first, forwardMostMove);
14047     if (appData.debugMode) {
14048         fprintf(debugFP, "EditPosDone\n");
14049     }
14050     DisplayTitle("");
14051     DisplayMessage("", "");
14052     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14053     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14054     gameMode = EditGame;
14055     ModeHighlight();
14056     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14057     ClearHighlights(); /* [AS] */
14058 }
14059
14060 /* Pause for `ms' milliseconds */
14061 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14062 void
14063 TimeDelay (long ms)
14064 {
14065     TimeMark m1, m2;
14066
14067     GetTimeMark(&m1);
14068     do {
14069         GetTimeMark(&m2);
14070     } while (SubtractTimeMarks(&m2, &m1) < ms);
14071 }
14072
14073 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14074 void
14075 SendMultiLineToICS (char *buf)
14076 {
14077     char temp[MSG_SIZ+1], *p;
14078     int len;
14079
14080     len = strlen(buf);
14081     if (len > MSG_SIZ)
14082       len = MSG_SIZ;
14083
14084     strncpy(temp, buf, len);
14085     temp[len] = 0;
14086
14087     p = temp;
14088     while (*p) {
14089         if (*p == '\n' || *p == '\r')
14090           *p = ' ';
14091         ++p;
14092     }
14093
14094     strcat(temp, "\n");
14095     SendToICS(temp);
14096     SendToPlayer(temp, strlen(temp));
14097 }
14098
14099 void
14100 SetWhiteToPlayEvent ()
14101 {
14102     if (gameMode == EditPosition) {
14103         blackPlaysFirst = FALSE;
14104         DisplayBothClocks();    /* works because currentMove is 0 */
14105     } else if (gameMode == IcsExamining) {
14106         SendToICS(ics_prefix);
14107         SendToICS("tomove white\n");
14108     }
14109 }
14110
14111 void
14112 SetBlackToPlayEvent ()
14113 {
14114     if (gameMode == EditPosition) {
14115         blackPlaysFirst = TRUE;
14116         currentMove = 1;        /* kludge */
14117         DisplayBothClocks();
14118         currentMove = 0;
14119     } else if (gameMode == IcsExamining) {
14120         SendToICS(ics_prefix);
14121         SendToICS("tomove black\n");
14122     }
14123 }
14124
14125 void
14126 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14127 {
14128     char buf[MSG_SIZ];
14129     ChessSquare piece = boards[0][y][x];
14130
14131     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14132
14133     switch (selection) {
14134       case ClearBoard:
14135         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14136             SendToICS(ics_prefix);
14137             SendToICS("bsetup clear\n");
14138         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14139             SendToICS(ics_prefix);
14140             SendToICS("clearboard\n");
14141         } else {
14142             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14143                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14144                 for (y = 0; y < BOARD_HEIGHT; y++) {
14145                     if (gameMode == IcsExamining) {
14146                         if (boards[currentMove][y][x] != EmptySquare) {
14147                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14148                                     AAA + x, ONE + y);
14149                             SendToICS(buf);
14150                         }
14151                     } else {
14152                         boards[0][y][x] = p;
14153                     }
14154                 }
14155             }
14156         }
14157         if (gameMode == EditPosition) {
14158             DrawPosition(FALSE, boards[0]);
14159         }
14160         break;
14161
14162       case WhitePlay:
14163         SetWhiteToPlayEvent();
14164         break;
14165
14166       case BlackPlay:
14167         SetBlackToPlayEvent();
14168         break;
14169
14170       case EmptySquare:
14171         if (gameMode == IcsExamining) {
14172             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14173             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14174             SendToICS(buf);
14175         } else {
14176             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14177                 if(x == BOARD_LEFT-2) {
14178                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14179                     boards[0][y][1] = 0;
14180                 } else
14181                 if(x == BOARD_RGHT+1) {
14182                     if(y >= gameInfo.holdingsSize) break;
14183                     boards[0][y][BOARD_WIDTH-2] = 0;
14184                 } else break;
14185             }
14186             boards[0][y][x] = EmptySquare;
14187             DrawPosition(FALSE, boards[0]);
14188         }
14189         break;
14190
14191       case PromotePiece:
14192         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14193            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14194             selection = (ChessSquare) (PROMOTED piece);
14195         } else if(piece == EmptySquare) selection = WhiteSilver;
14196         else selection = (ChessSquare)((int)piece - 1);
14197         goto defaultlabel;
14198
14199       case DemotePiece:
14200         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14201            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14202             selection = (ChessSquare) (DEMOTED piece);
14203         } else if(piece == EmptySquare) selection = BlackSilver;
14204         else selection = (ChessSquare)((int)piece + 1);
14205         goto defaultlabel;
14206
14207       case WhiteQueen:
14208       case BlackQueen:
14209         if(gameInfo.variant == VariantShatranj ||
14210            gameInfo.variant == VariantXiangqi  ||
14211            gameInfo.variant == VariantCourier  ||
14212            gameInfo.variant == VariantMakruk     )
14213             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14214         goto defaultlabel;
14215
14216       case WhiteKing:
14217       case BlackKing:
14218         if(gameInfo.variant == VariantXiangqi)
14219             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14220         if(gameInfo.variant == VariantKnightmate)
14221             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14222       default:
14223         defaultlabel:
14224         if (gameMode == IcsExamining) {
14225             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14226             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14227                      PieceToChar(selection), AAA + x, ONE + y);
14228             SendToICS(buf);
14229         } else {
14230             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14231                 int n;
14232                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14233                     n = PieceToNumber(selection - BlackPawn);
14234                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14235                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14236                     boards[0][BOARD_HEIGHT-1-n][1]++;
14237                 } else
14238                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14239                     n = PieceToNumber(selection);
14240                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14241                     boards[0][n][BOARD_WIDTH-1] = selection;
14242                     boards[0][n][BOARD_WIDTH-2]++;
14243                 }
14244             } else
14245             boards[0][y][x] = selection;
14246             DrawPosition(TRUE, boards[0]);
14247             ClearHighlights();
14248             fromX = fromY = -1;
14249         }
14250         break;
14251     }
14252 }
14253
14254
14255 void
14256 DropMenuEvent (ChessSquare selection, int x, int y)
14257 {
14258     ChessMove moveType;
14259
14260     switch (gameMode) {
14261       case IcsPlayingWhite:
14262       case MachinePlaysBlack:
14263         if (!WhiteOnMove(currentMove)) {
14264             DisplayMoveError(_("It is Black's turn"));
14265             return;
14266         }
14267         moveType = WhiteDrop;
14268         break;
14269       case IcsPlayingBlack:
14270       case MachinePlaysWhite:
14271         if (WhiteOnMove(currentMove)) {
14272             DisplayMoveError(_("It is White's turn"));
14273             return;
14274         }
14275         moveType = BlackDrop;
14276         break;
14277       case EditGame:
14278         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14279         break;
14280       default:
14281         return;
14282     }
14283
14284     if (moveType == BlackDrop && selection < BlackPawn) {
14285       selection = (ChessSquare) ((int) selection
14286                                  + (int) BlackPawn - (int) WhitePawn);
14287     }
14288     if (boards[currentMove][y][x] != EmptySquare) {
14289         DisplayMoveError(_("That square is occupied"));
14290         return;
14291     }
14292
14293     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14294 }
14295
14296 void
14297 AcceptEvent ()
14298 {
14299     /* Accept a pending offer of any kind from opponent */
14300
14301     if (appData.icsActive) {
14302         SendToICS(ics_prefix);
14303         SendToICS("accept\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             TruncateGame();
14310             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14311             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14312         } else {
14313             DisplayError(_("There is no pending offer on this move"), 0);
14314             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14315         }
14316     } else {
14317         /* Not used for offers from chess program */
14318     }
14319 }
14320
14321 void
14322 DeclineEvent ()
14323 {
14324     /* Decline a pending offer of any kind from opponent */
14325
14326     if (appData.icsActive) {
14327         SendToICS(ics_prefix);
14328         SendToICS("decline\n");
14329     } else if (cmailMsgLoaded) {
14330         if (currentMove == cmailOldMove &&
14331             commentList[cmailOldMove] != NULL &&
14332             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14333                    "Black offers a draw" : "White offers a draw")) {
14334 #ifdef NOTDEF
14335             AppendComment(cmailOldMove, "Draw declined", TRUE);
14336             DisplayComment(cmailOldMove - 1, "Draw declined");
14337 #endif /*NOTDEF*/
14338         } else {
14339             DisplayError(_("There is no pending offer on this move"), 0);
14340         }
14341     } else {
14342         /* Not used for offers from chess program */
14343     }
14344 }
14345
14346 void
14347 RematchEvent ()
14348 {
14349     /* Issue ICS rematch command */
14350     if (appData.icsActive) {
14351         SendToICS(ics_prefix);
14352         SendToICS("rematch\n");
14353     }
14354 }
14355
14356 void
14357 CallFlagEvent ()
14358 {
14359     /* Call your opponent's flag (claim a win on time) */
14360     if (appData.icsActive) {
14361         SendToICS(ics_prefix);
14362         SendToICS("flag\n");
14363     } else {
14364         switch (gameMode) {
14365           default:
14366             return;
14367           case MachinePlaysWhite:
14368             if (whiteFlag) {
14369                 if (blackFlag)
14370                   GameEnds(GameIsDrawn, "Both players ran out of time",
14371                            GE_PLAYER);
14372                 else
14373                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14374             } else {
14375                 DisplayError(_("Your opponent is not out of time"), 0);
14376             }
14377             break;
14378           case MachinePlaysBlack:
14379             if (blackFlag) {
14380                 if (whiteFlag)
14381                   GameEnds(GameIsDrawn, "Both players ran out of time",
14382                            GE_PLAYER);
14383                 else
14384                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14385             } else {
14386                 DisplayError(_("Your opponent is not out of time"), 0);
14387             }
14388             break;
14389         }
14390     }
14391 }
14392
14393 void
14394 ClockClick (int which)
14395 {       // [HGM] code moved to back-end from winboard.c
14396         if(which) { // black clock
14397           if (gameMode == EditPosition || gameMode == IcsExamining) {
14398             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14399             SetBlackToPlayEvent();
14400           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14401           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14402           } else if (shiftKey) {
14403             AdjustClock(which, -1);
14404           } else if (gameMode == IcsPlayingWhite ||
14405                      gameMode == MachinePlaysBlack) {
14406             CallFlagEvent();
14407           }
14408         } else { // white clock
14409           if (gameMode == EditPosition || gameMode == IcsExamining) {
14410             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14411             SetWhiteToPlayEvent();
14412           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14413           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14414           } else if (shiftKey) {
14415             AdjustClock(which, -1);
14416           } else if (gameMode == IcsPlayingBlack ||
14417                    gameMode == MachinePlaysWhite) {
14418             CallFlagEvent();
14419           }
14420         }
14421 }
14422
14423 void
14424 DrawEvent ()
14425 {
14426     /* Offer draw or accept pending draw offer from opponent */
14427
14428     if (appData.icsActive) {
14429         /* Note: tournament rules require draw offers to be
14430            made after you make your move but before you punch
14431            your clock.  Currently ICS doesn't let you do that;
14432            instead, you immediately punch your clock after making
14433            a move, but you can offer a draw at any time. */
14434
14435         SendToICS(ics_prefix);
14436         SendToICS("draw\n");
14437         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14438     } else if (cmailMsgLoaded) {
14439         if (currentMove == cmailOldMove &&
14440             commentList[cmailOldMove] != NULL &&
14441             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14442                    "Black offers a draw" : "White offers a draw")) {
14443             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14444             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14445         } else if (currentMove == cmailOldMove + 1) {
14446             char *offer = WhiteOnMove(cmailOldMove) ?
14447               "White offers a draw" : "Black offers a draw";
14448             AppendComment(currentMove, offer, TRUE);
14449             DisplayComment(currentMove - 1, offer);
14450             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14451         } else {
14452             DisplayError(_("You must make your move before offering a draw"), 0);
14453             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14454         }
14455     } else if (first.offeredDraw) {
14456         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14457     } else {
14458         if (first.sendDrawOffers) {
14459             SendToProgram("draw\n", &first);
14460             userOfferedDraw = TRUE;
14461         }
14462     }
14463 }
14464
14465 void
14466 AdjournEvent ()
14467 {
14468     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14469
14470     if (appData.icsActive) {
14471         SendToICS(ics_prefix);
14472         SendToICS("adjourn\n");
14473     } else {
14474         /* Currently GNU Chess doesn't offer or accept Adjourns */
14475     }
14476 }
14477
14478
14479 void
14480 AbortEvent ()
14481 {
14482     /* Offer Abort or accept pending Abort offer from opponent */
14483
14484     if (appData.icsActive) {
14485         SendToICS(ics_prefix);
14486         SendToICS("abort\n");
14487     } else {
14488         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14489     }
14490 }
14491
14492 void
14493 ResignEvent ()
14494 {
14495     /* Resign.  You can do this even if it's not your turn. */
14496
14497     if (appData.icsActive) {
14498         SendToICS(ics_prefix);
14499         SendToICS("resign\n");
14500     } else {
14501         switch (gameMode) {
14502           case MachinePlaysWhite:
14503             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14504             break;
14505           case MachinePlaysBlack:
14506             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14507             break;
14508           case EditGame:
14509             if (cmailMsgLoaded) {
14510                 TruncateGame();
14511                 if (WhiteOnMove(cmailOldMove)) {
14512                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14513                 } else {
14514                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14515                 }
14516                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14517             }
14518             break;
14519           default:
14520             break;
14521         }
14522     }
14523 }
14524
14525
14526 void
14527 StopObservingEvent ()
14528 {
14529     /* Stop observing current games */
14530     SendToICS(ics_prefix);
14531     SendToICS("unobserve\n");
14532 }
14533
14534 void
14535 StopExaminingEvent ()
14536 {
14537     /* Stop observing current game */
14538     SendToICS(ics_prefix);
14539     SendToICS("unexamine\n");
14540 }
14541
14542 void
14543 ForwardInner (int target)
14544 {
14545     int limit; int oldSeekGraphUp = seekGraphUp;
14546
14547     if (appData.debugMode)
14548         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14549                 target, currentMove, forwardMostMove);
14550
14551     if (gameMode == EditPosition)
14552       return;
14553
14554     seekGraphUp = FALSE;
14555     MarkTargetSquares(1);
14556
14557     if (gameMode == PlayFromGameFile && !pausing)
14558       PauseEvent();
14559
14560     if (gameMode == IcsExamining && pausing)
14561       limit = pauseExamForwardMostMove;
14562     else
14563       limit = forwardMostMove;
14564
14565     if (target > limit) target = limit;
14566
14567     if (target > 0 && moveList[target - 1][0]) {
14568         int fromX, fromY, toX, toY;
14569         toX = moveList[target - 1][2] - AAA;
14570         toY = moveList[target - 1][3] - ONE;
14571         if (moveList[target - 1][1] == '@') {
14572             if (appData.highlightLastMove) {
14573                 SetHighlights(-1, -1, toX, toY);
14574             }
14575         } else {
14576             fromX = moveList[target - 1][0] - AAA;
14577             fromY = moveList[target - 1][1] - ONE;
14578             if (target == currentMove + 1) {
14579                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14580             }
14581             if (appData.highlightLastMove) {
14582                 SetHighlights(fromX, fromY, toX, toY);
14583             }
14584         }
14585     }
14586     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14587         gameMode == Training || gameMode == PlayFromGameFile ||
14588         gameMode == AnalyzeFile) {
14589       if(target != currentMove && second.analyzing) ToggleSecond(); // for now, we just stop second analyzing engine
14590         while (currentMove < target) {
14591             SendMoveToProgram(currentMove++, &first);
14592         }
14593     } else {
14594         currentMove = target;
14595     }
14596
14597     if (gameMode == EditGame || gameMode == EndOfGame) {
14598         whiteTimeRemaining = timeRemaining[0][currentMove];
14599         blackTimeRemaining = timeRemaining[1][currentMove];
14600     }
14601     DisplayBothClocks();
14602     DisplayMove(currentMove - 1);
14603     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14604     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14605     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14606         DisplayComment(currentMove - 1, commentList[currentMove]);
14607     }
14608     ClearMap(); // [HGM] exclude: invalidate map
14609 }
14610
14611
14612 void
14613 ForwardEvent ()
14614 {
14615     if (gameMode == IcsExamining && !pausing) {
14616         SendToICS(ics_prefix);
14617         SendToICS("forward\n");
14618     } else {
14619         ForwardInner(currentMove + 1);
14620     }
14621 }
14622
14623 void
14624 ToEndEvent ()
14625 {
14626     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14627         /* to optimze, we temporarily turn off analysis mode while we feed
14628          * the remaining moves to the engine. Otherwise we get analysis output
14629          * after each move.
14630          */
14631         if (first.analysisSupport) {
14632           SendToProgram("exit\nforce\n", &first);
14633           first.analyzing = FALSE;
14634         }
14635     }
14636
14637     if (gameMode == IcsExamining && !pausing) {
14638         SendToICS(ics_prefix);
14639         SendToICS("forward 999999\n");
14640     } else {
14641         ForwardInner(forwardMostMove);
14642     }
14643
14644     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14645         /* we have fed all the moves, so reactivate analysis mode */
14646         SendToProgram("analyze\n", &first);
14647         first.analyzing = TRUE;
14648         /*first.maybeThinking = TRUE;*/
14649         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14650     }
14651 }
14652
14653 void
14654 BackwardInner (int target)
14655 {
14656     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14657
14658     if (appData.debugMode)
14659         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14660                 target, currentMove, forwardMostMove);
14661
14662     if (gameMode == EditPosition) return;
14663     seekGraphUp = FALSE;
14664     MarkTargetSquares(1);
14665     if (currentMove <= backwardMostMove) {
14666         ClearHighlights();
14667         DrawPosition(full_redraw, boards[currentMove]);
14668         return;
14669     }
14670     if (gameMode == PlayFromGameFile && !pausing)
14671       PauseEvent();
14672
14673     if (moveList[target][0]) {
14674         int fromX, fromY, toX, toY;
14675         toX = moveList[target][2] - AAA;
14676         toY = moveList[target][3] - ONE;
14677         if (moveList[target][1] == '@') {
14678             if (appData.highlightLastMove) {
14679                 SetHighlights(-1, -1, toX, toY);
14680             }
14681         } else {
14682             fromX = moveList[target][0] - AAA;
14683             fromY = moveList[target][1] - ONE;
14684             if (target == currentMove - 1) {
14685                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14686             }
14687             if (appData.highlightLastMove) {
14688                 SetHighlights(fromX, fromY, toX, toY);
14689             }
14690         }
14691     }
14692     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14693         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14694       if(target != currentMove && second.analyzing) ToggleSecond(); // for now, we just stop second analyzing engine
14695         while (currentMove > target) {
14696             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14697                 // null move cannot be undone. Reload program with move history before it.
14698                 int i;
14699                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14700                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14701                 }
14702                 SendBoard(&first, i); 
14703                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14704                 break;
14705             }
14706             SendToProgram("undo\n", &first);
14707             currentMove--;
14708         }
14709     } else {
14710         currentMove = target;
14711     }
14712
14713     if (gameMode == EditGame || gameMode == EndOfGame) {
14714         whiteTimeRemaining = timeRemaining[0][currentMove];
14715         blackTimeRemaining = timeRemaining[1][currentMove];
14716     }
14717     DisplayBothClocks();
14718     DisplayMove(currentMove - 1);
14719     DrawPosition(full_redraw, boards[currentMove]);
14720     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14721     // [HGM] PV info: routine tests if comment empty
14722     DisplayComment(currentMove - 1, commentList[currentMove]);
14723     ClearMap(); // [HGM] exclude: invalidate map
14724 }
14725
14726 void
14727 BackwardEvent ()
14728 {
14729     if (gameMode == IcsExamining && !pausing) {
14730         SendToICS(ics_prefix);
14731         SendToICS("backward\n");
14732     } else {
14733         BackwardInner(currentMove - 1);
14734     }
14735 }
14736
14737 void
14738 ToStartEvent ()
14739 {
14740     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14741         /* to optimize, we temporarily turn off analysis mode while we undo
14742          * all the moves. Otherwise we get analysis output after each undo.
14743          */
14744         if (first.analysisSupport) {
14745           SendToProgram("exit\nforce\n", &first);
14746           first.analyzing = FALSE;
14747         }
14748     }
14749
14750     if (gameMode == IcsExamining && !pausing) {
14751         SendToICS(ics_prefix);
14752         SendToICS("backward 999999\n");
14753     } else {
14754         BackwardInner(backwardMostMove);
14755     }
14756
14757     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14758         /* we have fed all the moves, so reactivate analysis mode */
14759         SendToProgram("analyze\n", &first);
14760         first.analyzing = TRUE;
14761         /*first.maybeThinking = TRUE;*/
14762         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14763     }
14764 }
14765
14766 void
14767 ToNrEvent (int to)
14768 {
14769   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14770   if (to >= forwardMostMove) to = forwardMostMove;
14771   if (to <= backwardMostMove) to = backwardMostMove;
14772   if (to < currentMove) {
14773     BackwardInner(to);
14774   } else {
14775     ForwardInner(to);
14776   }
14777 }
14778
14779 void
14780 RevertEvent (Boolean annotate)
14781 {
14782     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14783         return;
14784     }
14785     if (gameMode != IcsExamining) {
14786         DisplayError(_("You are not examining a game"), 0);
14787         return;
14788     }
14789     if (pausing) {
14790         DisplayError(_("You can't revert while pausing"), 0);
14791         return;
14792     }
14793     SendToICS(ics_prefix);
14794     SendToICS("revert\n");
14795 }
14796
14797 void
14798 RetractMoveEvent ()
14799 {
14800     switch (gameMode) {
14801       case MachinePlaysWhite:
14802       case MachinePlaysBlack:
14803         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14804             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14805             return;
14806         }
14807         if (forwardMostMove < 2) return;
14808         currentMove = forwardMostMove = forwardMostMove - 2;
14809         whiteTimeRemaining = timeRemaining[0][currentMove];
14810         blackTimeRemaining = timeRemaining[1][currentMove];
14811         DisplayBothClocks();
14812         DisplayMove(currentMove - 1);
14813         ClearHighlights();/*!! could figure this out*/
14814         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14815         SendToProgram("remove\n", &first);
14816         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14817         break;
14818
14819       case BeginningOfGame:
14820       default:
14821         break;
14822
14823       case IcsPlayingWhite:
14824       case IcsPlayingBlack:
14825         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14826             SendToICS(ics_prefix);
14827             SendToICS("takeback 2\n");
14828         } else {
14829             SendToICS(ics_prefix);
14830             SendToICS("takeback 1\n");
14831         }
14832         break;
14833     }
14834 }
14835
14836 void
14837 MoveNowEvent ()
14838 {
14839     ChessProgramState *cps;
14840
14841     switch (gameMode) {
14842       case MachinePlaysWhite:
14843         if (!WhiteOnMove(forwardMostMove)) {
14844             DisplayError(_("It is your turn"), 0);
14845             return;
14846         }
14847         cps = &first;
14848         break;
14849       case MachinePlaysBlack:
14850         if (WhiteOnMove(forwardMostMove)) {
14851             DisplayError(_("It is your turn"), 0);
14852             return;
14853         }
14854         cps = &first;
14855         break;
14856       case TwoMachinesPlay:
14857         if (WhiteOnMove(forwardMostMove) ==
14858             (first.twoMachinesColor[0] == 'w')) {
14859             cps = &first;
14860         } else {
14861             cps = &second;
14862         }
14863         break;
14864       case BeginningOfGame:
14865       default:
14866         return;
14867     }
14868     SendToProgram("?\n", cps);
14869 }
14870
14871 void
14872 TruncateGameEvent ()
14873 {
14874     EditGameEvent();
14875     if (gameMode != EditGame) return;
14876     TruncateGame();
14877 }
14878
14879 void
14880 TruncateGame ()
14881 {
14882     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14883     if (forwardMostMove > currentMove) {
14884         if (gameInfo.resultDetails != NULL) {
14885             free(gameInfo.resultDetails);
14886             gameInfo.resultDetails = NULL;
14887             gameInfo.result = GameUnfinished;
14888         }
14889         forwardMostMove = currentMove;
14890         HistorySet(parseList, backwardMostMove, forwardMostMove,
14891                    currentMove-1);
14892     }
14893 }
14894
14895 void
14896 HintEvent ()
14897 {
14898     if (appData.noChessProgram) return;
14899     switch (gameMode) {
14900       case MachinePlaysWhite:
14901         if (WhiteOnMove(forwardMostMove)) {
14902             DisplayError(_("Wait until your turn"), 0);
14903             return;
14904         }
14905         break;
14906       case BeginningOfGame:
14907       case MachinePlaysBlack:
14908         if (!WhiteOnMove(forwardMostMove)) {
14909             DisplayError(_("Wait until your turn"), 0);
14910             return;
14911         }
14912         break;
14913       default:
14914         DisplayError(_("No hint available"), 0);
14915         return;
14916     }
14917     SendToProgram("hint\n", &first);
14918     hintRequested = TRUE;
14919 }
14920
14921 void
14922 BookEvent ()
14923 {
14924     if (appData.noChessProgram) return;
14925     switch (gameMode) {
14926       case MachinePlaysWhite:
14927         if (WhiteOnMove(forwardMostMove)) {
14928             DisplayError(_("Wait until your turn"), 0);
14929             return;
14930         }
14931         break;
14932       case BeginningOfGame:
14933       case MachinePlaysBlack:
14934         if (!WhiteOnMove(forwardMostMove)) {
14935             DisplayError(_("Wait until your turn"), 0);
14936             return;
14937         }
14938         break;
14939       case EditPosition:
14940         EditPositionDone(TRUE);
14941         break;
14942       case TwoMachinesPlay:
14943         return;
14944       default:
14945         break;
14946     }
14947     SendToProgram("bk\n", &first);
14948     bookOutput[0] = NULLCHAR;
14949     bookRequested = TRUE;
14950 }
14951
14952 void
14953 AboutGameEvent ()
14954 {
14955     char *tags = PGNTags(&gameInfo);
14956     TagsPopUp(tags, CmailMsg());
14957     free(tags);
14958 }
14959
14960 /* end button procedures */
14961
14962 void
14963 PrintPosition (FILE *fp, int move)
14964 {
14965     int i, j;
14966
14967     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14968         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14969             char c = PieceToChar(boards[move][i][j]);
14970             fputc(c == 'x' ? '.' : c, fp);
14971             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14972         }
14973     }
14974     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14975       fprintf(fp, "white to play\n");
14976     else
14977       fprintf(fp, "black to play\n");
14978 }
14979
14980 void
14981 PrintOpponents (FILE *fp)
14982 {
14983     if (gameInfo.white != NULL) {
14984         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14985     } else {
14986         fprintf(fp, "\n");
14987     }
14988 }
14989
14990 /* Find last component of program's own name, using some heuristics */
14991 void
14992 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14993 {
14994     char *p, *q, c;
14995     int local = (strcmp(host, "localhost") == 0);
14996     while (!local && (p = strchr(prog, ';')) != NULL) {
14997         p++;
14998         while (*p == ' ') p++;
14999         prog = p;
15000     }
15001     if (*prog == '"' || *prog == '\'') {
15002         q = strchr(prog + 1, *prog);
15003     } else {
15004         q = strchr(prog, ' ');
15005     }
15006     if (q == NULL) q = prog + strlen(prog);
15007     p = q;
15008     while (p >= prog && *p != '/' && *p != '\\') p--;
15009     p++;
15010     if(p == prog && *p == '"') p++;
15011     c = *q; *q = 0;
15012     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15013     memcpy(buf, p, q - p);
15014     buf[q - p] = NULLCHAR;
15015     if (!local) {
15016         strcat(buf, "@");
15017         strcat(buf, host);
15018     }
15019 }
15020
15021 char *
15022 TimeControlTagValue ()
15023 {
15024     char buf[MSG_SIZ];
15025     if (!appData.clockMode) {
15026       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15027     } else if (movesPerSession > 0) {
15028       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15029     } else if (timeIncrement == 0) {
15030       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15031     } else {
15032       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15033     }
15034     return StrSave(buf);
15035 }
15036
15037 void
15038 SetGameInfo ()
15039 {
15040     /* This routine is used only for certain modes */
15041     VariantClass v = gameInfo.variant;
15042     ChessMove r = GameUnfinished;
15043     char *p = NULL;
15044
15045     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15046         r = gameInfo.result;
15047         p = gameInfo.resultDetails;
15048         gameInfo.resultDetails = NULL;
15049     }
15050     ClearGameInfo(&gameInfo);
15051     gameInfo.variant = v;
15052
15053     switch (gameMode) {
15054       case MachinePlaysWhite:
15055         gameInfo.event = StrSave( appData.pgnEventHeader );
15056         gameInfo.site = StrSave(HostName());
15057         gameInfo.date = PGNDate();
15058         gameInfo.round = StrSave("-");
15059         gameInfo.white = StrSave(first.tidy);
15060         gameInfo.black = StrSave(UserName());
15061         gameInfo.timeControl = TimeControlTagValue();
15062         break;
15063
15064       case MachinePlaysBlack:
15065         gameInfo.event = StrSave( appData.pgnEventHeader );
15066         gameInfo.site = StrSave(HostName());
15067         gameInfo.date = PGNDate();
15068         gameInfo.round = StrSave("-");
15069         gameInfo.white = StrSave(UserName());
15070         gameInfo.black = StrSave(first.tidy);
15071         gameInfo.timeControl = TimeControlTagValue();
15072         break;
15073
15074       case TwoMachinesPlay:
15075         gameInfo.event = StrSave( appData.pgnEventHeader );
15076         gameInfo.site = StrSave(HostName());
15077         gameInfo.date = PGNDate();
15078         if (roundNr > 0) {
15079             char buf[MSG_SIZ];
15080             snprintf(buf, MSG_SIZ, "%d", roundNr);
15081             gameInfo.round = StrSave(buf);
15082         } else {
15083             gameInfo.round = StrSave("-");
15084         }
15085         if (first.twoMachinesColor[0] == 'w') {
15086             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15087             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15088         } else {
15089             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15090             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15091         }
15092         gameInfo.timeControl = TimeControlTagValue();
15093         break;
15094
15095       case EditGame:
15096         gameInfo.event = StrSave("Edited game");
15097         gameInfo.site = StrSave(HostName());
15098         gameInfo.date = PGNDate();
15099         gameInfo.round = StrSave("-");
15100         gameInfo.white = StrSave("-");
15101         gameInfo.black = StrSave("-");
15102         gameInfo.result = r;
15103         gameInfo.resultDetails = p;
15104         break;
15105
15106       case EditPosition:
15107         gameInfo.event = StrSave("Edited position");
15108         gameInfo.site = StrSave(HostName());
15109         gameInfo.date = PGNDate();
15110         gameInfo.round = StrSave("-");
15111         gameInfo.white = StrSave("-");
15112         gameInfo.black = StrSave("-");
15113         break;
15114
15115       case IcsPlayingWhite:
15116       case IcsPlayingBlack:
15117       case IcsObserving:
15118       case IcsExamining:
15119         break;
15120
15121       case PlayFromGameFile:
15122         gameInfo.event = StrSave("Game from non-PGN file");
15123         gameInfo.site = StrSave(HostName());
15124         gameInfo.date = PGNDate();
15125         gameInfo.round = StrSave("-");
15126         gameInfo.white = StrSave("?");
15127         gameInfo.black = StrSave("?");
15128         break;
15129
15130       default:
15131         break;
15132     }
15133 }
15134
15135 void
15136 ReplaceComment (int index, char *text)
15137 {
15138     int len;
15139     char *p;
15140     float score;
15141
15142     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15143        pvInfoList[index-1].depth == len &&
15144        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15145        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15146     while (*text == '\n') text++;
15147     len = strlen(text);
15148     while (len > 0 && text[len - 1] == '\n') len--;
15149
15150     if (commentList[index] != NULL)
15151       free(commentList[index]);
15152
15153     if (len == 0) {
15154         commentList[index] = NULL;
15155         return;
15156     }
15157   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15158       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15159       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15160     commentList[index] = (char *) malloc(len + 2);
15161     strncpy(commentList[index], text, len);
15162     commentList[index][len] = '\n';
15163     commentList[index][len + 1] = NULLCHAR;
15164   } else {
15165     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15166     char *p;
15167     commentList[index] = (char *) malloc(len + 7);
15168     safeStrCpy(commentList[index], "{\n", 3);
15169     safeStrCpy(commentList[index]+2, text, len+1);
15170     commentList[index][len+2] = NULLCHAR;
15171     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15172     strcat(commentList[index], "\n}\n");
15173   }
15174 }
15175
15176 void
15177 CrushCRs (char *text)
15178 {
15179   char *p = text;
15180   char *q = text;
15181   char ch;
15182
15183   do {
15184     ch = *p++;
15185     if (ch == '\r') continue;
15186     *q++ = ch;
15187   } while (ch != '\0');
15188 }
15189
15190 void
15191 AppendComment (int index, char *text, Boolean addBraces)
15192 /* addBraces  tells if we should add {} */
15193 {
15194     int oldlen, len;
15195     char *old;
15196
15197 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15198     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15199
15200     CrushCRs(text);
15201     while (*text == '\n') text++;
15202     len = strlen(text);
15203     while (len > 0 && text[len - 1] == '\n') len--;
15204     text[len] = NULLCHAR;
15205
15206     if (len == 0) return;
15207
15208     if (commentList[index] != NULL) {
15209       Boolean addClosingBrace = addBraces;
15210         old = commentList[index];
15211         oldlen = strlen(old);
15212         while(commentList[index][oldlen-1] ==  '\n')
15213           commentList[index][--oldlen] = NULLCHAR;
15214         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15215         safeStrCpy(commentList[index], old, oldlen + len + 6);
15216         free(old);
15217         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15218         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15219           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15220           while (*text == '\n') { text++; len--; }
15221           commentList[index][--oldlen] = NULLCHAR;
15222       }
15223         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15224         else          strcat(commentList[index], "\n");
15225         strcat(commentList[index], text);
15226         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15227         else          strcat(commentList[index], "\n");
15228     } else {
15229         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15230         if(addBraces)
15231           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15232         else commentList[index][0] = NULLCHAR;
15233         strcat(commentList[index], text);
15234         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15235         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15236     }
15237 }
15238
15239 static char *
15240 FindStr (char * text, char * sub_text)
15241 {
15242     char * result = strstr( text, sub_text );
15243
15244     if( result != NULL ) {
15245         result += strlen( sub_text );
15246     }
15247
15248     return result;
15249 }
15250
15251 /* [AS] Try to extract PV info from PGN comment */
15252 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15253 char *
15254 GetInfoFromComment (int index, char * text)
15255 {
15256     char * sep = text, *p;
15257
15258     if( text != NULL && index > 0 ) {
15259         int score = 0;
15260         int depth = 0;
15261         int time = -1, sec = 0, deci;
15262         char * s_eval = FindStr( text, "[%eval " );
15263         char * s_emt = FindStr( text, "[%emt " );
15264
15265         if( s_eval != NULL || s_emt != NULL ) {
15266             /* New style */
15267             char delim;
15268
15269             if( s_eval != NULL ) {
15270                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15271                     return text;
15272                 }
15273
15274                 if( delim != ']' ) {
15275                     return text;
15276                 }
15277             }
15278
15279             if( s_emt != NULL ) {
15280             }
15281                 return text;
15282         }
15283         else {
15284             /* We expect something like: [+|-]nnn.nn/dd */
15285             int score_lo = 0;
15286
15287             if(*text != '{') return text; // [HGM] braces: must be normal comment
15288
15289             sep = strchr( text, '/' );
15290             if( sep == NULL || sep < (text+4) ) {
15291                 return text;
15292             }
15293
15294             p = text;
15295             if(p[1] == '(') { // comment starts with PV
15296                p = strchr(p, ')'); // locate end of PV
15297                if(p == NULL || sep < p+5) return text;
15298                // at this point we have something like "{(.*) +0.23/6 ..."
15299                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15300                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15301                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15302             }
15303             time = -1; sec = -1; deci = -1;
15304             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15305                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15306                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15307                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15308                 return text;
15309             }
15310
15311             if( score_lo < 0 || score_lo >= 100 ) {
15312                 return text;
15313             }
15314
15315             if(sec >= 0) time = 600*time + 10*sec; else
15316             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15317
15318             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15319
15320             /* [HGM] PV time: now locate end of PV info */
15321             while( *++sep >= '0' && *sep <= '9'); // strip depth
15322             if(time >= 0)
15323             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15324             if(sec >= 0)
15325             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15326             if(deci >= 0)
15327             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15328             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15329         }
15330
15331         if( depth <= 0 ) {
15332             return text;
15333         }
15334
15335         if( time < 0 ) {
15336             time = -1;
15337         }
15338
15339         pvInfoList[index-1].depth = depth;
15340         pvInfoList[index-1].score = score;
15341         pvInfoList[index-1].time  = 10*time; // centi-sec
15342         if(*sep == '}') *sep = 0; else *--sep = '{';
15343         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15344     }
15345     return sep;
15346 }
15347
15348 void
15349 SendToProgram (char *message, ChessProgramState *cps)
15350 {
15351     int count, outCount, error;
15352     char buf[MSG_SIZ];
15353
15354     if (cps->pr == NoProc) return;
15355     Attention(cps);
15356
15357     if (appData.debugMode) {
15358         TimeMark now;
15359         GetTimeMark(&now);
15360         fprintf(debugFP, "%ld >%-6s: %s",
15361                 SubtractTimeMarks(&now, &programStartTime),
15362                 cps->which, message);
15363         if(serverFP)
15364             fprintf(serverFP, "%ld >%-6s: %s",
15365                 SubtractTimeMarks(&now, &programStartTime),
15366                 cps->which, message), fflush(serverFP);
15367     }
15368
15369     count = strlen(message);
15370     outCount = OutputToProcess(cps->pr, message, count, &error);
15371     if (outCount < count && !exiting
15372                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15373       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15374       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15375         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15376             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15377                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15378                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15379                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15380             } else {
15381                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15382                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15383                 gameInfo.result = res;
15384             }
15385             gameInfo.resultDetails = StrSave(buf);
15386         }
15387         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15388         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15389     }
15390 }
15391
15392 void
15393 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15394 {
15395     char *end_str;
15396     char buf[MSG_SIZ];
15397     ChessProgramState *cps = (ChessProgramState *)closure;
15398
15399     if (isr != cps->isr) return; /* Killed intentionally */
15400     if (count <= 0) {
15401         if (count == 0) {
15402             RemoveInputSource(cps->isr);
15403             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15404                     _(cps->which), cps->program);
15405             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15406             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15407                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15408                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15409                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15410                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15411                 } else {
15412                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15413                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15414                     gameInfo.result = res;
15415                 }
15416                 gameInfo.resultDetails = StrSave(buf);
15417             }
15418             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15419             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15420         } else {
15421             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15422                     _(cps->which), cps->program);
15423             RemoveInputSource(cps->isr);
15424
15425             /* [AS] Program is misbehaving badly... kill it */
15426             if( count == -2 ) {
15427                 DestroyChildProcess( cps->pr, 9 );
15428                 cps->pr = NoProc;
15429             }
15430
15431             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15432         }
15433         return;
15434     }
15435
15436     if ((end_str = strchr(message, '\r')) != NULL)
15437       *end_str = NULLCHAR;
15438     if ((end_str = strchr(message, '\n')) != NULL)
15439       *end_str = NULLCHAR;
15440
15441     if (appData.debugMode) {
15442         TimeMark now; int print = 1;
15443         char *quote = ""; char c; int i;
15444
15445         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15446                 char start = message[0];
15447                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15448                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15449                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15450                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15451                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15452                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15453                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15454                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15455                    sscanf(message, "hint: %c", &c)!=1 && 
15456                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15457                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15458                     print = (appData.engineComments >= 2);
15459                 }
15460                 message[0] = start; // restore original message
15461         }
15462         if(print) {
15463                 GetTimeMark(&now);
15464                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15465                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15466                         quote,
15467                         message);
15468                 if(serverFP)
15469                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15470                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15471                         quote,
15472                         message), fflush(serverFP);
15473         }
15474     }
15475
15476     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15477     if (appData.icsEngineAnalyze) {
15478         if (strstr(message, "whisper") != NULL ||
15479              strstr(message, "kibitz") != NULL ||
15480             strstr(message, "tellics") != NULL) return;
15481     }
15482
15483     HandleMachineMove(message, cps);
15484 }
15485
15486
15487 void
15488 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15489 {
15490     char buf[MSG_SIZ];
15491     int seconds;
15492
15493     if( timeControl_2 > 0 ) {
15494         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15495             tc = timeControl_2;
15496         }
15497     }
15498     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15499     inc /= cps->timeOdds;
15500     st  /= cps->timeOdds;
15501
15502     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15503
15504     if (st > 0) {
15505       /* Set exact time per move, normally using st command */
15506       if (cps->stKludge) {
15507         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15508         seconds = st % 60;
15509         if (seconds == 0) {
15510           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15511         } else {
15512           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15513         }
15514       } else {
15515         snprintf(buf, MSG_SIZ, "st %d\n", st);
15516       }
15517     } else {
15518       /* Set conventional or incremental time control, using level command */
15519       if (seconds == 0) {
15520         /* Note old gnuchess bug -- minutes:seconds used to not work.
15521            Fixed in later versions, but still avoid :seconds
15522            when seconds is 0. */
15523         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15524       } else {
15525         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15526                  seconds, inc/1000.);
15527       }
15528     }
15529     SendToProgram(buf, cps);
15530
15531     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15532     /* Orthogonally, limit search to given depth */
15533     if (sd > 0) {
15534       if (cps->sdKludge) {
15535         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15536       } else {
15537         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15538       }
15539       SendToProgram(buf, cps);
15540     }
15541
15542     if(cps->nps >= 0) { /* [HGM] nps */
15543         if(cps->supportsNPS == FALSE)
15544           cps->nps = -1; // don't use if engine explicitly says not supported!
15545         else {
15546           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15547           SendToProgram(buf, cps);
15548         }
15549     }
15550 }
15551
15552 ChessProgramState *
15553 WhitePlayer ()
15554 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15555 {
15556     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15557        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15558         return &second;
15559     return &first;
15560 }
15561
15562 void
15563 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15564 {
15565     char message[MSG_SIZ];
15566     long time, otime;
15567
15568     /* Note: this routine must be called when the clocks are stopped
15569        or when they have *just* been set or switched; otherwise
15570        it will be off by the time since the current tick started.
15571     */
15572     if (machineWhite) {
15573         time = whiteTimeRemaining / 10;
15574         otime = blackTimeRemaining / 10;
15575     } else {
15576         time = blackTimeRemaining / 10;
15577         otime = whiteTimeRemaining / 10;
15578     }
15579     /* [HGM] translate opponent's time by time-odds factor */
15580     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15581
15582     if (time <= 0) time = 1;
15583     if (otime <= 0) otime = 1;
15584
15585     snprintf(message, MSG_SIZ, "time %ld\n", time);
15586     SendToProgram(message, cps);
15587
15588     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15589     SendToProgram(message, cps);
15590 }
15591
15592 int
15593 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15594 {
15595   char buf[MSG_SIZ];
15596   int len = strlen(name);
15597   int val;
15598
15599   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15600     (*p) += len + 1;
15601     sscanf(*p, "%d", &val);
15602     *loc = (val != 0);
15603     while (**p && **p != ' ')
15604       (*p)++;
15605     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15606     SendToProgram(buf, cps);
15607     return TRUE;
15608   }
15609   return FALSE;
15610 }
15611
15612 int
15613 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15614 {
15615   char buf[MSG_SIZ];
15616   int len = strlen(name);
15617   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15618     (*p) += len + 1;
15619     sscanf(*p, "%d", loc);
15620     while (**p && **p != ' ') (*p)++;
15621     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15622     SendToProgram(buf, cps);
15623     return TRUE;
15624   }
15625   return FALSE;
15626 }
15627
15628 int
15629 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15630 {
15631   char buf[MSG_SIZ];
15632   int len = strlen(name);
15633   if (strncmp((*p), name, len) == 0
15634       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15635     (*p) += len + 2;
15636     sscanf(*p, "%[^\"]", loc);
15637     while (**p && **p != '\"') (*p)++;
15638     if (**p == '\"') (*p)++;
15639     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15640     SendToProgram(buf, cps);
15641     return TRUE;
15642   }
15643   return FALSE;
15644 }
15645
15646 int
15647 ParseOption (Option *opt, ChessProgramState *cps)
15648 // [HGM] options: process the string that defines an engine option, and determine
15649 // name, type, default value, and allowed value range
15650 {
15651         char *p, *q, buf[MSG_SIZ];
15652         int n, min = (-1)<<31, max = 1<<31, def;
15653
15654         if(p = strstr(opt->name, " -spin ")) {
15655             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15656             if(max < min) max = min; // enforce consistency
15657             if(def < min) def = min;
15658             if(def > max) def = max;
15659             opt->value = def;
15660             opt->min = min;
15661             opt->max = max;
15662             opt->type = Spin;
15663         } else if((p = strstr(opt->name, " -slider "))) {
15664             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15665             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15666             if(max < min) max = min; // enforce consistency
15667             if(def < min) def = min;
15668             if(def > max) def = max;
15669             opt->value = def;
15670             opt->min = min;
15671             opt->max = max;
15672             opt->type = Spin; // Slider;
15673         } else if((p = strstr(opt->name, " -string "))) {
15674             opt->textValue = p+9;
15675             opt->type = TextBox;
15676         } else if((p = strstr(opt->name, " -file "))) {
15677             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15678             opt->textValue = p+7;
15679             opt->type = FileName; // FileName;
15680         } else if((p = strstr(opt->name, " -path "))) {
15681             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15682             opt->textValue = p+7;
15683             opt->type = PathName; // PathName;
15684         } else if(p = strstr(opt->name, " -check ")) {
15685             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15686             opt->value = (def != 0);
15687             opt->type = CheckBox;
15688         } else if(p = strstr(opt->name, " -combo ")) {
15689             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15690             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15691             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15692             opt->value = n = 0;
15693             while(q = StrStr(q, " /// ")) {
15694                 n++; *q = 0;    // count choices, and null-terminate each of them
15695                 q += 5;
15696                 if(*q == '*') { // remember default, which is marked with * prefix
15697                     q++;
15698                     opt->value = n;
15699                 }
15700                 cps->comboList[cps->comboCnt++] = q;
15701             }
15702             cps->comboList[cps->comboCnt++] = NULL;
15703             opt->max = n + 1;
15704             opt->type = ComboBox;
15705         } else if(p = strstr(opt->name, " -button")) {
15706             opt->type = Button;
15707         } else if(p = strstr(opt->name, " -save")) {
15708             opt->type = SaveButton;
15709         } else return FALSE;
15710         *p = 0; // terminate option name
15711         // now look if the command-line options define a setting for this engine option.
15712         if(cps->optionSettings && cps->optionSettings[0])
15713             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15714         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15715           snprintf(buf, MSG_SIZ, "option %s", p);
15716                 if(p = strstr(buf, ",")) *p = 0;
15717                 if(q = strchr(buf, '=')) switch(opt->type) {
15718                     case ComboBox:
15719                         for(n=0; n<opt->max; n++)
15720                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15721                         break;
15722                     case TextBox:
15723                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15724                         break;
15725                     case Spin:
15726                     case CheckBox:
15727                         opt->value = atoi(q+1);
15728                     default:
15729                         break;
15730                 }
15731                 strcat(buf, "\n");
15732                 SendToProgram(buf, cps);
15733         }
15734         return TRUE;
15735 }
15736
15737 void
15738 FeatureDone (ChessProgramState *cps, int val)
15739 {
15740   DelayedEventCallback cb = GetDelayedEvent();
15741   if ((cb == InitBackEnd3 && cps == &first) ||
15742       (cb == SettingsMenuIfReady && cps == &second) ||
15743       (cb == LoadEngine) ||
15744       (cb == TwoMachinesEventIfReady)) {
15745     CancelDelayedEvent();
15746     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15747   }
15748   cps->initDone = val;
15749 }
15750
15751 /* Parse feature command from engine */
15752 void
15753 ParseFeatures (char *args, ChessProgramState *cps)
15754 {
15755   char *p = args;
15756   char *q;
15757   int val;
15758   char buf[MSG_SIZ];
15759
15760   for (;;) {
15761     while (*p == ' ') p++;
15762     if (*p == NULLCHAR) return;
15763
15764     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15765     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15766     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15767     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15768     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15769     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15770     if (BoolFeature(&p, "reuse", &val, cps)) {
15771       /* Engine can disable reuse, but can't enable it if user said no */
15772       if (!val) cps->reuse = FALSE;
15773       continue;
15774     }
15775     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15776     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15777       if (gameMode == TwoMachinesPlay) {
15778         DisplayTwoMachinesTitle();
15779       } else {
15780         DisplayTitle("");
15781       }
15782       continue;
15783     }
15784     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15785     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15786     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15787     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15788     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15789     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15790     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15791     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15792     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15793     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15794     if (IntFeature(&p, "done", &val, cps)) {
15795       FeatureDone(cps, val);
15796       continue;
15797     }
15798     /* Added by Tord: */
15799     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15800     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15801     /* End of additions by Tord */
15802
15803     /* [HGM] added features: */
15804     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15805     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15806     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15807     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15808     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15809     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15810     if (StringFeature(&p, "option", buf, cps)) {
15811         FREE(cps->option[cps->nrOptions].name);
15812         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15813         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15814         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15815           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15816             SendToProgram(buf, cps);
15817             continue;
15818         }
15819         if(cps->nrOptions >= MAX_OPTIONS) {
15820             cps->nrOptions--;
15821             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15822             DisplayError(buf, 0);
15823         }
15824         continue;
15825     }
15826     /* End of additions by HGM */
15827
15828     /* unknown feature: complain and skip */
15829     q = p;
15830     while (*q && *q != '=') q++;
15831     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15832     SendToProgram(buf, cps);
15833     p = q;
15834     if (*p == '=') {
15835       p++;
15836       if (*p == '\"') {
15837         p++;
15838         while (*p && *p != '\"') p++;
15839         if (*p == '\"') p++;
15840       } else {
15841         while (*p && *p != ' ') p++;
15842       }
15843     }
15844   }
15845
15846 }
15847
15848 void
15849 PeriodicUpdatesEvent (int newState)
15850 {
15851     if (newState == appData.periodicUpdates)
15852       return;
15853
15854     appData.periodicUpdates=newState;
15855
15856     /* Display type changes, so update it now */
15857 //    DisplayAnalysis();
15858
15859     /* Get the ball rolling again... */
15860     if (newState) {
15861         AnalysisPeriodicEvent(1);
15862         StartAnalysisClock();
15863     }
15864 }
15865
15866 void
15867 PonderNextMoveEvent (int newState)
15868 {
15869     if (newState == appData.ponderNextMove) return;
15870     if (gameMode == EditPosition) EditPositionDone(TRUE);
15871     if (newState) {
15872         SendToProgram("hard\n", &first);
15873         if (gameMode == TwoMachinesPlay) {
15874             SendToProgram("hard\n", &second);
15875         }
15876     } else {
15877         SendToProgram("easy\n", &first);
15878         thinkOutput[0] = NULLCHAR;
15879         if (gameMode == TwoMachinesPlay) {
15880             SendToProgram("easy\n", &second);
15881         }
15882     }
15883     appData.ponderNextMove = newState;
15884 }
15885
15886 void
15887 NewSettingEvent (int option, int *feature, char *command, int value)
15888 {
15889     char buf[MSG_SIZ];
15890
15891     if (gameMode == EditPosition) EditPositionDone(TRUE);
15892     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15893     if(feature == NULL || *feature) SendToProgram(buf, &first);
15894     if (gameMode == TwoMachinesPlay) {
15895         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15896     }
15897 }
15898
15899 void
15900 ShowThinkingEvent ()
15901 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15902 {
15903     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15904     int newState = appData.showThinking
15905         // [HGM] thinking: other features now need thinking output as well
15906         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15907
15908     if (oldState == newState) return;
15909     oldState = newState;
15910     if (gameMode == EditPosition) EditPositionDone(TRUE);
15911     if (oldState) {
15912         SendToProgram("post\n", &first);
15913         if (gameMode == TwoMachinesPlay) {
15914             SendToProgram("post\n", &second);
15915         }
15916     } else {
15917         SendToProgram("nopost\n", &first);
15918         thinkOutput[0] = NULLCHAR;
15919         if (gameMode == TwoMachinesPlay) {
15920             SendToProgram("nopost\n", &second);
15921         }
15922     }
15923 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15924 }
15925
15926 void
15927 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15928 {
15929   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15930   if (pr == NoProc) return;
15931   AskQuestion(title, question, replyPrefix, pr);
15932 }
15933
15934 void
15935 TypeInEvent (char firstChar)
15936 {
15937     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15938         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15939         gameMode == AnalyzeMode || gameMode == EditGame || 
15940         gameMode == EditPosition || gameMode == IcsExamining ||
15941         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15942         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15943                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15944                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15945         gameMode == Training) PopUpMoveDialog(firstChar);
15946 }
15947
15948 void
15949 TypeInDoneEvent (char *move)
15950 {
15951         Board board;
15952         int n, fromX, fromY, toX, toY;
15953         char promoChar;
15954         ChessMove moveType;
15955
15956         // [HGM] FENedit
15957         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15958                 EditPositionPasteFEN(move);
15959                 return;
15960         }
15961         // [HGM] movenum: allow move number to be typed in any mode
15962         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15963           ToNrEvent(2*n-1);
15964           return;
15965         }
15966         // undocumented kludge: allow command-line option to be typed in!
15967         // (potentially fatal, and does not implement the effect of the option.)
15968         // should only be used for options that are values on which future decisions will be made,
15969         // and definitely not on options that would be used during initialization.
15970         if(strstr(move, "!!! -") == move) {
15971             ParseArgsFromString(move+4);
15972             return;
15973         }
15974
15975       if (gameMode != EditGame && currentMove != forwardMostMove && 
15976         gameMode != Training) {
15977         DisplayMoveError(_("Displayed move is not current"));
15978       } else {
15979         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15980           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15981         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15982         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15983           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15984           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15985         } else {
15986           DisplayMoveError(_("Could not parse move"));
15987         }
15988       }
15989 }
15990
15991 void
15992 DisplayMove (int moveNumber)
15993 {
15994     char message[MSG_SIZ];
15995     char res[MSG_SIZ];
15996     char cpThinkOutput[MSG_SIZ];
15997
15998     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15999
16000     if (moveNumber == forwardMostMove - 1 ||
16001         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16002
16003         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16004
16005         if (strchr(cpThinkOutput, '\n')) {
16006             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16007         }
16008     } else {
16009         *cpThinkOutput = NULLCHAR;
16010     }
16011
16012     /* [AS] Hide thinking from human user */
16013     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16014         *cpThinkOutput = NULLCHAR;
16015         if( thinkOutput[0] != NULLCHAR ) {
16016             int i;
16017
16018             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16019                 cpThinkOutput[i] = '.';
16020             }
16021             cpThinkOutput[i] = NULLCHAR;
16022             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16023         }
16024     }
16025
16026     if (moveNumber == forwardMostMove - 1 &&
16027         gameInfo.resultDetails != NULL) {
16028         if (gameInfo.resultDetails[0] == NULLCHAR) {
16029           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16030         } else {
16031           snprintf(res, MSG_SIZ, " {%s} %s",
16032                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16033         }
16034     } else {
16035         res[0] = NULLCHAR;
16036     }
16037
16038     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16039         DisplayMessage(res, cpThinkOutput);
16040     } else {
16041       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16042                 WhiteOnMove(moveNumber) ? " " : ".. ",
16043                 parseList[moveNumber], res);
16044         DisplayMessage(message, cpThinkOutput);
16045     }
16046 }
16047
16048 void
16049 DisplayComment (int moveNumber, char *text)
16050 {
16051     char title[MSG_SIZ];
16052
16053     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16054       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16055     } else {
16056       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16057               WhiteOnMove(moveNumber) ? " " : ".. ",
16058               parseList[moveNumber]);
16059     }
16060     if (text != NULL && (appData.autoDisplayComment || commentUp))
16061         CommentPopUp(title, text);
16062 }
16063
16064 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16065  * might be busy thinking or pondering.  It can be omitted if your
16066  * gnuchess is configured to stop thinking immediately on any user
16067  * input.  However, that gnuchess feature depends on the FIONREAD
16068  * ioctl, which does not work properly on some flavors of Unix.
16069  */
16070 void
16071 Attention (ChessProgramState *cps)
16072 {
16073 #if ATTENTION
16074     if (!cps->useSigint) return;
16075     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16076     switch (gameMode) {
16077       case MachinePlaysWhite:
16078       case MachinePlaysBlack:
16079       case TwoMachinesPlay:
16080       case IcsPlayingWhite:
16081       case IcsPlayingBlack:
16082       case AnalyzeMode:
16083       case AnalyzeFile:
16084         /* Skip if we know it isn't thinking */
16085         if (!cps->maybeThinking) return;
16086         if (appData.debugMode)
16087           fprintf(debugFP, "Interrupting %s\n", cps->which);
16088         InterruptChildProcess(cps->pr);
16089         cps->maybeThinking = FALSE;
16090         break;
16091       default:
16092         break;
16093     }
16094 #endif /*ATTENTION*/
16095 }
16096
16097 int
16098 CheckFlags ()
16099 {
16100     if (whiteTimeRemaining <= 0) {
16101         if (!whiteFlag) {
16102             whiteFlag = TRUE;
16103             if (appData.icsActive) {
16104                 if (appData.autoCallFlag &&
16105                     gameMode == IcsPlayingBlack && !blackFlag) {
16106                   SendToICS(ics_prefix);
16107                   SendToICS("flag\n");
16108                 }
16109             } else {
16110                 if (blackFlag) {
16111                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16112                 } else {
16113                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16114                     if (appData.autoCallFlag) {
16115                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16116                         return TRUE;
16117                     }
16118                 }
16119             }
16120         }
16121     }
16122     if (blackTimeRemaining <= 0) {
16123         if (!blackFlag) {
16124             blackFlag = TRUE;
16125             if (appData.icsActive) {
16126                 if (appData.autoCallFlag &&
16127                     gameMode == IcsPlayingWhite && !whiteFlag) {
16128                   SendToICS(ics_prefix);
16129                   SendToICS("flag\n");
16130                 }
16131             } else {
16132                 if (whiteFlag) {
16133                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16134                 } else {
16135                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16136                     if (appData.autoCallFlag) {
16137                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16138                         return TRUE;
16139                     }
16140                 }
16141             }
16142         }
16143     }
16144     return FALSE;
16145 }
16146
16147 void
16148 CheckTimeControl ()
16149 {
16150     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16151         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16152
16153     /*
16154      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16155      */
16156     if ( !WhiteOnMove(forwardMostMove) ) {
16157         /* White made time control */
16158         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16159         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16160         /* [HGM] time odds: correct new time quota for time odds! */
16161                                             / WhitePlayer()->timeOdds;
16162         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16163     } else {
16164         lastBlack -= blackTimeRemaining;
16165         /* Black made time control */
16166         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16167                                             / WhitePlayer()->other->timeOdds;
16168         lastWhite = whiteTimeRemaining;
16169     }
16170 }
16171
16172 void
16173 DisplayBothClocks ()
16174 {
16175     int wom = gameMode == EditPosition ?
16176       !blackPlaysFirst : WhiteOnMove(currentMove);
16177     DisplayWhiteClock(whiteTimeRemaining, wom);
16178     DisplayBlackClock(blackTimeRemaining, !wom);
16179 }
16180
16181
16182 /* Timekeeping seems to be a portability nightmare.  I think everyone
16183    has ftime(), but I'm really not sure, so I'm including some ifdefs
16184    to use other calls if you don't.  Clocks will be less accurate if
16185    you have neither ftime nor gettimeofday.
16186 */
16187
16188 /* VS 2008 requires the #include outside of the function */
16189 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16190 #include <sys/timeb.h>
16191 #endif
16192
16193 /* Get the current time as a TimeMark */
16194 void
16195 GetTimeMark (TimeMark *tm)
16196 {
16197 #if HAVE_GETTIMEOFDAY
16198
16199     struct timeval timeVal;
16200     struct timezone timeZone;
16201
16202     gettimeofday(&timeVal, &timeZone);
16203     tm->sec = (long) timeVal.tv_sec;
16204     tm->ms = (int) (timeVal.tv_usec / 1000L);
16205
16206 #else /*!HAVE_GETTIMEOFDAY*/
16207 #if HAVE_FTIME
16208
16209 // include <sys/timeb.h> / moved to just above start of function
16210     struct timeb timeB;
16211
16212     ftime(&timeB);
16213     tm->sec = (long) timeB.time;
16214     tm->ms = (int) timeB.millitm;
16215
16216 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16217     tm->sec = (long) time(NULL);
16218     tm->ms = 0;
16219 #endif
16220 #endif
16221 }
16222
16223 /* Return the difference in milliseconds between two
16224    time marks.  We assume the difference will fit in a long!
16225 */
16226 long
16227 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16228 {
16229     return 1000L*(tm2->sec - tm1->sec) +
16230            (long) (tm2->ms - tm1->ms);
16231 }
16232
16233
16234 /*
16235  * Code to manage the game clocks.
16236  *
16237  * In tournament play, black starts the clock and then white makes a move.
16238  * We give the human user a slight advantage if he is playing white---the
16239  * clocks don't run until he makes his first move, so it takes zero time.
16240  * Also, we don't account for network lag, so we could get out of sync
16241  * with GNU Chess's clock -- but then, referees are always right.
16242  */
16243
16244 static TimeMark tickStartTM;
16245 static long intendedTickLength;
16246
16247 long
16248 NextTickLength (long timeRemaining)
16249 {
16250     long nominalTickLength, nextTickLength;
16251
16252     if (timeRemaining > 0L && timeRemaining <= 10000L)
16253       nominalTickLength = 100L;
16254     else
16255       nominalTickLength = 1000L;
16256     nextTickLength = timeRemaining % nominalTickLength;
16257     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16258
16259     return nextTickLength;
16260 }
16261
16262 /* Adjust clock one minute up or down */
16263 void
16264 AdjustClock (Boolean which, int dir)
16265 {
16266     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16267     if(which) blackTimeRemaining += 60000*dir;
16268     else      whiteTimeRemaining += 60000*dir;
16269     DisplayBothClocks();
16270     adjustedClock = TRUE;
16271 }
16272
16273 /* Stop clocks and reset to a fresh time control */
16274 void
16275 ResetClocks ()
16276 {
16277     (void) StopClockTimer();
16278     if (appData.icsActive) {
16279         whiteTimeRemaining = blackTimeRemaining = 0;
16280     } else if (searchTime) {
16281         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16282         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16283     } else { /* [HGM] correct new time quote for time odds */
16284         whiteTC = blackTC = fullTimeControlString;
16285         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16286         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16287     }
16288     if (whiteFlag || blackFlag) {
16289         DisplayTitle("");
16290         whiteFlag = blackFlag = FALSE;
16291     }
16292     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16293     DisplayBothClocks();
16294     adjustedClock = FALSE;
16295 }
16296
16297 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16298
16299 /* Decrement running clock by amount of time that has passed */
16300 void
16301 DecrementClocks ()
16302 {
16303     long timeRemaining;
16304     long lastTickLength, fudge;
16305     TimeMark now;
16306
16307     if (!appData.clockMode) return;
16308     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16309
16310     GetTimeMark(&now);
16311
16312     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16313
16314     /* Fudge if we woke up a little too soon */
16315     fudge = intendedTickLength - lastTickLength;
16316     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16317
16318     if (WhiteOnMove(forwardMostMove)) {
16319         if(whiteNPS >= 0) lastTickLength = 0;
16320         timeRemaining = whiteTimeRemaining -= lastTickLength;
16321         if(timeRemaining < 0 && !appData.icsActive) {
16322             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16323             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16324                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16325                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16326             }
16327         }
16328         DisplayWhiteClock(whiteTimeRemaining - fudge,
16329                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16330     } else {
16331         if(blackNPS >= 0) lastTickLength = 0;
16332         timeRemaining = blackTimeRemaining -= lastTickLength;
16333         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16334             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16335             if(suddenDeath) {
16336                 blackStartMove = forwardMostMove;
16337                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16338             }
16339         }
16340         DisplayBlackClock(blackTimeRemaining - fudge,
16341                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16342     }
16343     if (CheckFlags()) return;
16344
16345     if(twoBoards) { // count down secondary board's clocks as well
16346         activePartnerTime -= lastTickLength;
16347         partnerUp = 1;
16348         if(activePartner == 'W')
16349             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16350         else
16351             DisplayBlackClock(activePartnerTime, TRUE);
16352         partnerUp = 0;
16353     }
16354
16355     tickStartTM = now;
16356     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16357     StartClockTimer(intendedTickLength);
16358
16359     /* if the time remaining has fallen below the alarm threshold, sound the
16360      * alarm. if the alarm has sounded and (due to a takeback or time control
16361      * with increment) the time remaining has increased to a level above the
16362      * threshold, reset the alarm so it can sound again.
16363      */
16364
16365     if (appData.icsActive && appData.icsAlarm) {
16366
16367         /* make sure we are dealing with the user's clock */
16368         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16369                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16370            )) return;
16371
16372         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16373             alarmSounded = FALSE;
16374         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16375             PlayAlarmSound();
16376             alarmSounded = TRUE;
16377         }
16378     }
16379 }
16380
16381
16382 /* A player has just moved, so stop the previously running
16383    clock and (if in clock mode) start the other one.
16384    We redisplay both clocks in case we're in ICS mode, because
16385    ICS gives us an update to both clocks after every move.
16386    Note that this routine is called *after* forwardMostMove
16387    is updated, so the last fractional tick must be subtracted
16388    from the color that is *not* on move now.
16389 */
16390 void
16391 SwitchClocks (int newMoveNr)
16392 {
16393     long lastTickLength;
16394     TimeMark now;
16395     int flagged = FALSE;
16396
16397     GetTimeMark(&now);
16398
16399     if (StopClockTimer() && appData.clockMode) {
16400         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16401         if (!WhiteOnMove(forwardMostMove)) {
16402             if(blackNPS >= 0) lastTickLength = 0;
16403             blackTimeRemaining -= lastTickLength;
16404            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16405 //         if(pvInfoList[forwardMostMove].time == -1)
16406                  pvInfoList[forwardMostMove].time =               // use GUI time
16407                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16408         } else {
16409            if(whiteNPS >= 0) lastTickLength = 0;
16410            whiteTimeRemaining -= lastTickLength;
16411            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16412 //         if(pvInfoList[forwardMostMove].time == -1)
16413                  pvInfoList[forwardMostMove].time =
16414                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16415         }
16416         flagged = CheckFlags();
16417     }
16418     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16419     CheckTimeControl();
16420
16421     if (flagged || !appData.clockMode) return;
16422
16423     switch (gameMode) {
16424       case MachinePlaysBlack:
16425       case MachinePlaysWhite:
16426       case BeginningOfGame:
16427         if (pausing) return;
16428         break;
16429
16430       case EditGame:
16431       case PlayFromGameFile:
16432       case IcsExamining:
16433         return;
16434
16435       default:
16436         break;
16437     }
16438
16439     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16440         if(WhiteOnMove(forwardMostMove))
16441              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16442         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16443     }
16444
16445     tickStartTM = now;
16446     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16447       whiteTimeRemaining : blackTimeRemaining);
16448     StartClockTimer(intendedTickLength);
16449 }
16450
16451
16452 /* Stop both clocks */
16453 void
16454 StopClocks ()
16455 {
16456     long lastTickLength;
16457     TimeMark now;
16458
16459     if (!StopClockTimer()) return;
16460     if (!appData.clockMode) return;
16461
16462     GetTimeMark(&now);
16463
16464     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16465     if (WhiteOnMove(forwardMostMove)) {
16466         if(whiteNPS >= 0) lastTickLength = 0;
16467         whiteTimeRemaining -= lastTickLength;
16468         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16469     } else {
16470         if(blackNPS >= 0) lastTickLength = 0;
16471         blackTimeRemaining -= lastTickLength;
16472         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16473     }
16474     CheckFlags();
16475 }
16476
16477 /* Start clock of player on move.  Time may have been reset, so
16478    if clock is already running, stop and restart it. */
16479 void
16480 StartClocks ()
16481 {
16482     (void) StopClockTimer(); /* in case it was running already */
16483     DisplayBothClocks();
16484     if (CheckFlags()) return;
16485
16486     if (!appData.clockMode) return;
16487     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16488
16489     GetTimeMark(&tickStartTM);
16490     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16491       whiteTimeRemaining : blackTimeRemaining);
16492
16493    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16494     whiteNPS = blackNPS = -1;
16495     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16496        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16497         whiteNPS = first.nps;
16498     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16499        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16500         blackNPS = first.nps;
16501     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16502         whiteNPS = second.nps;
16503     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16504         blackNPS = second.nps;
16505     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16506
16507     StartClockTimer(intendedTickLength);
16508 }
16509
16510 char *
16511 TimeString (long ms)
16512 {
16513     long second, minute, hour, day;
16514     char *sign = "";
16515     static char buf[32];
16516
16517     if (ms > 0 && ms <= 9900) {
16518       /* convert milliseconds to tenths, rounding up */
16519       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16520
16521       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16522       return buf;
16523     }
16524
16525     /* convert milliseconds to seconds, rounding up */
16526     /* use floating point to avoid strangeness of integer division
16527        with negative dividends on many machines */
16528     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16529
16530     if (second < 0) {
16531         sign = "-";
16532         second = -second;
16533     }
16534
16535     day = second / (60 * 60 * 24);
16536     second = second % (60 * 60 * 24);
16537     hour = second / (60 * 60);
16538     second = second % (60 * 60);
16539     minute = second / 60;
16540     second = second % 60;
16541
16542     if (day > 0)
16543       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16544               sign, day, hour, minute, second);
16545     else if (hour > 0)
16546       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16547     else
16548       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16549
16550     return buf;
16551 }
16552
16553
16554 /*
16555  * This is necessary because some C libraries aren't ANSI C compliant yet.
16556  */
16557 char *
16558 StrStr (char *string, char *match)
16559 {
16560     int i, length;
16561
16562     length = strlen(match);
16563
16564     for (i = strlen(string) - length; i >= 0; i--, string++)
16565       if (!strncmp(match, string, length))
16566         return string;
16567
16568     return NULL;
16569 }
16570
16571 char *
16572 StrCaseStr (char *string, char *match)
16573 {
16574     int i, j, length;
16575
16576     length = strlen(match);
16577
16578     for (i = strlen(string) - length; i >= 0; i--, string++) {
16579         for (j = 0; j < length; j++) {
16580             if (ToLower(match[j]) != ToLower(string[j]))
16581               break;
16582         }
16583         if (j == length) return string;
16584     }
16585
16586     return NULL;
16587 }
16588
16589 #ifndef _amigados
16590 int
16591 StrCaseCmp (char *s1, char *s2)
16592 {
16593     char c1, c2;
16594
16595     for (;;) {
16596         c1 = ToLower(*s1++);
16597         c2 = ToLower(*s2++);
16598         if (c1 > c2) return 1;
16599         if (c1 < c2) return -1;
16600         if (c1 == NULLCHAR) return 0;
16601     }
16602 }
16603
16604
16605 int
16606 ToLower (int c)
16607 {
16608     return isupper(c) ? tolower(c) : c;
16609 }
16610
16611
16612 int
16613 ToUpper (int c)
16614 {
16615     return islower(c) ? toupper(c) : c;
16616 }
16617 #endif /* !_amigados    */
16618
16619 char *
16620 StrSave (char *s)
16621 {
16622   char *ret;
16623
16624   if ((ret = (char *) malloc(strlen(s) + 1)))
16625     {
16626       safeStrCpy(ret, s, strlen(s)+1);
16627     }
16628   return ret;
16629 }
16630
16631 char *
16632 StrSavePtr (char *s, char **savePtr)
16633 {
16634     if (*savePtr) {
16635         free(*savePtr);
16636     }
16637     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16638       safeStrCpy(*savePtr, s, strlen(s)+1);
16639     }
16640     return(*savePtr);
16641 }
16642
16643 char *
16644 PGNDate ()
16645 {
16646     time_t clock;
16647     struct tm *tm;
16648     char buf[MSG_SIZ];
16649
16650     clock = time((time_t *)NULL);
16651     tm = localtime(&clock);
16652     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16653             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16654     return StrSave(buf);
16655 }
16656
16657
16658 char *
16659 PositionToFEN (int move, char *overrideCastling)
16660 {
16661     int i, j, fromX, fromY, toX, toY;
16662     int whiteToPlay;
16663     char buf[MSG_SIZ];
16664     char *p, *q;
16665     int emptycount;
16666     ChessSquare piece;
16667
16668     whiteToPlay = (gameMode == EditPosition) ?
16669       !blackPlaysFirst : (move % 2 == 0);
16670     p = buf;
16671
16672     /* Piece placement data */
16673     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16674         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16675         emptycount = 0;
16676         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16677             if (boards[move][i][j] == EmptySquare) {
16678                 emptycount++;
16679             } else { ChessSquare piece = boards[move][i][j];
16680                 if (emptycount > 0) {
16681                     if(emptycount<10) /* [HGM] can be >= 10 */
16682                         *p++ = '0' + emptycount;
16683                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16684                     emptycount = 0;
16685                 }
16686                 if(PieceToChar(piece) == '+') {
16687                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16688                     *p++ = '+';
16689                     piece = (ChessSquare)(DEMOTED piece);
16690                 }
16691                 *p++ = PieceToChar(piece);
16692                 if(p[-1] == '~') {
16693                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16694                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16695                     *p++ = '~';
16696                 }
16697             }
16698         }
16699         if (emptycount > 0) {
16700             if(emptycount<10) /* [HGM] can be >= 10 */
16701                 *p++ = '0' + emptycount;
16702             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16703             emptycount = 0;
16704         }
16705         *p++ = '/';
16706     }
16707     *(p - 1) = ' ';
16708
16709     /* [HGM] print Crazyhouse or Shogi holdings */
16710     if( gameInfo.holdingsWidth ) {
16711         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16712         q = p;
16713         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16714             piece = boards[move][i][BOARD_WIDTH-1];
16715             if( piece != EmptySquare )
16716               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16717                   *p++ = PieceToChar(piece);
16718         }
16719         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16720             piece = boards[move][BOARD_HEIGHT-i-1][0];
16721             if( piece != EmptySquare )
16722               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16723                   *p++ = PieceToChar(piece);
16724         }
16725
16726         if( q == p ) *p++ = '-';
16727         *p++ = ']';
16728         *p++ = ' ';
16729     }
16730
16731     /* Active color */
16732     *p++ = whiteToPlay ? 'w' : 'b';
16733     *p++ = ' ';
16734
16735   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16736     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16737   } else {
16738   if(nrCastlingRights) {
16739      q = p;
16740      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16741        /* [HGM] write directly from rights */
16742            if(boards[move][CASTLING][2] != NoRights &&
16743               boards[move][CASTLING][0] != NoRights   )
16744                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16745            if(boards[move][CASTLING][2] != NoRights &&
16746               boards[move][CASTLING][1] != NoRights   )
16747                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16748            if(boards[move][CASTLING][5] != NoRights &&
16749               boards[move][CASTLING][3] != NoRights   )
16750                 *p++ = boards[move][CASTLING][3] + AAA;
16751            if(boards[move][CASTLING][5] != NoRights &&
16752               boards[move][CASTLING][4] != NoRights   )
16753                 *p++ = boards[move][CASTLING][4] + AAA;
16754      } else {
16755
16756         /* [HGM] write true castling rights */
16757         if( nrCastlingRights == 6 ) {
16758             int q, k=0;
16759             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16760                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
16761             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
16762                  boards[move][CASTLING][2] != NoRights  );
16763             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
16764                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
16765                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16766                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
16767                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
16768             }
16769             if(q) *p++ = 'Q';
16770             k = 0;
16771             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16772                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
16773             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
16774                  boards[move][CASTLING][5] != NoRights  );
16775             if(gameInfo.variant == VariantSChess) {
16776                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
16777                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16778                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
16779                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
16780             }
16781             if(q) *p++ = 'q';
16782         }
16783      }
16784      if (q == p) *p++ = '-'; /* No castling rights */
16785      *p++ = ' ';
16786   }
16787
16788   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16789      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16790     /* En passant target square */
16791     if (move > backwardMostMove) {
16792         fromX = moveList[move - 1][0] - AAA;
16793         fromY = moveList[move - 1][1] - ONE;
16794         toX = moveList[move - 1][2] - AAA;
16795         toY = moveList[move - 1][3] - ONE;
16796         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16797             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16798             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16799             fromX == toX) {
16800             /* 2-square pawn move just happened */
16801             *p++ = toX + AAA;
16802             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16803         } else {
16804             *p++ = '-';
16805         }
16806     } else if(move == backwardMostMove) {
16807         // [HGM] perhaps we should always do it like this, and forget the above?
16808         if((signed char)boards[move][EP_STATUS] >= 0) {
16809             *p++ = boards[move][EP_STATUS] + AAA;
16810             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16811         } else {
16812             *p++ = '-';
16813         }
16814     } else {
16815         *p++ = '-';
16816     }
16817     *p++ = ' ';
16818   }
16819   }
16820
16821     /* [HGM] find reversible plies */
16822     {   int i = 0, j=move;
16823
16824         if (appData.debugMode) { int k;
16825             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16826             for(k=backwardMostMove; k<=forwardMostMove; k++)
16827                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16828
16829         }
16830
16831         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16832         if( j == backwardMostMove ) i += initialRulePlies;
16833         sprintf(p, "%d ", i);
16834         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16835     }
16836     /* Fullmove number */
16837     sprintf(p, "%d", (move / 2) + 1);
16838
16839     return StrSave(buf);
16840 }
16841
16842 Boolean
16843 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16844 {
16845     int i, j;
16846     char *p, c;
16847     int emptycount, virgin[BOARD_FILES];
16848     ChessSquare piece;
16849
16850     p = fen;
16851
16852     /* [HGM] by default clear Crazyhouse holdings, if present */
16853     if(gameInfo.holdingsWidth) {
16854        for(i=0; i<BOARD_HEIGHT; i++) {
16855            board[i][0]             = EmptySquare; /* black holdings */
16856            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16857            board[i][1]             = (ChessSquare) 0; /* black counts */
16858            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16859        }
16860     }
16861
16862     /* Piece placement data */
16863     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16864         j = 0;
16865         for (;;) {
16866             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16867                 if (*p == '/') p++;
16868                 emptycount = gameInfo.boardWidth - j;
16869                 while (emptycount--)
16870                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16871                 break;
16872 #if(BOARD_FILES >= 10)
16873             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16874                 p++; emptycount=10;
16875                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16876                 while (emptycount--)
16877                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16878 #endif
16879             } else if (isdigit(*p)) {
16880                 emptycount = *p++ - '0';
16881                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16882                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16883                 while (emptycount--)
16884                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16885             } else if (*p == '+' || isalpha(*p)) {
16886                 if (j >= gameInfo.boardWidth) return FALSE;
16887                 if(*p=='+') {
16888                     piece = CharToPiece(*++p);
16889                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16890                     piece = (ChessSquare) (PROMOTED piece ); p++;
16891                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16892                 } else piece = CharToPiece(*p++);
16893
16894                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16895                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16896                     piece = (ChessSquare) (PROMOTED piece);
16897                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16898                     p++;
16899                 }
16900                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16901             } else {
16902                 return FALSE;
16903             }
16904         }
16905     }
16906     while (*p == '/' || *p == ' ') p++;
16907
16908     /* [HGM] look for Crazyhouse holdings here */
16909     while(*p==' ') p++;
16910     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16911         if(*p == '[') p++;
16912         if(*p == '-' ) p++; /* empty holdings */ else {
16913             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16914             /* if we would allow FEN reading to set board size, we would   */
16915             /* have to add holdings and shift the board read so far here   */
16916             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16917                 p++;
16918                 if((int) piece >= (int) BlackPawn ) {
16919                     i = (int)piece - (int)BlackPawn;
16920                     i = PieceToNumber((ChessSquare)i);
16921                     if( i >= gameInfo.holdingsSize ) return FALSE;
16922                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16923                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16924                 } else {
16925                     i = (int)piece - (int)WhitePawn;
16926                     i = PieceToNumber((ChessSquare)i);
16927                     if( i >= gameInfo.holdingsSize ) return FALSE;
16928                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16929                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16930                 }
16931             }
16932         }
16933         if(*p == ']') p++;
16934     }
16935
16936     while(*p == ' ') p++;
16937
16938     /* Active color */
16939     c = *p++;
16940     if(appData.colorNickNames) {
16941       if( c == appData.colorNickNames[0] ) c = 'w'; else
16942       if( c == appData.colorNickNames[1] ) c = 'b';
16943     }
16944     switch (c) {
16945       case 'w':
16946         *blackPlaysFirst = FALSE;
16947         break;
16948       case 'b':
16949         *blackPlaysFirst = TRUE;
16950         break;
16951       default:
16952         return FALSE;
16953     }
16954
16955     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16956     /* return the extra info in global variiables             */
16957
16958     /* set defaults in case FEN is incomplete */
16959     board[EP_STATUS] = EP_UNKNOWN;
16960     for(i=0; i<nrCastlingRights; i++ ) {
16961         board[CASTLING][i] =
16962             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16963     }   /* assume possible unless obviously impossible */
16964     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16965     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16966     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16967                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16968     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16969     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16970     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16971                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16972     FENrulePlies = 0;
16973
16974     while(*p==' ') p++;
16975     if(nrCastlingRights) {
16976       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
16977       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
16978           /* castling indicator present, so default becomes no castlings */
16979           for(i=0; i<nrCastlingRights; i++ ) {
16980                  board[CASTLING][i] = NoRights;
16981           }
16982       }
16983       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16984              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
16985              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16986              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16987         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
16988
16989         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16990             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16991             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16992         }
16993         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16994             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16995         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16996                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16997         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16998                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16999         switch(c) {
17000           case'K':
17001               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17002               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17003               board[CASTLING][2] = whiteKingFile;
17004               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17005               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17006               break;
17007           case'Q':
17008               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17009               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17010               board[CASTLING][2] = whiteKingFile;
17011               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17012               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17013               break;
17014           case'k':
17015               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17016               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17017               board[CASTLING][5] = blackKingFile;
17018               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17019               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17020               break;
17021           case'q':
17022               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17023               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17024               board[CASTLING][5] = blackKingFile;
17025               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17026               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17027           case '-':
17028               break;
17029           default: /* FRC castlings */
17030               if(c >= 'a') { /* black rights */
17031                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17032                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17033                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17034                   if(i == BOARD_RGHT) break;
17035                   board[CASTLING][5] = i;
17036                   c -= AAA;
17037                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17038                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17039                   if(c > i)
17040                       board[CASTLING][3] = c;
17041                   else
17042                       board[CASTLING][4] = c;
17043               } else { /* white rights */
17044                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17045                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17046                     if(board[0][i] == WhiteKing) break;
17047                   if(i == BOARD_RGHT) break;
17048                   board[CASTLING][2] = i;
17049                   c -= AAA - 'a' + 'A';
17050                   if(board[0][c] >= WhiteKing) break;
17051                   if(c > i)
17052                       board[CASTLING][0] = c;
17053                   else
17054                       board[CASTLING][1] = c;
17055               }
17056         }
17057       }
17058       for(i=0; i<nrCastlingRights; i++)
17059         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17060       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17061     if (appData.debugMode) {
17062         fprintf(debugFP, "FEN castling rights:");
17063         for(i=0; i<nrCastlingRights; i++)
17064         fprintf(debugFP, " %d", board[CASTLING][i]);
17065         fprintf(debugFP, "\n");
17066     }
17067
17068       while(*p==' ') p++;
17069     }
17070
17071     /* read e.p. field in games that know e.p. capture */
17072     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17073        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17074       if(*p=='-') {
17075         p++; board[EP_STATUS] = EP_NONE;
17076       } else {
17077          char c = *p++ - AAA;
17078
17079          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17080          if(*p >= '0' && *p <='9') p++;
17081          board[EP_STATUS] = c;
17082       }
17083     }
17084
17085
17086     if(sscanf(p, "%d", &i) == 1) {
17087         FENrulePlies = i; /* 50-move ply counter */
17088         /* (The move number is still ignored)    */
17089     }
17090
17091     return TRUE;
17092 }
17093
17094 void
17095 EditPositionPasteFEN (char *fen)
17096 {
17097   if (fen != NULL) {
17098     Board initial_position;
17099
17100     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17101       DisplayError(_("Bad FEN position in clipboard"), 0);
17102       return ;
17103     } else {
17104       int savedBlackPlaysFirst = blackPlaysFirst;
17105       EditPositionEvent();
17106       blackPlaysFirst = savedBlackPlaysFirst;
17107       CopyBoard(boards[0], initial_position);
17108       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17109       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17110       DisplayBothClocks();
17111       DrawPosition(FALSE, boards[currentMove]);
17112     }
17113   }
17114 }
17115
17116 static char cseq[12] = "\\   ";
17117
17118 Boolean
17119 set_cont_sequence (char *new_seq)
17120 {
17121     int len;
17122     Boolean ret;
17123
17124     // handle bad attempts to set the sequence
17125         if (!new_seq)
17126                 return 0; // acceptable error - no debug
17127
17128     len = strlen(new_seq);
17129     ret = (len > 0) && (len < sizeof(cseq));
17130     if (ret)
17131       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17132     else if (appData.debugMode)
17133       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17134     return ret;
17135 }
17136
17137 /*
17138     reformat a source message so words don't cross the width boundary.  internal
17139     newlines are not removed.  returns the wrapped size (no null character unless
17140     included in source message).  If dest is NULL, only calculate the size required
17141     for the dest buffer.  lp argument indicats line position upon entry, and it's
17142     passed back upon exit.
17143 */
17144 int
17145 wrap (char *dest, char *src, int count, int width, int *lp)
17146 {
17147     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17148
17149     cseq_len = strlen(cseq);
17150     old_line = line = *lp;
17151     ansi = len = clen = 0;
17152
17153     for (i=0; i < count; i++)
17154     {
17155         if (src[i] == '\033')
17156             ansi = 1;
17157
17158         // if we hit the width, back up
17159         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17160         {
17161             // store i & len in case the word is too long
17162             old_i = i, old_len = len;
17163
17164             // find the end of the last word
17165             while (i && src[i] != ' ' && src[i] != '\n')
17166             {
17167                 i--;
17168                 len--;
17169             }
17170
17171             // word too long?  restore i & len before splitting it
17172             if ((old_i-i+clen) >= width)
17173             {
17174                 i = old_i;
17175                 len = old_len;
17176             }
17177
17178             // extra space?
17179             if (i && src[i-1] == ' ')
17180                 len--;
17181
17182             if (src[i] != ' ' && src[i] != '\n')
17183             {
17184                 i--;
17185                 if (len)
17186                     len--;
17187             }
17188
17189             // now append the newline and continuation sequence
17190             if (dest)
17191                 dest[len] = '\n';
17192             len++;
17193             if (dest)
17194                 strncpy(dest+len, cseq, cseq_len);
17195             len += cseq_len;
17196             line = cseq_len;
17197             clen = cseq_len;
17198             continue;
17199         }
17200
17201         if (dest)
17202             dest[len] = src[i];
17203         len++;
17204         if (!ansi)
17205             line++;
17206         if (src[i] == '\n')
17207             line = 0;
17208         if (src[i] == 'm')
17209             ansi = 0;
17210     }
17211     if (dest && appData.debugMode)
17212     {
17213         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17214             count, width, line, len, *lp);
17215         show_bytes(debugFP, src, count);
17216         fprintf(debugFP, "\ndest: ");
17217         show_bytes(debugFP, dest, len);
17218         fprintf(debugFP, "\n");
17219     }
17220     *lp = dest ? line : old_line;
17221
17222     return len;
17223 }
17224
17225 // [HGM] vari: routines for shelving variations
17226 Boolean modeRestore = FALSE;
17227
17228 void
17229 PushInner (int firstMove, int lastMove)
17230 {
17231         int i, j, nrMoves = lastMove - firstMove;
17232
17233         // push current tail of game on stack
17234         savedResult[storedGames] = gameInfo.result;
17235         savedDetails[storedGames] = gameInfo.resultDetails;
17236         gameInfo.resultDetails = NULL;
17237         savedFirst[storedGames] = firstMove;
17238         savedLast [storedGames] = lastMove;
17239         savedFramePtr[storedGames] = framePtr;
17240         framePtr -= nrMoves; // reserve space for the boards
17241         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17242             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17243             for(j=0; j<MOVE_LEN; j++)
17244                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17245             for(j=0; j<2*MOVE_LEN; j++)
17246                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17247             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17248             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17249             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17250             pvInfoList[firstMove+i-1].depth = 0;
17251             commentList[framePtr+i] = commentList[firstMove+i];
17252             commentList[firstMove+i] = NULL;
17253         }
17254
17255         storedGames++;
17256         forwardMostMove = firstMove; // truncate game so we can start variation
17257 }
17258
17259 void
17260 PushTail (int firstMove, int lastMove)
17261 {
17262         if(appData.icsActive) { // only in local mode
17263                 forwardMostMove = currentMove; // mimic old ICS behavior
17264                 return;
17265         }
17266         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17267
17268         PushInner(firstMove, lastMove);
17269         if(storedGames == 1) GreyRevert(FALSE);
17270         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17271 }
17272
17273 void
17274 PopInner (Boolean annotate)
17275 {
17276         int i, j, nrMoves;
17277         char buf[8000], moveBuf[20];
17278
17279         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17280         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17281         nrMoves = savedLast[storedGames] - currentMove;
17282         if(annotate) {
17283                 int cnt = 10;
17284                 if(!WhiteOnMove(currentMove))
17285                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17286                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17287                 for(i=currentMove; i<forwardMostMove; i++) {
17288                         if(WhiteOnMove(i))
17289                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17290                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17291                         strcat(buf, moveBuf);
17292                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17293                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17294                 }
17295                 strcat(buf, ")");
17296         }
17297         for(i=1; i<=nrMoves; i++) { // copy last variation back
17298             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17299             for(j=0; j<MOVE_LEN; j++)
17300                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17301             for(j=0; j<2*MOVE_LEN; j++)
17302                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17303             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17304             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17305             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17306             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17307             commentList[currentMove+i] = commentList[framePtr+i];
17308             commentList[framePtr+i] = NULL;
17309         }
17310         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17311         framePtr = savedFramePtr[storedGames];
17312         gameInfo.result = savedResult[storedGames];
17313         if(gameInfo.resultDetails != NULL) {
17314             free(gameInfo.resultDetails);
17315       }
17316         gameInfo.resultDetails = savedDetails[storedGames];
17317         forwardMostMove = currentMove + nrMoves;
17318 }
17319
17320 Boolean
17321 PopTail (Boolean annotate)
17322 {
17323         if(appData.icsActive) return FALSE; // only in local mode
17324         if(!storedGames) return FALSE; // sanity
17325         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17326
17327         PopInner(annotate);
17328         if(currentMove < forwardMostMove) ForwardEvent(); else
17329         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17330
17331         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17332         return TRUE;
17333 }
17334
17335 void
17336 CleanupTail ()
17337 {       // remove all shelved variations
17338         int i;
17339         for(i=0; i<storedGames; i++) {
17340             if(savedDetails[i])
17341                 free(savedDetails[i]);
17342             savedDetails[i] = NULL;
17343         }
17344         for(i=framePtr; i<MAX_MOVES; i++) {
17345                 if(commentList[i]) free(commentList[i]);
17346                 commentList[i] = NULL;
17347         }
17348         framePtr = MAX_MOVES-1;
17349         storedGames = 0;
17350 }
17351
17352 void
17353 LoadVariation (int index, char *text)
17354 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17355         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17356         int level = 0, move;
17357
17358         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17359         // first find outermost bracketing variation
17360         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17361             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17362                 if(*p == '{') wait = '}'; else
17363                 if(*p == '[') wait = ']'; else
17364                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17365                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17366             }
17367             if(*p == wait) wait = NULLCHAR; // closing ]} found
17368             p++;
17369         }
17370         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17371         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17372         end[1] = NULLCHAR; // clip off comment beyond variation
17373         ToNrEvent(currentMove-1);
17374         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17375         // kludge: use ParsePV() to append variation to game
17376         move = currentMove;
17377         ParsePV(start, TRUE, TRUE);
17378         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17379         ClearPremoveHighlights();
17380         CommentPopDown();
17381         ToNrEvent(currentMove+1);
17382 }
17383