Suppress testing for availability in bughouse drops
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "gettext.h"
133
134 #ifdef ENABLE_NLS
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
138 #else
139 # ifdef WIN32
140 #   define _(s) T_(s)
141 #   define N_(s) s
142 # else
143 #   define _(s) (s)
144 #   define N_(s) s
145 #   define T_(s) s
146 # endif
147 #endif
148
149
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152                          char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154                       char *buf, int count, int error));
155 void ics_printf P((char *format, ...));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226
227 #ifdef WIN32
228        extern void ConsoleCreate();
229 #endif
230
231 ChessProgramState *WhitePlayer();
232 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
233 int VerifyDisplayMode P(());
234
235 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
236 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
237 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
238 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
239 void ics_update_width P((int new_width));
240 extern char installDir[MSG_SIZ];
241 VariantClass startVariant; /* [HGM] nicks: initial variant */
242 Boolean abortMatch;
243
244 extern int tinyLayout, smallLayout;
245 ChessProgramStats programStats;
246 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
247 int endPV = -1;
248 static int exiting = 0; /* [HGM] moved to top */
249 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
250 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
251 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
252 int partnerHighlight[2];
253 Boolean partnerBoardValid = 0;
254 char partnerStatus[MSG_SIZ];
255 Boolean partnerUp;
256 Boolean originalFlip;
257 Boolean twoBoards = 0;
258 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
259 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
260 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
261 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
262 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
263 int opponentKibitzes;
264 int lastSavedGame; /* [HGM] save: ID of game */
265 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
266 extern int chatCount;
267 int chattingPartner;
268 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
269 char lastMsg[MSG_SIZ];
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
272 int promoDefaultAltered;
273
274 /* States for ics_getting_history */
275 #define H_FALSE 0
276 #define H_REQUESTED 1
277 #define H_GOT_REQ_HEADER 2
278 #define H_GOT_UNREQ_HEADER 3
279 #define H_GETTING_MOVES 4
280 #define H_GOT_UNWANTED_HEADER 5
281
282 /* whosays values for GameEnds */
283 #define GE_ICS 0
284 #define GE_ENGINE 1
285 #define GE_PLAYER 2
286 #define GE_FILE 3
287 #define GE_XBOARD 4
288 #define GE_ENGINE1 5
289 #define GE_ENGINE2 6
290
291 /* Maximum number of games in a cmail message */
292 #define CMAIL_MAX_GAMES 20
293
294 /* Different types of move when calling RegisterMove */
295 #define CMAIL_MOVE   0
296 #define CMAIL_RESIGN 1
297 #define CMAIL_DRAW   2
298 #define CMAIL_ACCEPT 3
299
300 /* Different types of result to remember for each game */
301 #define CMAIL_NOT_RESULT 0
302 #define CMAIL_OLD_RESULT 1
303 #define CMAIL_NEW_RESULT 2
304
305 /* Telnet protocol constants */
306 #define TN_WILL 0373
307 #define TN_WONT 0374
308 #define TN_DO   0375
309 #define TN_DONT 0376
310 #define TN_IAC  0377
311 #define TN_ECHO 0001
312 #define TN_SGA  0003
313 #define TN_PORT 23
314
315 char*
316 safeStrCpy (char *dst, const char *src, size_t count)
317 { // [HGM] made safe
318   int i;
319   assert( dst != NULL );
320   assert( src != NULL );
321   assert( count > 0 );
322
323   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
324   if(  i == count && dst[count-1] != NULLCHAR)
325     {
326       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
327       if(appData.debugMode)
328       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
329     }
330
331   return dst;
332 }
333
334 /* Some compiler can't cast u64 to double
335  * This function do the job for us:
336
337  * We use the highest bit for cast, this only
338  * works if the highest bit is not
339  * in use (This should not happen)
340  *
341  * We used this for all compiler
342  */
343 double
344 u64ToDouble (u64 value)
345 {
346   double r;
347   u64 tmp = value & u64Const(0x7fffffffffffffff);
348   r = (double)(s64)tmp;
349   if (value & u64Const(0x8000000000000000))
350        r +=  9.2233720368547758080e18; /* 2^63 */
351  return r;
352 }
353
354 /* Fake up flags for now, as we aren't keeping track of castling
355    availability yet. [HGM] Change of logic: the flag now only
356    indicates the type of castlings allowed by the rule of the game.
357    The actual rights themselves are maintained in the array
358    castlingRights, as part of the game history, and are not probed
359    by this function.
360  */
361 int
362 PosFlags (index)
363 {
364   int flags = F_ALL_CASTLE_OK;
365   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
366   switch (gameInfo.variant) {
367   case VariantSuicide:
368     flags &= ~F_ALL_CASTLE_OK;
369   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
370     flags |= F_IGNORE_CHECK;
371   case VariantLosers:
372     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
373     break;
374   case VariantAtomic:
375     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
376     break;
377   case VariantKriegspiel:
378     flags |= F_KRIEGSPIEL_CAPTURE;
379     break;
380   case VariantCapaRandom:
381   case VariantFischeRandom:
382     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
383   case VariantNoCastle:
384   case VariantShatranj:
385   case VariantCourier:
386   case VariantMakruk:
387   case VariantGrand:
388     flags &= ~F_ALL_CASTLE_OK;
389     break;
390   default:
391     break;
392   }
393   return flags;
394 }
395
396 FILE *gameFileFP, *debugFP, *serverFP;
397 char *currentDebugFile; // [HGM] debug split: to remember name
398
399 /*
400     [AS] Note: sometimes, the sscanf() function is used to parse the input
401     into a fixed-size buffer. Because of this, we must be prepared to
402     receive strings as long as the size of the input buffer, which is currently
403     set to 4K for Windows and 8K for the rest.
404     So, we must either allocate sufficiently large buffers here, or
405     reduce the size of the input buffer in the input reading part.
406 */
407
408 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
409 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
410 char thinkOutput1[MSG_SIZ*10];
411
412 ChessProgramState first, second, pairing;
413
414 /* premove variables */
415 int premoveToX = 0;
416 int premoveToY = 0;
417 int premoveFromX = 0;
418 int premoveFromY = 0;
419 int premovePromoChar = 0;
420 int gotPremove = 0;
421 Boolean alarmSounded;
422 /* end premove variables */
423
424 char *ics_prefix = "$";
425 int ics_type = ICS_GENERIC;
426
427 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
428 int pauseExamForwardMostMove = 0;
429 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
430 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
431 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
432 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
433 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
434 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
435 int whiteFlag = FALSE, blackFlag = FALSE;
436 int userOfferedDraw = FALSE;
437 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
438 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
439 int cmailMoveType[CMAIL_MAX_GAMES];
440 long ics_clock_paused = 0;
441 ProcRef icsPR = NoProc, cmailPR = NoProc;
442 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
443 GameMode gameMode = BeginningOfGame;
444 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
445 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
446 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
447 int hiddenThinkOutputState = 0; /* [AS] */
448 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
449 int adjudicateLossPlies = 6;
450 char white_holding[64], black_holding[64];
451 TimeMark lastNodeCountTime;
452 long lastNodeCount=0;
453 int shiftKey, controlKey; // [HGM] set by mouse handler
454
455 int have_sent_ICS_logon = 0;
456 int movesPerSession;
457 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
458 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
459 Boolean adjustedClock;
460 long timeControl_2; /* [AS] Allow separate time controls */
461 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
462 long timeRemaining[2][MAX_MOVES];
463 int matchGame = 0, nextGame = 0, roundNr = 0;
464 Boolean waitingForGame = FALSE;
465 TimeMark programStartTime, pauseStart;
466 char ics_handle[MSG_SIZ];
467 int have_set_title = 0;
468
469 /* animateTraining preserves the state of appData.animate
470  * when Training mode is activated. This allows the
471  * response to be animated when appData.animate == TRUE and
472  * appData.animateDragging == TRUE.
473  */
474 Boolean animateTraining;
475
476 GameInfo gameInfo;
477
478 AppData appData;
479
480 Board boards[MAX_MOVES];
481 /* [HGM] Following 7 needed for accurate legality tests: */
482 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
483 signed char  initialRights[BOARD_FILES];
484 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
485 int   initialRulePlies, FENrulePlies;
486 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
487 int loadFlag = 0;
488 Boolean shuffleOpenings;
489 int mute; // mute all sounds
490
491 // [HGM] vari: next 12 to save and restore variations
492 #define MAX_VARIATIONS 10
493 int framePtr = MAX_MOVES-1; // points to free stack entry
494 int storedGames = 0;
495 int savedFirst[MAX_VARIATIONS];
496 int savedLast[MAX_VARIATIONS];
497 int savedFramePtr[MAX_VARIATIONS];
498 char *savedDetails[MAX_VARIATIONS];
499 ChessMove savedResult[MAX_VARIATIONS];
500
501 void PushTail P((int firstMove, int lastMove));
502 Boolean PopTail P((Boolean annotate));
503 void PushInner P((int firstMove, int lastMove));
504 void PopInner P((Boolean annotate));
505 void CleanupTail P((void));
506
507 ChessSquare  FIDEArray[2][BOARD_FILES] = {
508     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
509         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
510     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
511         BlackKing, BlackBishop, BlackKnight, BlackRook }
512 };
513
514 ChessSquare twoKingsArray[2][BOARD_FILES] = {
515     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
516         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
517     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
518         BlackKing, BlackKing, BlackKnight, BlackRook }
519 };
520
521 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
522     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
523         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
524     { BlackRook, BlackMan, BlackBishop, BlackQueen,
525         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
526 };
527
528 ChessSquare SpartanArray[2][BOARD_FILES] = {
529     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
530         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
531     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
532         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
533 };
534
535 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
536     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
537         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
538     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
539         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
540 };
541
542 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
543     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
544         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
545     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
546         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
547 };
548
549 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
550     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
551         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
552     { BlackRook, BlackKnight, BlackMan, BlackFerz,
553         BlackKing, BlackMan, BlackKnight, BlackRook }
554 };
555
556
557 #if (BOARD_FILES>=10)
558 ChessSquare ShogiArray[2][BOARD_FILES] = {
559     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
560         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
561     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
562         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
563 };
564
565 ChessSquare XiangqiArray[2][BOARD_FILES] = {
566     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
567         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
568     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
569         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
570 };
571
572 ChessSquare CapablancaArray[2][BOARD_FILES] = {
573     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
574         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
575     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
576         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
577 };
578
579 ChessSquare GreatArray[2][BOARD_FILES] = {
580     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
581         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
582     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
583         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
584 };
585
586 ChessSquare JanusArray[2][BOARD_FILES] = {
587     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
588         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
589     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
590         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
591 };
592
593 ChessSquare GrandArray[2][BOARD_FILES] = {
594     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
595         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
596     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
597         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
598 };
599
600 #ifdef GOTHIC
601 ChessSquare GothicArray[2][BOARD_FILES] = {
602     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
603         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
604     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
605         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
606 };
607 #else // !GOTHIC
608 #define GothicArray CapablancaArray
609 #endif // !GOTHIC
610
611 #ifdef FALCON
612 ChessSquare FalconArray[2][BOARD_FILES] = {
613     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
614         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
615     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
616         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
617 };
618 #else // !FALCON
619 #define FalconArray CapablancaArray
620 #endif // !FALCON
621
622 #else // !(BOARD_FILES>=10)
623 #define XiangqiPosition FIDEArray
624 #define CapablancaArray FIDEArray
625 #define GothicArray FIDEArray
626 #define GreatArray FIDEArray
627 #endif // !(BOARD_FILES>=10)
628
629 #if (BOARD_FILES>=12)
630 ChessSquare CourierArray[2][BOARD_FILES] = {
631     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
632         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
633     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
634         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
635 };
636 #else // !(BOARD_FILES>=12)
637 #define CourierArray CapablancaArray
638 #endif // !(BOARD_FILES>=12)
639
640
641 Board initialPosition;
642
643
644 /* Convert str to a rating. Checks for special cases of "----",
645
646    "++++", etc. Also strips ()'s */
647 int
648 string_to_rating (char *str)
649 {
650   while(*str && !isdigit(*str)) ++str;
651   if (!*str)
652     return 0;   /* One of the special "no rating" cases */
653   else
654     return atoi(str);
655 }
656
657 void
658 ClearProgramStats ()
659 {
660     /* Init programStats */
661     programStats.movelist[0] = 0;
662     programStats.depth = 0;
663     programStats.nr_moves = 0;
664     programStats.moves_left = 0;
665     programStats.nodes = 0;
666     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
667     programStats.score = 0;
668     programStats.got_only_move = 0;
669     programStats.got_fail = 0;
670     programStats.line_is_book = 0;
671 }
672
673 void
674 CommonEngineInit ()
675 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
676     if (appData.firstPlaysBlack) {
677         first.twoMachinesColor = "black\n";
678         second.twoMachinesColor = "white\n";
679     } else {
680         first.twoMachinesColor = "white\n";
681         second.twoMachinesColor = "black\n";
682     }
683
684     first.other = &second;
685     second.other = &first;
686
687     { float norm = 1;
688         if(appData.timeOddsMode) {
689             norm = appData.timeOdds[0];
690             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
691         }
692         first.timeOdds  = appData.timeOdds[0]/norm;
693         second.timeOdds = appData.timeOdds[1]/norm;
694     }
695
696     if(programVersion) free(programVersion);
697     if (appData.noChessProgram) {
698         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
699         sprintf(programVersion, "%s", PACKAGE_STRING);
700     } else {
701       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
702       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
703       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
704     }
705 }
706
707 void
708 UnloadEngine (ChessProgramState *cps)
709 {
710         /* Kill off first chess program */
711         if (cps->isr != NULL)
712           RemoveInputSource(cps->isr);
713         cps->isr = NULL;
714
715         if (cps->pr != NoProc) {
716             ExitAnalyzeMode();
717             DoSleep( appData.delayBeforeQuit );
718             SendToProgram("quit\n", cps);
719             DoSleep( appData.delayAfterQuit );
720             DestroyChildProcess(cps->pr, cps->useSigterm);
721         }
722         cps->pr = NoProc;
723         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
724 }
725
726 void
727 ClearOptions (ChessProgramState *cps)
728 {
729     int i;
730     cps->nrOptions = cps->comboCnt = 0;
731     for(i=0; i<MAX_OPTIONS; i++) {
732         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
733         cps->option[i].textValue = 0;
734     }
735 }
736
737 char *engineNames[] = {
738   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
739      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
740 N_("first"),
741   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
742      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
743 N_("second")
744 };
745
746 void
747 InitEngine (ChessProgramState *cps, int n)
748 {   // [HGM] all engine initialiation put in a function that does one engine
749
750     ClearOptions(cps);
751
752     cps->which = engineNames[n];
753     cps->maybeThinking = FALSE;
754     cps->pr = NoProc;
755     cps->isr = NULL;
756     cps->sendTime = 2;
757     cps->sendDrawOffers = 1;
758
759     cps->program = appData.chessProgram[n];
760     cps->host = appData.host[n];
761     cps->dir = appData.directory[n];
762     cps->initString = appData.engInitString[n];
763     cps->computerString = appData.computerString[n];
764     cps->useSigint  = TRUE;
765     cps->useSigterm = TRUE;
766     cps->reuse = appData.reuse[n];
767     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
768     cps->useSetboard = FALSE;
769     cps->useSAN = FALSE;
770     cps->usePing = FALSE;
771     cps->lastPing = 0;
772     cps->lastPong = 0;
773     cps->usePlayother = FALSE;
774     cps->useColors = TRUE;
775     cps->useUsermove = FALSE;
776     cps->sendICS = FALSE;
777     cps->sendName = appData.icsActive;
778     cps->sdKludge = FALSE;
779     cps->stKludge = FALSE;
780     TidyProgramName(cps->program, cps->host, cps->tidy);
781     cps->matchWins = 0;
782     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
783     cps->analysisSupport = 2; /* detect */
784     cps->analyzing = FALSE;
785     cps->initDone = FALSE;
786
787     /* New features added by Tord: */
788     cps->useFEN960 = FALSE;
789     cps->useOOCastle = TRUE;
790     /* End of new features added by Tord. */
791     cps->fenOverride  = appData.fenOverride[n];
792
793     /* [HGM] time odds: set factor for each machine */
794     cps->timeOdds  = appData.timeOdds[n];
795
796     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
797     cps->accumulateTC = appData.accumulateTC[n];
798     cps->maxNrOfSessions = 1;
799
800     /* [HGM] debug */
801     cps->debug = FALSE;
802
803     cps->supportsNPS = UNKNOWN;
804     cps->memSize = FALSE;
805     cps->maxCores = FALSE;
806     cps->egtFormats[0] = NULLCHAR;
807
808     /* [HGM] options */
809     cps->optionSettings  = appData.engOptions[n];
810
811     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
812     cps->isUCI = appData.isUCI[n]; /* [AS] */
813     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
814
815     if (appData.protocolVersion[n] > PROTOVER
816         || appData.protocolVersion[n] < 1)
817       {
818         char buf[MSG_SIZ];
819         int len;
820
821         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
822                        appData.protocolVersion[n]);
823         if( (len >= MSG_SIZ) && appData.debugMode )
824           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
825
826         DisplayFatalError(buf, 0, 2);
827       }
828     else
829       {
830         cps->protocolVersion = appData.protocolVersion[n];
831       }
832
833     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
834     ParseFeatures(appData.featureDefaults, cps);
835 }
836
837 ChessProgramState *savCps;
838
839 void
840 LoadEngine ()
841 {
842     int i;
843     if(WaitForEngine(savCps, LoadEngine)) return;
844     CommonEngineInit(); // recalculate time odds
845     if(gameInfo.variant != StringToVariant(appData.variant)) {
846         // we changed variant when loading the engine; this forces us to reset
847         Reset(TRUE, savCps != &first);
848         EditGameEvent(); // for consistency with other path, as Reset changes mode
849     }
850     InitChessProgram(savCps, FALSE);
851     SendToProgram("force\n", savCps);
852     DisplayMessage("", "");
853     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
854     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
855     ThawUI();
856     SetGNUMode();
857 }
858
859 void
860 ReplaceEngine (ChessProgramState *cps, int n)
861 {
862     EditGameEvent();
863     UnloadEngine(cps);
864     appData.noChessProgram = FALSE;
865     appData.clockMode = TRUE;
866     InitEngine(cps, n);
867     UpdateLogos(TRUE);
868     if(n) return; // only startup first engine immediately; second can wait
869     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
870     LoadEngine();
871 }
872
873 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
874 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
875
876 static char resetOptions[] = 
877         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
878         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
879         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
880         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
881
882 void
883 FloatToFront(char **list, char *engineLine)
884 {
885     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
886     int i=0;
887     if(appData.recentEngines <= 0) return;
888     TidyProgramName(engineLine, "localhost", tidy+1);
889     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
890     strncpy(buf+1, *list, MSG_SIZ-50);
891     if(p = strstr(buf, tidy)) { // tidy name appears in list
892         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
893         while(*p++ = *++q); // squeeze out
894     }
895     strcat(tidy, buf+1); // put list behind tidy name
896     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
897     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
898     ASSIGN(*list, tidy+1);
899 }
900
901 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
902
903 void
904 Load (ChessProgramState *cps, int i)
905 {
906     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
907     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
908         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
909         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
910         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
911         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
912         appData.firstProtocolVersion = PROTOVER;
913         ParseArgsFromString(buf);
914         SwapEngines(i);
915         ReplaceEngine(cps, i);
916         FloatToFront(&appData.recentEngineList, engineLine);
917         return;
918     }
919     p = engineName;
920     while(q = strchr(p, SLASH)) p = q+1;
921     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
922     if(engineDir[0] != NULLCHAR) {
923         ASSIGN(appData.directory[i], engineDir); p = engineName;
924     } else if(p != engineName) { // derive directory from engine path, when not given
925         p[-1] = 0;
926         ASSIGN(appData.directory[i], engineName);
927         p[-1] = SLASH;
928         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
929     } else { ASSIGN(appData.directory[i], "."); }
930     if(params[0]) {
931         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
932         snprintf(command, MSG_SIZ, "%s %s", p, params);
933         p = command;
934     }
935     ASSIGN(appData.chessProgram[i], p);
936     appData.isUCI[i] = isUCI;
937     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
938     appData.hasOwnBookUCI[i] = hasBook;
939     if(!nickName[0]) useNick = FALSE;
940     if(useNick) ASSIGN(appData.pgnName[i], nickName);
941     if(addToList) {
942         int len;
943         char quote;
944         q = firstChessProgramNames;
945         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
946         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
947         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
948                         quote, p, quote, appData.directory[i], 
949                         useNick ? " -fn \"" : "",
950                         useNick ? nickName : "",
951                         useNick ? "\"" : "",
952                         v1 ? " -firstProtocolVersion 1" : "",
953                         hasBook ? "" : " -fNoOwnBookUCI",
954                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
955                         storeVariant ? " -variant " : "",
956                         storeVariant ? VariantName(gameInfo.variant) : "");
957         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
958         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
959         if(insert != q) insert[-1] = NULLCHAR;
960         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
961         if(q)   free(q);
962         FloatToFront(&appData.recentEngineList, buf);
963     }
964     ReplaceEngine(cps, i);
965 }
966
967 void
968 InitTimeControls ()
969 {
970     int matched, min, sec;
971     /*
972      * Parse timeControl resource
973      */
974     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
975                           appData.movesPerSession)) {
976         char buf[MSG_SIZ];
977         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
978         DisplayFatalError(buf, 0, 2);
979     }
980
981     /*
982      * Parse searchTime resource
983      */
984     if (*appData.searchTime != NULLCHAR) {
985         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
986         if (matched == 1) {
987             searchTime = min * 60;
988         } else if (matched == 2) {
989             searchTime = min * 60 + sec;
990         } else {
991             char buf[MSG_SIZ];
992             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
993             DisplayFatalError(buf, 0, 2);
994         }
995     }
996 }
997
998 void
999 InitBackEnd1 ()
1000 {
1001
1002     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1003     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1004
1005     GetTimeMark(&programStartTime);
1006     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1007     appData.seedBase = random() + (random()<<15);
1008     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1009
1010     ClearProgramStats();
1011     programStats.ok_to_send = 1;
1012     programStats.seen_stat = 0;
1013
1014     /*
1015      * Initialize game list
1016      */
1017     ListNew(&gameList);
1018
1019
1020     /*
1021      * Internet chess server status
1022      */
1023     if (appData.icsActive) {
1024         appData.matchMode = FALSE;
1025         appData.matchGames = 0;
1026 #if ZIPPY
1027         appData.noChessProgram = !appData.zippyPlay;
1028 #else
1029         appData.zippyPlay = FALSE;
1030         appData.zippyTalk = FALSE;
1031         appData.noChessProgram = TRUE;
1032 #endif
1033         if (*appData.icsHelper != NULLCHAR) {
1034             appData.useTelnet = TRUE;
1035             appData.telnetProgram = appData.icsHelper;
1036         }
1037     } else {
1038         appData.zippyTalk = appData.zippyPlay = FALSE;
1039     }
1040
1041     /* [AS] Initialize pv info list [HGM] and game state */
1042     {
1043         int i, j;
1044
1045         for( i=0; i<=framePtr; i++ ) {
1046             pvInfoList[i].depth = -1;
1047             boards[i][EP_STATUS] = EP_NONE;
1048             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1049         }
1050     }
1051
1052     InitTimeControls();
1053
1054     /* [AS] Adjudication threshold */
1055     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1056
1057     InitEngine(&first, 0);
1058     InitEngine(&second, 1);
1059     CommonEngineInit();
1060
1061     pairing.which = "pairing"; // pairing engine
1062     pairing.pr = NoProc;
1063     pairing.isr = NULL;
1064     pairing.program = appData.pairingEngine;
1065     pairing.host = "localhost";
1066     pairing.dir = ".";
1067
1068     if (appData.icsActive) {
1069         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1070     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1071         appData.clockMode = FALSE;
1072         first.sendTime = second.sendTime = 0;
1073     }
1074
1075 #if ZIPPY
1076     /* Override some settings from environment variables, for backward
1077        compatibility.  Unfortunately it's not feasible to have the env
1078        vars just set defaults, at least in xboard.  Ugh.
1079     */
1080     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1081       ZippyInit();
1082     }
1083 #endif
1084
1085     if (!appData.icsActive) {
1086       char buf[MSG_SIZ];
1087       int len;
1088
1089       /* Check for variants that are supported only in ICS mode,
1090          or not at all.  Some that are accepted here nevertheless
1091          have bugs; see comments below.
1092       */
1093       VariantClass variant = StringToVariant(appData.variant);
1094       switch (variant) {
1095       case VariantBughouse:     /* need four players and two boards */
1096       case VariantKriegspiel:   /* need to hide pieces and move details */
1097         /* case VariantFischeRandom: (Fabien: moved below) */
1098         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1099         if( (len >= MSG_SIZ) && appData.debugMode )
1100           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1101
1102         DisplayFatalError(buf, 0, 2);
1103         return;
1104
1105       case VariantUnknown:
1106       case VariantLoadable:
1107       case Variant29:
1108       case Variant30:
1109       case Variant31:
1110       case Variant32:
1111       case Variant33:
1112       case Variant34:
1113       case Variant35:
1114       case Variant36:
1115       default:
1116         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1117         if( (len >= MSG_SIZ) && appData.debugMode )
1118           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1119
1120         DisplayFatalError(buf, 0, 2);
1121         return;
1122
1123       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1124       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1125       case VariantGothic:     /* [HGM] should work */
1126       case VariantCapablanca: /* [HGM] should work */
1127       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1128       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1129       case VariantKnightmate: /* [HGM] should work */
1130       case VariantCylinder:   /* [HGM] untested */
1131       case VariantFalcon:     /* [HGM] untested */
1132       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1133                                  offboard interposition not understood */
1134       case VariantNormal:     /* definitely works! */
1135       case VariantWildCastle: /* pieces not automatically shuffled */
1136       case VariantNoCastle:   /* pieces not automatically shuffled */
1137       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1138       case VariantLosers:     /* should work except for win condition,
1139                                  and doesn't know captures are mandatory */
1140       case VariantSuicide:    /* should work except for win condition,
1141                                  and doesn't know captures are mandatory */
1142       case VariantGiveaway:   /* should work except for win condition,
1143                                  and doesn't know captures are mandatory */
1144       case VariantTwoKings:   /* should work */
1145       case VariantAtomic:     /* should work except for win condition */
1146       case Variant3Check:     /* should work except for win condition */
1147       case VariantShatranj:   /* should work except for all win conditions */
1148       case VariantMakruk:     /* should work except for draw countdown */
1149       case VariantBerolina:   /* might work if TestLegality is off */
1150       case VariantCapaRandom: /* should work */
1151       case VariantJanus:      /* should work */
1152       case VariantSuper:      /* experimental */
1153       case VariantGreat:      /* experimental, requires legality testing to be off */
1154       case VariantSChess:     /* S-Chess, should work */
1155       case VariantGrand:      /* should work */
1156       case VariantSpartan:    /* should work */
1157         break;
1158       }
1159     }
1160
1161 }
1162
1163 int
1164 NextIntegerFromString (char ** str, long * value)
1165 {
1166     int result = -1;
1167     char * s = *str;
1168
1169     while( *s == ' ' || *s == '\t' ) {
1170         s++;
1171     }
1172
1173     *value = 0;
1174
1175     if( *s >= '0' && *s <= '9' ) {
1176         while( *s >= '0' && *s <= '9' ) {
1177             *value = *value * 10 + (*s - '0');
1178             s++;
1179         }
1180
1181         result = 0;
1182     }
1183
1184     *str = s;
1185
1186     return result;
1187 }
1188
1189 int
1190 NextTimeControlFromString (char ** str, long * value)
1191 {
1192     long temp;
1193     int result = NextIntegerFromString( str, &temp );
1194
1195     if( result == 0 ) {
1196         *value = temp * 60; /* Minutes */
1197         if( **str == ':' ) {
1198             (*str)++;
1199             result = NextIntegerFromString( str, &temp );
1200             *value += temp; /* Seconds */
1201         }
1202     }
1203
1204     return result;
1205 }
1206
1207 int
1208 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1209 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1210     int result = -1, type = 0; long temp, temp2;
1211
1212     if(**str != ':') return -1; // old params remain in force!
1213     (*str)++;
1214     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1215     if( NextIntegerFromString( str, &temp ) ) return -1;
1216     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1217
1218     if(**str != '/') {
1219         /* time only: incremental or sudden-death time control */
1220         if(**str == '+') { /* increment follows; read it */
1221             (*str)++;
1222             if(**str == '!') type = *(*str)++; // Bronstein TC
1223             if(result = NextIntegerFromString( str, &temp2)) return -1;
1224             *inc = temp2 * 1000;
1225             if(**str == '.') { // read fraction of increment
1226                 char *start = ++(*str);
1227                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1228                 temp2 *= 1000;
1229                 while(start++ < *str) temp2 /= 10;
1230                 *inc += temp2;
1231             }
1232         } else *inc = 0;
1233         *moves = 0; *tc = temp * 1000; *incType = type;
1234         return 0;
1235     }
1236
1237     (*str)++; /* classical time control */
1238     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1239
1240     if(result == 0) {
1241         *moves = temp;
1242         *tc    = temp2 * 1000;
1243         *inc   = 0;
1244         *incType = type;
1245     }
1246     return result;
1247 }
1248
1249 int
1250 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1251 {   /* [HGM] get time to add from the multi-session time-control string */
1252     int incType, moves=1; /* kludge to force reading of first session */
1253     long time, increment;
1254     char *s = tcString;
1255
1256     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1257     do {
1258         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1259         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1260         if(movenr == -1) return time;    /* last move before new session     */
1261         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1262         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1263         if(!moves) return increment;     /* current session is incremental   */
1264         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1265     } while(movenr >= -1);               /* try again for next session       */
1266
1267     return 0; // no new time quota on this move
1268 }
1269
1270 int
1271 ParseTimeControl (char *tc, float ti, int mps)
1272 {
1273   long tc1;
1274   long tc2;
1275   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1276   int min, sec=0;
1277
1278   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1279   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1280       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1281   if(ti > 0) {
1282
1283     if(mps)
1284       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1285     else 
1286       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1287   } else {
1288     if(mps)
1289       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1290     else 
1291       snprintf(buf, MSG_SIZ, ":%s", mytc);
1292   }
1293   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1294   
1295   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1296     return FALSE;
1297   }
1298
1299   if( *tc == '/' ) {
1300     /* Parse second time control */
1301     tc++;
1302
1303     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1304       return FALSE;
1305     }
1306
1307     if( tc2 == 0 ) {
1308       return FALSE;
1309     }
1310
1311     timeControl_2 = tc2 * 1000;
1312   }
1313   else {
1314     timeControl_2 = 0;
1315   }
1316
1317   if( tc1 == 0 ) {
1318     return FALSE;
1319   }
1320
1321   timeControl = tc1 * 1000;
1322
1323   if (ti >= 0) {
1324     timeIncrement = ti * 1000;  /* convert to ms */
1325     movesPerSession = 0;
1326   } else {
1327     timeIncrement = 0;
1328     movesPerSession = mps;
1329   }
1330   return TRUE;
1331 }
1332
1333 void
1334 InitBackEnd2 ()
1335 {
1336     if (appData.debugMode) {
1337         fprintf(debugFP, "%s\n", programVersion);
1338     }
1339     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1340
1341     set_cont_sequence(appData.wrapContSeq);
1342     if (appData.matchGames > 0) {
1343         appData.matchMode = TRUE;
1344     } else if (appData.matchMode) {
1345         appData.matchGames = 1;
1346     }
1347     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1348         appData.matchGames = appData.sameColorGames;
1349     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1350         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1351         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1352     }
1353     Reset(TRUE, FALSE);
1354     if (appData.noChessProgram || first.protocolVersion == 1) {
1355       InitBackEnd3();
1356     } else {
1357       /* kludge: allow timeout for initial "feature" commands */
1358       FreezeUI();
1359       DisplayMessage("", _("Starting chess program"));
1360       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1361     }
1362 }
1363
1364 int
1365 CalculateIndex (int index, int gameNr)
1366 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1367     int res;
1368     if(index > 0) return index; // fixed nmber
1369     if(index == 0) return 1;
1370     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1371     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1372     return res;
1373 }
1374
1375 int
1376 LoadGameOrPosition (int gameNr)
1377 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1378     if (*appData.loadGameFile != NULLCHAR) {
1379         if (!LoadGameFromFile(appData.loadGameFile,
1380                 CalculateIndex(appData.loadGameIndex, gameNr),
1381                               appData.loadGameFile, FALSE)) {
1382             DisplayFatalError(_("Bad game file"), 0, 1);
1383             return 0;
1384         }
1385     } else if (*appData.loadPositionFile != NULLCHAR) {
1386         if (!LoadPositionFromFile(appData.loadPositionFile,
1387                 CalculateIndex(appData.loadPositionIndex, gameNr),
1388                                   appData.loadPositionFile)) {
1389             DisplayFatalError(_("Bad position file"), 0, 1);
1390             return 0;
1391         }
1392     }
1393     return 1;
1394 }
1395
1396 void
1397 ReserveGame (int gameNr, char resChar)
1398 {
1399     FILE *tf = fopen(appData.tourneyFile, "r+");
1400     char *p, *q, c, buf[MSG_SIZ];
1401     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1402     safeStrCpy(buf, lastMsg, MSG_SIZ);
1403     DisplayMessage(_("Pick new game"), "");
1404     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1405     ParseArgsFromFile(tf);
1406     p = q = appData.results;
1407     if(appData.debugMode) {
1408       char *r = appData.participants;
1409       fprintf(debugFP, "results = '%s'\n", p);
1410       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1411       fprintf(debugFP, "\n");
1412     }
1413     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1414     nextGame = q - p;
1415     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1416     safeStrCpy(q, p, strlen(p) + 2);
1417     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1418     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1419     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1420         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1421         q[nextGame] = '*';
1422     }
1423     fseek(tf, -(strlen(p)+4), SEEK_END);
1424     c = fgetc(tf);
1425     if(c != '"') // depending on DOS or Unix line endings we can be one off
1426          fseek(tf, -(strlen(p)+2), SEEK_END);
1427     else fseek(tf, -(strlen(p)+3), SEEK_END);
1428     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1429     DisplayMessage(buf, "");
1430     free(p); appData.results = q;
1431     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1432        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1433       int round = appData.defaultMatchGames * appData.tourneyType;
1434       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1435          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1436         UnloadEngine(&first);  // next game belongs to other pairing;
1437         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1438     }
1439     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1440 }
1441
1442 void
1443 MatchEvent (int mode)
1444 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1445         int dummy;
1446         if(matchMode) { // already in match mode: switch it off
1447             abortMatch = TRUE;
1448             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1449             return;
1450         }
1451 //      if(gameMode != BeginningOfGame) {
1452 //          DisplayError(_("You can only start a match from the initial position."), 0);
1453 //          return;
1454 //      }
1455         abortMatch = FALSE;
1456         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1457         /* Set up machine vs. machine match */
1458         nextGame = 0;
1459         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1460         if(appData.tourneyFile[0]) {
1461             ReserveGame(-1, 0);
1462             if(nextGame > appData.matchGames) {
1463                 char buf[MSG_SIZ];
1464                 if(strchr(appData.results, '*') == NULL) {
1465                     FILE *f;
1466                     appData.tourneyCycles++;
1467                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1468                         fclose(f);
1469                         NextTourneyGame(-1, &dummy);
1470                         ReserveGame(-1, 0);
1471                         if(nextGame <= appData.matchGames) {
1472                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1473                             matchMode = mode;
1474                             ScheduleDelayedEvent(NextMatchGame, 10000);
1475                             return;
1476                         }
1477                     }
1478                 }
1479                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1480                 DisplayError(buf, 0);
1481                 appData.tourneyFile[0] = 0;
1482                 return;
1483             }
1484         } else
1485         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1486             DisplayFatalError(_("Can't have a match with no chess programs"),
1487                               0, 2);
1488             return;
1489         }
1490         matchMode = mode;
1491         matchGame = roundNr = 1;
1492         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1493         NextMatchGame();
1494 }
1495
1496 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1497
1498 void
1499 InitBackEnd3 P((void))
1500 {
1501     GameMode initialMode;
1502     char buf[MSG_SIZ];
1503     int err, len;
1504
1505     InitChessProgram(&first, startedFromSetupPosition);
1506
1507     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1508         free(programVersion);
1509         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1510         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1511         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1512     }
1513
1514     if (appData.icsActive) {
1515 #ifdef WIN32
1516         /* [DM] Make a console window if needed [HGM] merged ifs */
1517         ConsoleCreate();
1518 #endif
1519         err = establish();
1520         if (err != 0)
1521           {
1522             if (*appData.icsCommPort != NULLCHAR)
1523               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1524                              appData.icsCommPort);
1525             else
1526               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1527                         appData.icsHost, appData.icsPort);
1528
1529             if( (len >= MSG_SIZ) && appData.debugMode )
1530               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1531
1532             DisplayFatalError(buf, err, 1);
1533             return;
1534         }
1535         SetICSMode();
1536         telnetISR =
1537           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1538         fromUserISR =
1539           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1540         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1541             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1542     } else if (appData.noChessProgram) {
1543         SetNCPMode();
1544     } else {
1545         SetGNUMode();
1546     }
1547
1548     if (*appData.cmailGameName != NULLCHAR) {
1549         SetCmailMode();
1550         OpenLoopback(&cmailPR);
1551         cmailISR =
1552           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1553     }
1554
1555     ThawUI();
1556     DisplayMessage("", "");
1557     if (StrCaseCmp(appData.initialMode, "") == 0) {
1558       initialMode = BeginningOfGame;
1559       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1560         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1561         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1562         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1563         ModeHighlight();
1564       }
1565     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1566       initialMode = TwoMachinesPlay;
1567     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1568       initialMode = AnalyzeFile;
1569     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1570       initialMode = AnalyzeMode;
1571     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1572       initialMode = MachinePlaysWhite;
1573     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1574       initialMode = MachinePlaysBlack;
1575     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1576       initialMode = EditGame;
1577     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1578       initialMode = EditPosition;
1579     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1580       initialMode = Training;
1581     } else {
1582       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1583       if( (len >= MSG_SIZ) && appData.debugMode )
1584         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1585
1586       DisplayFatalError(buf, 0, 2);
1587       return;
1588     }
1589
1590     if (appData.matchMode) {
1591         if(appData.tourneyFile[0]) { // start tourney from command line
1592             FILE *f;
1593             if(f = fopen(appData.tourneyFile, "r")) {
1594                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1595                 fclose(f);
1596                 appData.clockMode = TRUE;
1597                 SetGNUMode();
1598             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1599         }
1600         MatchEvent(TRUE);
1601     } else if (*appData.cmailGameName != NULLCHAR) {
1602         /* Set up cmail mode */
1603         ReloadCmailMsgEvent(TRUE);
1604     } else {
1605         /* Set up other modes */
1606         if (initialMode == AnalyzeFile) {
1607           if (*appData.loadGameFile == NULLCHAR) {
1608             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1609             return;
1610           }
1611         }
1612         if (*appData.loadGameFile != NULLCHAR) {
1613             (void) LoadGameFromFile(appData.loadGameFile,
1614                                     appData.loadGameIndex,
1615                                     appData.loadGameFile, TRUE);
1616         } else if (*appData.loadPositionFile != NULLCHAR) {
1617             (void) LoadPositionFromFile(appData.loadPositionFile,
1618                                         appData.loadPositionIndex,
1619                                         appData.loadPositionFile);
1620             /* [HGM] try to make self-starting even after FEN load */
1621             /* to allow automatic setup of fairy variants with wtm */
1622             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1623                 gameMode = BeginningOfGame;
1624                 setboardSpoiledMachineBlack = 1;
1625             }
1626             /* [HGM] loadPos: make that every new game uses the setup */
1627             /* from file as long as we do not switch variant          */
1628             if(!blackPlaysFirst) {
1629                 startedFromPositionFile = TRUE;
1630                 CopyBoard(filePosition, boards[0]);
1631             }
1632         }
1633         if (initialMode == AnalyzeMode) {
1634           if (appData.noChessProgram) {
1635             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1636             return;
1637           }
1638           if (appData.icsActive) {
1639             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1640             return;
1641           }
1642           AnalyzeModeEvent();
1643         } else if (initialMode == AnalyzeFile) {
1644           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1645           ShowThinkingEvent();
1646           AnalyzeFileEvent();
1647           AnalysisPeriodicEvent(1);
1648         } else if (initialMode == MachinePlaysWhite) {
1649           if (appData.noChessProgram) {
1650             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1651                               0, 2);
1652             return;
1653           }
1654           if (appData.icsActive) {
1655             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1656                               0, 2);
1657             return;
1658           }
1659           MachineWhiteEvent();
1660         } else if (initialMode == MachinePlaysBlack) {
1661           if (appData.noChessProgram) {
1662             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1663                               0, 2);
1664             return;
1665           }
1666           if (appData.icsActive) {
1667             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1668                               0, 2);
1669             return;
1670           }
1671           MachineBlackEvent();
1672         } else if (initialMode == TwoMachinesPlay) {
1673           if (appData.noChessProgram) {
1674             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1675                               0, 2);
1676             return;
1677           }
1678           if (appData.icsActive) {
1679             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1680                               0, 2);
1681             return;
1682           }
1683           TwoMachinesEvent();
1684         } else if (initialMode == EditGame) {
1685           EditGameEvent();
1686         } else if (initialMode == EditPosition) {
1687           EditPositionEvent();
1688         } else if (initialMode == Training) {
1689           if (*appData.loadGameFile == NULLCHAR) {
1690             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1691             return;
1692           }
1693           TrainingEvent();
1694         }
1695     }
1696 }
1697
1698 void
1699 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1700 {
1701     DisplayBook(current+1);
1702
1703     MoveHistorySet( movelist, first, last, current, pvInfoList );
1704
1705     EvalGraphSet( first, last, current, pvInfoList );
1706
1707     MakeEngineOutputTitle();
1708 }
1709
1710 /*
1711  * Establish will establish a contact to a remote host.port.
1712  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1713  *  used to talk to the host.
1714  * Returns 0 if okay, error code if not.
1715  */
1716 int
1717 establish ()
1718 {
1719     char buf[MSG_SIZ];
1720
1721     if (*appData.icsCommPort != NULLCHAR) {
1722         /* Talk to the host through a serial comm port */
1723         return OpenCommPort(appData.icsCommPort, &icsPR);
1724
1725     } else if (*appData.gateway != NULLCHAR) {
1726         if (*appData.remoteShell == NULLCHAR) {
1727             /* Use the rcmd protocol to run telnet program on a gateway host */
1728             snprintf(buf, sizeof(buf), "%s %s %s",
1729                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1730             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1731
1732         } else {
1733             /* Use the rsh program to run telnet program on a gateway host */
1734             if (*appData.remoteUser == NULLCHAR) {
1735                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1736                         appData.gateway, appData.telnetProgram,
1737                         appData.icsHost, appData.icsPort);
1738             } else {
1739                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1740                         appData.remoteShell, appData.gateway,
1741                         appData.remoteUser, appData.telnetProgram,
1742                         appData.icsHost, appData.icsPort);
1743             }
1744             return StartChildProcess(buf, "", &icsPR);
1745
1746         }
1747     } else if (appData.useTelnet) {
1748         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1749
1750     } else {
1751         /* TCP socket interface differs somewhat between
1752            Unix and NT; handle details in the front end.
1753            */
1754         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1755     }
1756 }
1757
1758 void
1759 EscapeExpand (char *p, char *q)
1760 {       // [HGM] initstring: routine to shape up string arguments
1761         while(*p++ = *q++) if(p[-1] == '\\')
1762             switch(*q++) {
1763                 case 'n': p[-1] = '\n'; break;
1764                 case 'r': p[-1] = '\r'; break;
1765                 case 't': p[-1] = '\t'; break;
1766                 case '\\': p[-1] = '\\'; break;
1767                 case 0: *p = 0; return;
1768                 default: p[-1] = q[-1]; break;
1769             }
1770 }
1771
1772 void
1773 show_bytes (FILE *fp, char *buf, int count)
1774 {
1775     while (count--) {
1776         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1777             fprintf(fp, "\\%03o", *buf & 0xff);
1778         } else {
1779             putc(*buf, fp);
1780         }
1781         buf++;
1782     }
1783     fflush(fp);
1784 }
1785
1786 /* Returns an errno value */
1787 int
1788 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1789 {
1790     char buf[8192], *p, *q, *buflim;
1791     int left, newcount, outcount;
1792
1793     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1794         *appData.gateway != NULLCHAR) {
1795         if (appData.debugMode) {
1796             fprintf(debugFP, ">ICS: ");
1797             show_bytes(debugFP, message, count);
1798             fprintf(debugFP, "\n");
1799         }
1800         return OutputToProcess(pr, message, count, outError);
1801     }
1802
1803     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1804     p = message;
1805     q = buf;
1806     left = count;
1807     newcount = 0;
1808     while (left) {
1809         if (q >= buflim) {
1810             if (appData.debugMode) {
1811                 fprintf(debugFP, ">ICS: ");
1812                 show_bytes(debugFP, buf, newcount);
1813                 fprintf(debugFP, "\n");
1814             }
1815             outcount = OutputToProcess(pr, buf, newcount, outError);
1816             if (outcount < newcount) return -1; /* to be sure */
1817             q = buf;
1818             newcount = 0;
1819         }
1820         if (*p == '\n') {
1821             *q++ = '\r';
1822             newcount++;
1823         } else if (((unsigned char) *p) == TN_IAC) {
1824             *q++ = (char) TN_IAC;
1825             newcount ++;
1826         }
1827         *q++ = *p++;
1828         newcount++;
1829         left--;
1830     }
1831     if (appData.debugMode) {
1832         fprintf(debugFP, ">ICS: ");
1833         show_bytes(debugFP, buf, newcount);
1834         fprintf(debugFP, "\n");
1835     }
1836     outcount = OutputToProcess(pr, buf, newcount, outError);
1837     if (outcount < newcount) return -1; /* to be sure */
1838     return count;
1839 }
1840
1841 void
1842 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1843 {
1844     int outError, outCount;
1845     static int gotEof = 0;
1846
1847     /* Pass data read from player on to ICS */
1848     if (count > 0) {
1849         gotEof = 0;
1850         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1851         if (outCount < count) {
1852             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1853         }
1854     } else if (count < 0) {
1855         RemoveInputSource(isr);
1856         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1857     } else if (gotEof++ > 0) {
1858         RemoveInputSource(isr);
1859         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1860     }
1861 }
1862
1863 void
1864 KeepAlive ()
1865 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1866     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1867     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1868     SendToICS("date\n");
1869     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1870 }
1871
1872 /* added routine for printf style output to ics */
1873 void
1874 ics_printf (char *format, ...)
1875 {
1876     char buffer[MSG_SIZ];
1877     va_list args;
1878
1879     va_start(args, format);
1880     vsnprintf(buffer, sizeof(buffer), format, args);
1881     buffer[sizeof(buffer)-1] = '\0';
1882     SendToICS(buffer);
1883     va_end(args);
1884 }
1885
1886 void
1887 SendToICS (char *s)
1888 {
1889     int count, outCount, outError;
1890
1891     if (icsPR == NoProc) return;
1892
1893     count = strlen(s);
1894     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1895     if (outCount < count) {
1896         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1897     }
1898 }
1899
1900 /* This is used for sending logon scripts to the ICS. Sending
1901    without a delay causes problems when using timestamp on ICC
1902    (at least on my machine). */
1903 void
1904 SendToICSDelayed (char *s, long msdelay)
1905 {
1906     int count, outCount, outError;
1907
1908     if (icsPR == NoProc) return;
1909
1910     count = strlen(s);
1911     if (appData.debugMode) {
1912         fprintf(debugFP, ">ICS: ");
1913         show_bytes(debugFP, s, count);
1914         fprintf(debugFP, "\n");
1915     }
1916     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1917                                       msdelay);
1918     if (outCount < count) {
1919         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1920     }
1921 }
1922
1923
1924 /* Remove all highlighting escape sequences in s
1925    Also deletes any suffix starting with '('
1926    */
1927 char *
1928 StripHighlightAndTitle (char *s)
1929 {
1930     static char retbuf[MSG_SIZ];
1931     char *p = retbuf;
1932
1933     while (*s != NULLCHAR) {
1934         while (*s == '\033') {
1935             while (*s != NULLCHAR && !isalpha(*s)) s++;
1936             if (*s != NULLCHAR) s++;
1937         }
1938         while (*s != NULLCHAR && *s != '\033') {
1939             if (*s == '(' || *s == '[') {
1940                 *p = NULLCHAR;
1941                 return retbuf;
1942             }
1943             *p++ = *s++;
1944         }
1945     }
1946     *p = NULLCHAR;
1947     return retbuf;
1948 }
1949
1950 /* Remove all highlighting escape sequences in s */
1951 char *
1952 StripHighlight (char *s)
1953 {
1954     static char retbuf[MSG_SIZ];
1955     char *p = retbuf;
1956
1957     while (*s != NULLCHAR) {
1958         while (*s == '\033') {
1959             while (*s != NULLCHAR && !isalpha(*s)) s++;
1960             if (*s != NULLCHAR) s++;
1961         }
1962         while (*s != NULLCHAR && *s != '\033') {
1963             *p++ = *s++;
1964         }
1965     }
1966     *p = NULLCHAR;
1967     return retbuf;
1968 }
1969
1970 char *variantNames[] = VARIANT_NAMES;
1971 char *
1972 VariantName (VariantClass v)
1973 {
1974     return variantNames[v];
1975 }
1976
1977
1978 /* Identify a variant from the strings the chess servers use or the
1979    PGN Variant tag names we use. */
1980 VariantClass
1981 StringToVariant (char *e)
1982 {
1983     char *p;
1984     int wnum = -1;
1985     VariantClass v = VariantNormal;
1986     int i, found = FALSE;
1987     char buf[MSG_SIZ];
1988     int len;
1989
1990     if (!e) return v;
1991
1992     /* [HGM] skip over optional board-size prefixes */
1993     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1994         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1995         while( *e++ != '_');
1996     }
1997
1998     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1999         v = VariantNormal;
2000         found = TRUE;
2001     } else
2002     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2003       if (StrCaseStr(e, variantNames[i])) {
2004         v = (VariantClass) i;
2005         found = TRUE;
2006         break;
2007       }
2008     }
2009
2010     if (!found) {
2011       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2012           || StrCaseStr(e, "wild/fr")
2013           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2014         v = VariantFischeRandom;
2015       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2016                  (i = 1, p = StrCaseStr(e, "w"))) {
2017         p += i;
2018         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2019         if (isdigit(*p)) {
2020           wnum = atoi(p);
2021         } else {
2022           wnum = -1;
2023         }
2024         switch (wnum) {
2025         case 0: /* FICS only, actually */
2026         case 1:
2027           /* Castling legal even if K starts on d-file */
2028           v = VariantWildCastle;
2029           break;
2030         case 2:
2031         case 3:
2032         case 4:
2033           /* Castling illegal even if K & R happen to start in
2034              normal positions. */
2035           v = VariantNoCastle;
2036           break;
2037         case 5:
2038         case 7:
2039         case 8:
2040         case 10:
2041         case 11:
2042         case 12:
2043         case 13:
2044         case 14:
2045         case 15:
2046         case 18:
2047         case 19:
2048           /* Castling legal iff K & R start in normal positions */
2049           v = VariantNormal;
2050           break;
2051         case 6:
2052         case 20:
2053         case 21:
2054           /* Special wilds for position setup; unclear what to do here */
2055           v = VariantLoadable;
2056           break;
2057         case 9:
2058           /* Bizarre ICC game */
2059           v = VariantTwoKings;
2060           break;
2061         case 16:
2062           v = VariantKriegspiel;
2063           break;
2064         case 17:
2065           v = VariantLosers;
2066           break;
2067         case 22:
2068           v = VariantFischeRandom;
2069           break;
2070         case 23:
2071           v = VariantCrazyhouse;
2072           break;
2073         case 24:
2074           v = VariantBughouse;
2075           break;
2076         case 25:
2077           v = Variant3Check;
2078           break;
2079         case 26:
2080           /* Not quite the same as FICS suicide! */
2081           v = VariantGiveaway;
2082           break;
2083         case 27:
2084           v = VariantAtomic;
2085           break;
2086         case 28:
2087           v = VariantShatranj;
2088           break;
2089
2090         /* Temporary names for future ICC types.  The name *will* change in
2091            the next xboard/WinBoard release after ICC defines it. */
2092         case 29:
2093           v = Variant29;
2094           break;
2095         case 30:
2096           v = Variant30;
2097           break;
2098         case 31:
2099           v = Variant31;
2100           break;
2101         case 32:
2102           v = Variant32;
2103           break;
2104         case 33:
2105           v = Variant33;
2106           break;
2107         case 34:
2108           v = Variant34;
2109           break;
2110         case 35:
2111           v = Variant35;
2112           break;
2113         case 36:
2114           v = Variant36;
2115           break;
2116         case 37:
2117           v = VariantShogi;
2118           break;
2119         case 38:
2120           v = VariantXiangqi;
2121           break;
2122         case 39:
2123           v = VariantCourier;
2124           break;
2125         case 40:
2126           v = VariantGothic;
2127           break;
2128         case 41:
2129           v = VariantCapablanca;
2130           break;
2131         case 42:
2132           v = VariantKnightmate;
2133           break;
2134         case 43:
2135           v = VariantFairy;
2136           break;
2137         case 44:
2138           v = VariantCylinder;
2139           break;
2140         case 45:
2141           v = VariantFalcon;
2142           break;
2143         case 46:
2144           v = VariantCapaRandom;
2145           break;
2146         case 47:
2147           v = VariantBerolina;
2148           break;
2149         case 48:
2150           v = VariantJanus;
2151           break;
2152         case 49:
2153           v = VariantSuper;
2154           break;
2155         case 50:
2156           v = VariantGreat;
2157           break;
2158         case -1:
2159           /* Found "wild" or "w" in the string but no number;
2160              must assume it's normal chess. */
2161           v = VariantNormal;
2162           break;
2163         default:
2164           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2165           if( (len >= MSG_SIZ) && appData.debugMode )
2166             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2167
2168           DisplayError(buf, 0);
2169           v = VariantUnknown;
2170           break;
2171         }
2172       }
2173     }
2174     if (appData.debugMode) {
2175       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2176               e, wnum, VariantName(v));
2177     }
2178     return v;
2179 }
2180
2181 static int leftover_start = 0, leftover_len = 0;
2182 char star_match[STAR_MATCH_N][MSG_SIZ];
2183
2184 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2185    advance *index beyond it, and set leftover_start to the new value of
2186    *index; else return FALSE.  If pattern contains the character '*', it
2187    matches any sequence of characters not containing '\r', '\n', or the
2188    character following the '*' (if any), and the matched sequence(s) are
2189    copied into star_match.
2190    */
2191 int
2192 looking_at ( char *buf, int *index, char *pattern)
2193 {
2194     char *bufp = &buf[*index], *patternp = pattern;
2195     int star_count = 0;
2196     char *matchp = star_match[0];
2197
2198     for (;;) {
2199         if (*patternp == NULLCHAR) {
2200             *index = leftover_start = bufp - buf;
2201             *matchp = NULLCHAR;
2202             return TRUE;
2203         }
2204         if (*bufp == NULLCHAR) return FALSE;
2205         if (*patternp == '*') {
2206             if (*bufp == *(patternp + 1)) {
2207                 *matchp = NULLCHAR;
2208                 matchp = star_match[++star_count];
2209                 patternp += 2;
2210                 bufp++;
2211                 continue;
2212             } else if (*bufp == '\n' || *bufp == '\r') {
2213                 patternp++;
2214                 if (*patternp == NULLCHAR)
2215                   continue;
2216                 else
2217                   return FALSE;
2218             } else {
2219                 *matchp++ = *bufp++;
2220                 continue;
2221             }
2222         }
2223         if (*patternp != *bufp) return FALSE;
2224         patternp++;
2225         bufp++;
2226     }
2227 }
2228
2229 void
2230 SendToPlayer (char *data, int length)
2231 {
2232     int error, outCount;
2233     outCount = OutputToProcess(NoProc, data, length, &error);
2234     if (outCount < length) {
2235         DisplayFatalError(_("Error writing to display"), error, 1);
2236     }
2237 }
2238
2239 void
2240 PackHolding (char packed[], char *holding)
2241 {
2242     char *p = holding;
2243     char *q = packed;
2244     int runlength = 0;
2245     int curr = 9999;
2246     do {
2247         if (*p == curr) {
2248             runlength++;
2249         } else {
2250             switch (runlength) {
2251               case 0:
2252                 break;
2253               case 1:
2254                 *q++ = curr;
2255                 break;
2256               case 2:
2257                 *q++ = curr;
2258                 *q++ = curr;
2259                 break;
2260               default:
2261                 sprintf(q, "%d", runlength);
2262                 while (*q) q++;
2263                 *q++ = curr;
2264                 break;
2265             }
2266             runlength = 1;
2267             curr = *p;
2268         }
2269     } while (*p++);
2270     *q = NULLCHAR;
2271 }
2272
2273 /* Telnet protocol requests from the front end */
2274 void
2275 TelnetRequest (unsigned char ddww, unsigned char option)
2276 {
2277     unsigned char msg[3];
2278     int outCount, outError;
2279
2280     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2281
2282     if (appData.debugMode) {
2283         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2284         switch (ddww) {
2285           case TN_DO:
2286             ddwwStr = "DO";
2287             break;
2288           case TN_DONT:
2289             ddwwStr = "DONT";
2290             break;
2291           case TN_WILL:
2292             ddwwStr = "WILL";
2293             break;
2294           case TN_WONT:
2295             ddwwStr = "WONT";
2296             break;
2297           default:
2298             ddwwStr = buf1;
2299             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2300             break;
2301         }
2302         switch (option) {
2303           case TN_ECHO:
2304             optionStr = "ECHO";
2305             break;
2306           default:
2307             optionStr = buf2;
2308             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2309             break;
2310         }
2311         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2312     }
2313     msg[0] = TN_IAC;
2314     msg[1] = ddww;
2315     msg[2] = option;
2316     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2317     if (outCount < 3) {
2318         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2319     }
2320 }
2321
2322 void
2323 DoEcho ()
2324 {
2325     if (!appData.icsActive) return;
2326     TelnetRequest(TN_DO, TN_ECHO);
2327 }
2328
2329 void
2330 DontEcho ()
2331 {
2332     if (!appData.icsActive) return;
2333     TelnetRequest(TN_DONT, TN_ECHO);
2334 }
2335
2336 void
2337 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2338 {
2339     /* put the holdings sent to us by the server on the board holdings area */
2340     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2341     char p;
2342     ChessSquare piece;
2343
2344     if(gameInfo.holdingsWidth < 2)  return;
2345     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2346         return; // prevent overwriting by pre-board holdings
2347
2348     if( (int)lowestPiece >= BlackPawn ) {
2349         holdingsColumn = 0;
2350         countsColumn = 1;
2351         holdingsStartRow = BOARD_HEIGHT-1;
2352         direction = -1;
2353     } else {
2354         holdingsColumn = BOARD_WIDTH-1;
2355         countsColumn = BOARD_WIDTH-2;
2356         holdingsStartRow = 0;
2357         direction = 1;
2358     }
2359
2360     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2361         board[i][holdingsColumn] = EmptySquare;
2362         board[i][countsColumn]   = (ChessSquare) 0;
2363     }
2364     while( (p=*holdings++) != NULLCHAR ) {
2365         piece = CharToPiece( ToUpper(p) );
2366         if(piece == EmptySquare) continue;
2367         /*j = (int) piece - (int) WhitePawn;*/
2368         j = PieceToNumber(piece);
2369         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2370         if(j < 0) continue;               /* should not happen */
2371         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2372         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2373         board[holdingsStartRow+j*direction][countsColumn]++;
2374     }
2375 }
2376
2377
2378 void
2379 VariantSwitch (Board board, VariantClass newVariant)
2380 {
2381    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2382    static Board oldBoard;
2383
2384    startedFromPositionFile = FALSE;
2385    if(gameInfo.variant == newVariant) return;
2386
2387    /* [HGM] This routine is called each time an assignment is made to
2388     * gameInfo.variant during a game, to make sure the board sizes
2389     * are set to match the new variant. If that means adding or deleting
2390     * holdings, we shift the playing board accordingly
2391     * This kludge is needed because in ICS observe mode, we get boards
2392     * of an ongoing game without knowing the variant, and learn about the
2393     * latter only later. This can be because of the move list we requested,
2394     * in which case the game history is refilled from the beginning anyway,
2395     * but also when receiving holdings of a crazyhouse game. In the latter
2396     * case we want to add those holdings to the already received position.
2397     */
2398
2399
2400    if (appData.debugMode) {
2401      fprintf(debugFP, "Switch board from %s to %s\n",
2402              VariantName(gameInfo.variant), VariantName(newVariant));
2403      setbuf(debugFP, NULL);
2404    }
2405    shuffleOpenings = 0;       /* [HGM] shuffle */
2406    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2407    switch(newVariant)
2408      {
2409      case VariantShogi:
2410        newWidth = 9;  newHeight = 9;
2411        gameInfo.holdingsSize = 7;
2412      case VariantBughouse:
2413      case VariantCrazyhouse:
2414        newHoldingsWidth = 2; break;
2415      case VariantGreat:
2416        newWidth = 10;
2417      case VariantSuper:
2418        newHoldingsWidth = 2;
2419        gameInfo.holdingsSize = 8;
2420        break;
2421      case VariantGothic:
2422      case VariantCapablanca:
2423      case VariantCapaRandom:
2424        newWidth = 10;
2425      default:
2426        newHoldingsWidth = gameInfo.holdingsSize = 0;
2427      };
2428
2429    if(newWidth  != gameInfo.boardWidth  ||
2430       newHeight != gameInfo.boardHeight ||
2431       newHoldingsWidth != gameInfo.holdingsWidth ) {
2432
2433      /* shift position to new playing area, if needed */
2434      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2435        for(i=0; i<BOARD_HEIGHT; i++)
2436          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2437            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2438              board[i][j];
2439        for(i=0; i<newHeight; i++) {
2440          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2441          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2442        }
2443      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2444        for(i=0; i<BOARD_HEIGHT; i++)
2445          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2446            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2447              board[i][j];
2448      }
2449      board[HOLDINGS_SET] = 0;
2450      gameInfo.boardWidth  = newWidth;
2451      gameInfo.boardHeight = newHeight;
2452      gameInfo.holdingsWidth = newHoldingsWidth;
2453      gameInfo.variant = newVariant;
2454      InitDrawingSizes(-2, 0);
2455    } else gameInfo.variant = newVariant;
2456    CopyBoard(oldBoard, board);   // remember correctly formatted board
2457      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2458    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2459 }
2460
2461 static int loggedOn = FALSE;
2462
2463 /*-- Game start info cache: --*/
2464 int gs_gamenum;
2465 char gs_kind[MSG_SIZ];
2466 static char player1Name[128] = "";
2467 static char player2Name[128] = "";
2468 static char cont_seq[] = "\n\\   ";
2469 static int player1Rating = -1;
2470 static int player2Rating = -1;
2471 /*----------------------------*/
2472
2473 ColorClass curColor = ColorNormal;
2474 int suppressKibitz = 0;
2475
2476 // [HGM] seekgraph
2477 Boolean soughtPending = FALSE;
2478 Boolean seekGraphUp;
2479 #define MAX_SEEK_ADS 200
2480 #define SQUARE 0x80
2481 char *seekAdList[MAX_SEEK_ADS];
2482 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2483 float tcList[MAX_SEEK_ADS];
2484 char colorList[MAX_SEEK_ADS];
2485 int nrOfSeekAds = 0;
2486 int minRating = 1010, maxRating = 2800;
2487 int hMargin = 10, vMargin = 20, h, w;
2488 extern int squareSize, lineGap;
2489
2490 void
2491 PlotSeekAd (int i)
2492 {
2493         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2494         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2495         if(r < minRating+100 && r >=0 ) r = minRating+100;
2496         if(r > maxRating) r = maxRating;
2497         if(tc < 1.) tc = 1.;
2498         if(tc > 95.) tc = 95.;
2499         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2500         y = ((double)r - minRating)/(maxRating - minRating)
2501             * (h-vMargin-squareSize/8-1) + vMargin;
2502         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2503         if(strstr(seekAdList[i], " u ")) color = 1;
2504         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2505            !strstr(seekAdList[i], "bullet") &&
2506            !strstr(seekAdList[i], "blitz") &&
2507            !strstr(seekAdList[i], "standard") ) color = 2;
2508         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2509         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2510 }
2511
2512 void
2513 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2514 {
2515         char buf[MSG_SIZ], *ext = "";
2516         VariantClass v = StringToVariant(type);
2517         if(strstr(type, "wild")) {
2518             ext = type + 4; // append wild number
2519             if(v == VariantFischeRandom) type = "chess960"; else
2520             if(v == VariantLoadable) type = "setup"; else
2521             type = VariantName(v);
2522         }
2523         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2524         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2525             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2526             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2527             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2528             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2529             seekNrList[nrOfSeekAds] = nr;
2530             zList[nrOfSeekAds] = 0;
2531             seekAdList[nrOfSeekAds++] = StrSave(buf);
2532             if(plot) PlotSeekAd(nrOfSeekAds-1);
2533         }
2534 }
2535
2536 void
2537 EraseSeekDot (int i)
2538 {
2539     int x = xList[i], y = yList[i], d=squareSize/4, k;
2540     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2541     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2542     // now replot every dot that overlapped
2543     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2544         int xx = xList[k], yy = yList[k];
2545         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2546             DrawSeekDot(xx, yy, colorList[k]);
2547     }
2548 }
2549
2550 void
2551 RemoveSeekAd (int nr)
2552 {
2553         int i;
2554         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2555             EraseSeekDot(i);
2556             if(seekAdList[i]) free(seekAdList[i]);
2557             seekAdList[i] = seekAdList[--nrOfSeekAds];
2558             seekNrList[i] = seekNrList[nrOfSeekAds];
2559             ratingList[i] = ratingList[nrOfSeekAds];
2560             colorList[i]  = colorList[nrOfSeekAds];
2561             tcList[i] = tcList[nrOfSeekAds];
2562             xList[i]  = xList[nrOfSeekAds];
2563             yList[i]  = yList[nrOfSeekAds];
2564             zList[i]  = zList[nrOfSeekAds];
2565             seekAdList[nrOfSeekAds] = NULL;
2566             break;
2567         }
2568 }
2569
2570 Boolean
2571 MatchSoughtLine (char *line)
2572 {
2573     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2574     int nr, base, inc, u=0; char dummy;
2575
2576     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2577        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2578        (u=1) &&
2579        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2580         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2581         // match: compact and save the line
2582         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2583         return TRUE;
2584     }
2585     return FALSE;
2586 }
2587
2588 int
2589 DrawSeekGraph ()
2590 {
2591     int i;
2592     if(!seekGraphUp) return FALSE;
2593     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2594     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2595
2596     DrawSeekBackground(0, 0, w, h);
2597     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2598     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2599     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2600         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2601         yy = h-1-yy;
2602         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2603         if(i%500 == 0) {
2604             char buf[MSG_SIZ];
2605             snprintf(buf, MSG_SIZ, "%d", i);
2606             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2607         }
2608     }
2609     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2610     for(i=1; i<100; i+=(i<10?1:5)) {
2611         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2612         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2613         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2614             char buf[MSG_SIZ];
2615             snprintf(buf, MSG_SIZ, "%d", i);
2616             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2617         }
2618     }
2619     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2620     return TRUE;
2621 }
2622
2623 int
2624 SeekGraphClick (ClickType click, int x, int y, int moving)
2625 {
2626     static int lastDown = 0, displayed = 0, lastSecond;
2627     if(y < 0) return FALSE;
2628     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2629         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2630         if(!seekGraphUp) return FALSE;
2631         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2632         DrawPosition(TRUE, NULL);
2633         return TRUE;
2634     }
2635     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2636         if(click == Release || moving) return FALSE;
2637         nrOfSeekAds = 0;
2638         soughtPending = TRUE;
2639         SendToICS(ics_prefix);
2640         SendToICS("sought\n"); // should this be "sought all"?
2641     } else { // issue challenge based on clicked ad
2642         int dist = 10000; int i, closest = 0, second = 0;
2643         for(i=0; i<nrOfSeekAds; i++) {
2644             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2645             if(d < dist) { dist = d; closest = i; }
2646             second += (d - zList[i] < 120); // count in-range ads
2647             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2648         }
2649         if(dist < 120) {
2650             char buf[MSG_SIZ];
2651             second = (second > 1);
2652             if(displayed != closest || second != lastSecond) {
2653                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2654                 lastSecond = second; displayed = closest;
2655             }
2656             if(click == Press) {
2657                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2658                 lastDown = closest;
2659                 return TRUE;
2660             } // on press 'hit', only show info
2661             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2662             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2663             SendToICS(ics_prefix);
2664             SendToICS(buf);
2665             return TRUE; // let incoming board of started game pop down the graph
2666         } else if(click == Release) { // release 'miss' is ignored
2667             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2668             if(moving == 2) { // right up-click
2669                 nrOfSeekAds = 0; // refresh graph
2670                 soughtPending = TRUE;
2671                 SendToICS(ics_prefix);
2672                 SendToICS("sought\n"); // should this be "sought all"?
2673             }
2674             return TRUE;
2675         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2676         // press miss or release hit 'pop down' seek graph
2677         seekGraphUp = FALSE;
2678         DrawPosition(TRUE, NULL);
2679     }
2680     return TRUE;
2681 }
2682
2683 void
2684 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2685 {
2686 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2687 #define STARTED_NONE 0
2688 #define STARTED_MOVES 1
2689 #define STARTED_BOARD 2
2690 #define STARTED_OBSERVE 3
2691 #define STARTED_HOLDINGS 4
2692 #define STARTED_CHATTER 5
2693 #define STARTED_COMMENT 6
2694 #define STARTED_MOVES_NOHIDE 7
2695
2696     static int started = STARTED_NONE;
2697     static char parse[20000];
2698     static int parse_pos = 0;
2699     static char buf[BUF_SIZE + 1];
2700     static int firstTime = TRUE, intfSet = FALSE;
2701     static ColorClass prevColor = ColorNormal;
2702     static int savingComment = FALSE;
2703     static int cmatch = 0; // continuation sequence match
2704     char *bp;
2705     char str[MSG_SIZ];
2706     int i, oldi;
2707     int buf_len;
2708     int next_out;
2709     int tkind;
2710     int backup;    /* [DM] For zippy color lines */
2711     char *p;
2712     char talker[MSG_SIZ]; // [HGM] chat
2713     int channel;
2714
2715     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2716
2717     if (appData.debugMode) {
2718       if (!error) {
2719         fprintf(debugFP, "<ICS: ");
2720         show_bytes(debugFP, data, count);
2721         fprintf(debugFP, "\n");
2722       }
2723     }
2724
2725     if (appData.debugMode) { int f = forwardMostMove;
2726         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2727                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2728                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2729     }
2730     if (count > 0) {
2731         /* If last read ended with a partial line that we couldn't parse,
2732            prepend it to the new read and try again. */
2733         if (leftover_len > 0) {
2734             for (i=0; i<leftover_len; i++)
2735               buf[i] = buf[leftover_start + i];
2736         }
2737
2738     /* copy new characters into the buffer */
2739     bp = buf + leftover_len;
2740     buf_len=leftover_len;
2741     for (i=0; i<count; i++)
2742     {
2743         // ignore these
2744         if (data[i] == '\r')
2745             continue;
2746
2747         // join lines split by ICS?
2748         if (!appData.noJoin)
2749         {
2750             /*
2751                 Joining just consists of finding matches against the
2752                 continuation sequence, and discarding that sequence
2753                 if found instead of copying it.  So, until a match
2754                 fails, there's nothing to do since it might be the
2755                 complete sequence, and thus, something we don't want
2756                 copied.
2757             */
2758             if (data[i] == cont_seq[cmatch])
2759             {
2760                 cmatch++;
2761                 if (cmatch == strlen(cont_seq))
2762                 {
2763                     cmatch = 0; // complete match.  just reset the counter
2764
2765                     /*
2766                         it's possible for the ICS to not include the space
2767                         at the end of the last word, making our [correct]
2768                         join operation fuse two separate words.  the server
2769                         does this when the space occurs at the width setting.
2770                     */
2771                     if (!buf_len || buf[buf_len-1] != ' ')
2772                     {
2773                         *bp++ = ' ';
2774                         buf_len++;
2775                     }
2776                 }
2777                 continue;
2778             }
2779             else if (cmatch)
2780             {
2781                 /*
2782                     match failed, so we have to copy what matched before
2783                     falling through and copying this character.  In reality,
2784                     this will only ever be just the newline character, but
2785                     it doesn't hurt to be precise.
2786                 */
2787                 strncpy(bp, cont_seq, cmatch);
2788                 bp += cmatch;
2789                 buf_len += cmatch;
2790                 cmatch = 0;
2791             }
2792         }
2793
2794         // copy this char
2795         *bp++ = data[i];
2796         buf_len++;
2797     }
2798
2799         buf[buf_len] = NULLCHAR;
2800 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2801         next_out = 0;
2802         leftover_start = 0;
2803
2804         i = 0;
2805         while (i < buf_len) {
2806             /* Deal with part of the TELNET option negotiation
2807                protocol.  We refuse to do anything beyond the
2808                defaults, except that we allow the WILL ECHO option,
2809                which ICS uses to turn off password echoing when we are
2810                directly connected to it.  We reject this option
2811                if localLineEditing mode is on (always on in xboard)
2812                and we are talking to port 23, which might be a real
2813                telnet server that will try to keep WILL ECHO on permanently.
2814              */
2815             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2816                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2817                 unsigned char option;
2818                 oldi = i;
2819                 switch ((unsigned char) buf[++i]) {
2820                   case TN_WILL:
2821                     if (appData.debugMode)
2822                       fprintf(debugFP, "\n<WILL ");
2823                     switch (option = (unsigned char) buf[++i]) {
2824                       case TN_ECHO:
2825                         if (appData.debugMode)
2826                           fprintf(debugFP, "ECHO ");
2827                         /* Reply only if this is a change, according
2828                            to the protocol rules. */
2829                         if (remoteEchoOption) break;
2830                         if (appData.localLineEditing &&
2831                             atoi(appData.icsPort) == TN_PORT) {
2832                             TelnetRequest(TN_DONT, TN_ECHO);
2833                         } else {
2834                             EchoOff();
2835                             TelnetRequest(TN_DO, TN_ECHO);
2836                             remoteEchoOption = TRUE;
2837                         }
2838                         break;
2839                       default:
2840                         if (appData.debugMode)
2841                           fprintf(debugFP, "%d ", option);
2842                         /* Whatever this is, we don't want it. */
2843                         TelnetRequest(TN_DONT, option);
2844                         break;
2845                     }
2846                     break;
2847                   case TN_WONT:
2848                     if (appData.debugMode)
2849                       fprintf(debugFP, "\n<WONT ");
2850                     switch (option = (unsigned char) buf[++i]) {
2851                       case TN_ECHO:
2852                         if (appData.debugMode)
2853                           fprintf(debugFP, "ECHO ");
2854                         /* Reply only if this is a change, according
2855                            to the protocol rules. */
2856                         if (!remoteEchoOption) break;
2857                         EchoOn();
2858                         TelnetRequest(TN_DONT, TN_ECHO);
2859                         remoteEchoOption = FALSE;
2860                         break;
2861                       default:
2862                         if (appData.debugMode)
2863                           fprintf(debugFP, "%d ", (unsigned char) option);
2864                         /* Whatever this is, it must already be turned
2865                            off, because we never agree to turn on
2866                            anything non-default, so according to the
2867                            protocol rules, we don't reply. */
2868                         break;
2869                     }
2870                     break;
2871                   case TN_DO:
2872                     if (appData.debugMode)
2873                       fprintf(debugFP, "\n<DO ");
2874                     switch (option = (unsigned char) buf[++i]) {
2875                       default:
2876                         /* Whatever this is, we refuse to do it. */
2877                         if (appData.debugMode)
2878                           fprintf(debugFP, "%d ", option);
2879                         TelnetRequest(TN_WONT, option);
2880                         break;
2881                     }
2882                     break;
2883                   case TN_DONT:
2884                     if (appData.debugMode)
2885                       fprintf(debugFP, "\n<DONT ");
2886                     switch (option = (unsigned char) buf[++i]) {
2887                       default:
2888                         if (appData.debugMode)
2889                           fprintf(debugFP, "%d ", option);
2890                         /* Whatever this is, we are already not doing
2891                            it, because we never agree to do anything
2892                            non-default, so according to the protocol
2893                            rules, we don't reply. */
2894                         break;
2895                     }
2896                     break;
2897                   case TN_IAC:
2898                     if (appData.debugMode)
2899                       fprintf(debugFP, "\n<IAC ");
2900                     /* Doubled IAC; pass it through */
2901                     i--;
2902                     break;
2903                   default:
2904                     if (appData.debugMode)
2905                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2906                     /* Drop all other telnet commands on the floor */
2907                     break;
2908                 }
2909                 if (oldi > next_out)
2910                   SendToPlayer(&buf[next_out], oldi - next_out);
2911                 if (++i > next_out)
2912                   next_out = i;
2913                 continue;
2914             }
2915
2916             /* OK, this at least will *usually* work */
2917             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2918                 loggedOn = TRUE;
2919             }
2920
2921             if (loggedOn && !intfSet) {
2922                 if (ics_type == ICS_ICC) {
2923                   snprintf(str, MSG_SIZ,
2924                           "/set-quietly interface %s\n/set-quietly style 12\n",
2925                           programVersion);
2926                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2927                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2928                 } else if (ics_type == ICS_CHESSNET) {
2929                   snprintf(str, MSG_SIZ, "/style 12\n");
2930                 } else {
2931                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2932                   strcat(str, programVersion);
2933                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2934                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2935                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2936 #ifdef WIN32
2937                   strcat(str, "$iset nohighlight 1\n");
2938 #endif
2939                   strcat(str, "$iset lock 1\n$style 12\n");
2940                 }
2941                 SendToICS(str);
2942                 NotifyFrontendLogin();
2943                 intfSet = TRUE;
2944             }
2945
2946             if (started == STARTED_COMMENT) {
2947                 /* Accumulate characters in comment */
2948                 parse[parse_pos++] = buf[i];
2949                 if (buf[i] == '\n') {
2950                     parse[parse_pos] = NULLCHAR;
2951                     if(chattingPartner>=0) {
2952                         char mess[MSG_SIZ];
2953                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2954                         OutputChatMessage(chattingPartner, mess);
2955                         chattingPartner = -1;
2956                         next_out = i+1; // [HGM] suppress printing in ICS window
2957                     } else
2958                     if(!suppressKibitz) // [HGM] kibitz
2959                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2960                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2961                         int nrDigit = 0, nrAlph = 0, j;
2962                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2963                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2964                         parse[parse_pos] = NULLCHAR;
2965                         // try to be smart: if it does not look like search info, it should go to
2966                         // ICS interaction window after all, not to engine-output window.
2967                         for(j=0; j<parse_pos; j++) { // count letters and digits
2968                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2969                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2970                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2971                         }
2972                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2973                             int depth=0; float score;
2974                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2975                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2976                                 pvInfoList[forwardMostMove-1].depth = depth;
2977                                 pvInfoList[forwardMostMove-1].score = 100*score;
2978                             }
2979                             OutputKibitz(suppressKibitz, parse);
2980                         } else {
2981                             char tmp[MSG_SIZ];
2982                             if(gameMode == IcsObserving) // restore original ICS messages
2983                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2984                             else
2985                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2986                             SendToPlayer(tmp, strlen(tmp));
2987                         }
2988                         next_out = i+1; // [HGM] suppress printing in ICS window
2989                     }
2990                     started = STARTED_NONE;
2991                 } else {
2992                     /* Don't match patterns against characters in comment */
2993                     i++;
2994                     continue;
2995                 }
2996             }
2997             if (started == STARTED_CHATTER) {
2998                 if (buf[i] != '\n') {
2999                     /* Don't match patterns against characters in chatter */
3000                     i++;
3001                     continue;
3002                 }
3003                 started = STARTED_NONE;
3004                 if(suppressKibitz) next_out = i+1;
3005             }
3006
3007             /* Kludge to deal with rcmd protocol */
3008             if (firstTime && looking_at(buf, &i, "\001*")) {
3009                 DisplayFatalError(&buf[1], 0, 1);
3010                 continue;
3011             } else {
3012                 firstTime = FALSE;
3013             }
3014
3015             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3016                 ics_type = ICS_ICC;
3017                 ics_prefix = "/";
3018                 if (appData.debugMode)
3019                   fprintf(debugFP, "ics_type %d\n", ics_type);
3020                 continue;
3021             }
3022             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3023                 ics_type = ICS_FICS;
3024                 ics_prefix = "$";
3025                 if (appData.debugMode)
3026                   fprintf(debugFP, "ics_type %d\n", ics_type);
3027                 continue;
3028             }
3029             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3030                 ics_type = ICS_CHESSNET;
3031                 ics_prefix = "/";
3032                 if (appData.debugMode)
3033                   fprintf(debugFP, "ics_type %d\n", ics_type);
3034                 continue;
3035             }
3036
3037             if (!loggedOn &&
3038                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3039                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3040                  looking_at(buf, &i, "will be \"*\""))) {
3041               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3042               continue;
3043             }
3044
3045             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3046               char buf[MSG_SIZ];
3047               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3048               DisplayIcsInteractionTitle(buf);
3049               have_set_title = TRUE;
3050             }
3051
3052             /* skip finger notes */
3053             if (started == STARTED_NONE &&
3054                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3055                  (buf[i] == '1' && buf[i+1] == '0')) &&
3056                 buf[i+2] == ':' && buf[i+3] == ' ') {
3057               started = STARTED_CHATTER;
3058               i += 3;
3059               continue;
3060             }
3061
3062             oldi = i;
3063             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3064             if(appData.seekGraph) {
3065                 if(soughtPending && MatchSoughtLine(buf+i)) {
3066                     i = strstr(buf+i, "rated") - buf;
3067                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3068                     next_out = leftover_start = i;
3069                     started = STARTED_CHATTER;
3070                     suppressKibitz = TRUE;
3071                     continue;
3072                 }
3073                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3074                         && looking_at(buf, &i, "* ads displayed")) {
3075                     soughtPending = FALSE;
3076                     seekGraphUp = TRUE;
3077                     DrawSeekGraph();
3078                     continue;
3079                 }
3080                 if(appData.autoRefresh) {
3081                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3082                         int s = (ics_type == ICS_ICC); // ICC format differs
3083                         if(seekGraphUp)
3084                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3085                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3086                         looking_at(buf, &i, "*% "); // eat prompt
3087                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3088                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3089                         next_out = i; // suppress
3090                         continue;
3091                     }
3092                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3093                         char *p = star_match[0];
3094                         while(*p) {
3095                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3096                             while(*p && *p++ != ' '); // next
3097                         }
3098                         looking_at(buf, &i, "*% "); // eat prompt
3099                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3100                         next_out = i;
3101                         continue;
3102                     }
3103                 }
3104             }
3105
3106             /* skip formula vars */
3107             if (started == STARTED_NONE &&
3108                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3109               started = STARTED_CHATTER;
3110               i += 3;
3111               continue;
3112             }
3113
3114             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3115             if (appData.autoKibitz && started == STARTED_NONE &&
3116                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3117                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3118                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3119                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3120                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3121                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3122                         suppressKibitz = TRUE;
3123                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3124                         next_out = i;
3125                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3126                                 && (gameMode == IcsPlayingWhite)) ||
3127                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3128                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3129                             started = STARTED_CHATTER; // own kibitz we simply discard
3130                         else {
3131                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3132                             parse_pos = 0; parse[0] = NULLCHAR;
3133                             savingComment = TRUE;
3134                             suppressKibitz = gameMode != IcsObserving ? 2 :
3135                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3136                         }
3137                         continue;
3138                 } else
3139                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3140                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3141                          && atoi(star_match[0])) {
3142                     // suppress the acknowledgements of our own autoKibitz
3143                     char *p;
3144                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3145                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3146                     SendToPlayer(star_match[0], strlen(star_match[0]));
3147                     if(looking_at(buf, &i, "*% ")) // eat prompt
3148                         suppressKibitz = FALSE;
3149                     next_out = i;
3150                     continue;
3151                 }
3152             } // [HGM] kibitz: end of patch
3153
3154             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3155
3156             // [HGM] chat: intercept tells by users for which we have an open chat window
3157             channel = -1;
3158             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3159                                            looking_at(buf, &i, "* whispers:") ||
3160                                            looking_at(buf, &i, "* kibitzes:") ||
3161                                            looking_at(buf, &i, "* shouts:") ||
3162                                            looking_at(buf, &i, "* c-shouts:") ||
3163                                            looking_at(buf, &i, "--> * ") ||
3164                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3165                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3166                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3167                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3168                 int p;
3169                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3170                 chattingPartner = -1;
3171
3172                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3173                 for(p=0; p<MAX_CHAT; p++) {
3174                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3175                     talker[0] = '['; strcat(talker, "] ");
3176                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3177                     chattingPartner = p; break;
3178                     }
3179                 } else
3180                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3181                 for(p=0; p<MAX_CHAT; p++) {
3182                     if(!strcmp("kibitzes", chatPartner[p])) {
3183                         talker[0] = '['; strcat(talker, "] ");
3184                         chattingPartner = p; break;
3185                     }
3186                 } else
3187                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3188                 for(p=0; p<MAX_CHAT; p++) {
3189                     if(!strcmp("whispers", chatPartner[p])) {
3190                         talker[0] = '['; strcat(talker, "] ");
3191                         chattingPartner = p; break;
3192                     }
3193                 } else
3194                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3195                   if(buf[i-8] == '-' && buf[i-3] == 't')
3196                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3197                     if(!strcmp("c-shouts", chatPartner[p])) {
3198                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3199                         chattingPartner = p; break;
3200                     }
3201                   }
3202                   if(chattingPartner < 0)
3203                   for(p=0; p<MAX_CHAT; p++) {
3204                     if(!strcmp("shouts", chatPartner[p])) {
3205                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3206                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3207                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3208                         chattingPartner = p; break;
3209                     }
3210                   }
3211                 }
3212                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3213                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3214                     talker[0] = 0; Colorize(ColorTell, FALSE);
3215                     chattingPartner = p; break;
3216                 }
3217                 if(chattingPartner<0) i = oldi; else {
3218                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3219                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3220                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3221                     started = STARTED_COMMENT;
3222                     parse_pos = 0; parse[0] = NULLCHAR;
3223                     savingComment = 3 + chattingPartner; // counts as TRUE
3224                     suppressKibitz = TRUE;
3225                     continue;
3226                 }
3227             } // [HGM] chat: end of patch
3228
3229           backup = i;
3230             if (appData.zippyTalk || appData.zippyPlay) {
3231                 /* [DM] Backup address for color zippy lines */
3232 #if ZIPPY
3233                if (loggedOn == TRUE)
3234                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3235                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3236 #endif
3237             } // [DM] 'else { ' deleted
3238                 if (
3239                     /* Regular tells and says */
3240                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3241                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3242                     looking_at(buf, &i, "* says: ") ||
3243                     /* Don't color "message" or "messages" output */
3244                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3245                     looking_at(buf, &i, "*. * at *:*: ") ||
3246                     looking_at(buf, &i, "--* (*:*): ") ||
3247                     /* Message notifications (same color as tells) */
3248                     looking_at(buf, &i, "* has left a message ") ||
3249                     looking_at(buf, &i, "* just sent you a message:\n") ||
3250                     /* Whispers and kibitzes */
3251                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3252                     looking_at(buf, &i, "* kibitzes: ") ||
3253                     /* Channel tells */
3254                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3255
3256                   if (tkind == 1 && strchr(star_match[0], ':')) {
3257                       /* Avoid "tells you:" spoofs in channels */
3258                      tkind = 3;
3259                   }
3260                   if (star_match[0][0] == NULLCHAR ||
3261                       strchr(star_match[0], ' ') ||
3262                       (tkind == 3 && strchr(star_match[1], ' '))) {
3263                     /* Reject bogus matches */
3264                     i = oldi;
3265                   } else {
3266                     if (appData.colorize) {
3267                       if (oldi > next_out) {
3268                         SendToPlayer(&buf[next_out], oldi - next_out);
3269                         next_out = oldi;
3270                       }
3271                       switch (tkind) {
3272                       case 1:
3273                         Colorize(ColorTell, FALSE);
3274                         curColor = ColorTell;
3275                         break;
3276                       case 2:
3277                         Colorize(ColorKibitz, FALSE);
3278                         curColor = ColorKibitz;
3279                         break;
3280                       case 3:
3281                         p = strrchr(star_match[1], '(');
3282                         if (p == NULL) {
3283                           p = star_match[1];
3284                         } else {
3285                           p++;
3286                         }
3287                         if (atoi(p) == 1) {
3288                           Colorize(ColorChannel1, FALSE);
3289                           curColor = ColorChannel1;
3290                         } else {
3291                           Colorize(ColorChannel, FALSE);
3292                           curColor = ColorChannel;
3293                         }
3294                         break;
3295                       case 5:
3296                         curColor = ColorNormal;
3297                         break;
3298                       }
3299                     }
3300                     if (started == STARTED_NONE && appData.autoComment &&
3301                         (gameMode == IcsObserving ||
3302                          gameMode == IcsPlayingWhite ||
3303                          gameMode == IcsPlayingBlack)) {
3304                       parse_pos = i - oldi;
3305                       memcpy(parse, &buf[oldi], parse_pos);
3306                       parse[parse_pos] = NULLCHAR;
3307                       started = STARTED_COMMENT;
3308                       savingComment = TRUE;
3309                     } else {
3310                       started = STARTED_CHATTER;
3311                       savingComment = FALSE;
3312                     }
3313                     loggedOn = TRUE;
3314                     continue;
3315                   }
3316                 }
3317
3318                 if (looking_at(buf, &i, "* s-shouts: ") ||
3319                     looking_at(buf, &i, "* c-shouts: ")) {
3320                     if (appData.colorize) {
3321                         if (oldi > next_out) {
3322                             SendToPlayer(&buf[next_out], oldi - next_out);
3323                             next_out = oldi;
3324                         }
3325                         Colorize(ColorSShout, FALSE);
3326                         curColor = ColorSShout;
3327                     }
3328                     loggedOn = TRUE;
3329                     started = STARTED_CHATTER;
3330                     continue;
3331                 }
3332
3333                 if (looking_at(buf, &i, "--->")) {
3334                     loggedOn = TRUE;
3335                     continue;
3336                 }
3337
3338                 if (looking_at(buf, &i, "* shouts: ") ||
3339                     looking_at(buf, &i, "--> ")) {
3340                     if (appData.colorize) {
3341                         if (oldi > next_out) {
3342                             SendToPlayer(&buf[next_out], oldi - next_out);
3343                             next_out = oldi;
3344                         }
3345                         Colorize(ColorShout, FALSE);
3346                         curColor = ColorShout;
3347                     }
3348                     loggedOn = TRUE;
3349                     started = STARTED_CHATTER;
3350                     continue;
3351                 }
3352
3353                 if (looking_at( buf, &i, "Challenge:")) {
3354                     if (appData.colorize) {
3355                         if (oldi > next_out) {
3356                             SendToPlayer(&buf[next_out], oldi - next_out);
3357                             next_out = oldi;
3358                         }
3359                         Colorize(ColorChallenge, FALSE);
3360                         curColor = ColorChallenge;
3361                     }
3362                     loggedOn = TRUE;
3363                     continue;
3364                 }
3365
3366                 if (looking_at(buf, &i, "* offers you") ||
3367                     looking_at(buf, &i, "* offers to be") ||
3368                     looking_at(buf, &i, "* would like to") ||
3369                     looking_at(buf, &i, "* requests to") ||
3370                     looking_at(buf, &i, "Your opponent offers") ||
3371                     looking_at(buf, &i, "Your opponent requests")) {
3372
3373                     if (appData.colorize) {
3374                         if (oldi > next_out) {
3375                             SendToPlayer(&buf[next_out], oldi - next_out);
3376                             next_out = oldi;
3377                         }
3378                         Colorize(ColorRequest, FALSE);
3379                         curColor = ColorRequest;
3380                     }
3381                     continue;
3382                 }
3383
3384                 if (looking_at(buf, &i, "* (*) seeking")) {
3385                     if (appData.colorize) {
3386                         if (oldi > next_out) {
3387                             SendToPlayer(&buf[next_out], oldi - next_out);
3388                             next_out = oldi;
3389                         }
3390                         Colorize(ColorSeek, FALSE);
3391                         curColor = ColorSeek;
3392                     }
3393                     continue;
3394             }
3395
3396           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3397
3398             if (looking_at(buf, &i, "\\   ")) {
3399                 if (prevColor != ColorNormal) {
3400                     if (oldi > next_out) {
3401                         SendToPlayer(&buf[next_out], oldi - next_out);
3402                         next_out = oldi;
3403                     }
3404                     Colorize(prevColor, TRUE);
3405                     curColor = prevColor;
3406                 }
3407                 if (savingComment) {
3408                     parse_pos = i - oldi;
3409                     memcpy(parse, &buf[oldi], parse_pos);
3410                     parse[parse_pos] = NULLCHAR;
3411                     started = STARTED_COMMENT;
3412                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3413                         chattingPartner = savingComment - 3; // kludge to remember the box
3414                 } else {
3415                     started = STARTED_CHATTER;
3416                 }
3417                 continue;
3418             }
3419
3420             if (looking_at(buf, &i, "Black Strength :") ||
3421                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3422                 looking_at(buf, &i, "<10>") ||
3423                 looking_at(buf, &i, "#@#")) {
3424                 /* Wrong board style */
3425                 loggedOn = TRUE;
3426                 SendToICS(ics_prefix);
3427                 SendToICS("set style 12\n");
3428                 SendToICS(ics_prefix);
3429                 SendToICS("refresh\n");
3430                 continue;
3431             }
3432
3433             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3434                 ICSInitScript();
3435                 have_sent_ICS_logon = 1;
3436                 continue;
3437             }
3438
3439             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3440                 (looking_at(buf, &i, "\n<12> ") ||
3441                  looking_at(buf, &i, "<12> "))) {
3442                 loggedOn = TRUE;
3443                 if (oldi > next_out) {
3444                     SendToPlayer(&buf[next_out], oldi - next_out);
3445                 }
3446                 next_out = i;
3447                 started = STARTED_BOARD;
3448                 parse_pos = 0;
3449                 continue;
3450             }
3451
3452             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3453                 looking_at(buf, &i, "<b1> ")) {
3454                 if (oldi > next_out) {
3455                     SendToPlayer(&buf[next_out], oldi - next_out);
3456                 }
3457                 next_out = i;
3458                 started = STARTED_HOLDINGS;
3459                 parse_pos = 0;
3460                 continue;
3461             }
3462
3463             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3464                 loggedOn = TRUE;
3465                 /* Header for a move list -- first line */
3466
3467                 switch (ics_getting_history) {
3468                   case H_FALSE:
3469                     switch (gameMode) {
3470                       case IcsIdle:
3471                       case BeginningOfGame:
3472                         /* User typed "moves" or "oldmoves" while we
3473                            were idle.  Pretend we asked for these
3474                            moves and soak them up so user can step
3475                            through them and/or save them.
3476                            */
3477                         Reset(FALSE, TRUE);
3478                         gameMode = IcsObserving;
3479                         ModeHighlight();
3480                         ics_gamenum = -1;
3481                         ics_getting_history = H_GOT_UNREQ_HEADER;
3482                         break;
3483                       case EditGame: /*?*/
3484                       case EditPosition: /*?*/
3485                         /* Should above feature work in these modes too? */
3486                         /* For now it doesn't */
3487                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3488                         break;
3489                       default:
3490                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3491                         break;
3492                     }
3493                     break;
3494                   case H_REQUESTED:
3495                     /* Is this the right one? */
3496                     if (gameInfo.white && gameInfo.black &&
3497                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3498                         strcmp(gameInfo.black, star_match[2]) == 0) {
3499                         /* All is well */
3500                         ics_getting_history = H_GOT_REQ_HEADER;
3501                     }
3502                     break;
3503                   case H_GOT_REQ_HEADER:
3504                   case H_GOT_UNREQ_HEADER:
3505                   case H_GOT_UNWANTED_HEADER:
3506                   case H_GETTING_MOVES:
3507                     /* Should not happen */
3508                     DisplayError(_("Error gathering move list: two headers"), 0);
3509                     ics_getting_history = H_FALSE;
3510                     break;
3511                 }
3512
3513                 /* Save player ratings into gameInfo if needed */
3514                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3515                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3516                     (gameInfo.whiteRating == -1 ||
3517                      gameInfo.blackRating == -1)) {
3518
3519                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3520                     gameInfo.blackRating = string_to_rating(star_match[3]);
3521                     if (appData.debugMode)
3522                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3523                               gameInfo.whiteRating, gameInfo.blackRating);
3524                 }
3525                 continue;
3526             }
3527
3528             if (looking_at(buf, &i,
3529               "* * match, initial time: * minute*, increment: * second")) {
3530                 /* Header for a move list -- second line */
3531                 /* Initial board will follow if this is a wild game */
3532                 if (gameInfo.event != NULL) free(gameInfo.event);
3533                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3534                 gameInfo.event = StrSave(str);
3535                 /* [HGM] we switched variant. Translate boards if needed. */
3536                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3537                 continue;
3538             }
3539
3540             if (looking_at(buf, &i, "Move  ")) {
3541                 /* Beginning of a move list */
3542                 switch (ics_getting_history) {
3543                   case H_FALSE:
3544                     /* Normally should not happen */
3545                     /* Maybe user hit reset while we were parsing */
3546                     break;
3547                   case H_REQUESTED:
3548                     /* Happens if we are ignoring a move list that is not
3549                      * the one we just requested.  Common if the user
3550                      * tries to observe two games without turning off
3551                      * getMoveList */
3552                     break;
3553                   case H_GETTING_MOVES:
3554                     /* Should not happen */
3555                     DisplayError(_("Error gathering move list: nested"), 0);
3556                     ics_getting_history = H_FALSE;
3557                     break;
3558                   case H_GOT_REQ_HEADER:
3559                     ics_getting_history = H_GETTING_MOVES;
3560                     started = STARTED_MOVES;
3561                     parse_pos = 0;
3562                     if (oldi > next_out) {
3563                         SendToPlayer(&buf[next_out], oldi - next_out);
3564                     }
3565                     break;
3566                   case H_GOT_UNREQ_HEADER:
3567                     ics_getting_history = H_GETTING_MOVES;
3568                     started = STARTED_MOVES_NOHIDE;
3569                     parse_pos = 0;
3570                     break;
3571                   case H_GOT_UNWANTED_HEADER:
3572                     ics_getting_history = H_FALSE;
3573                     break;
3574                 }
3575                 continue;
3576             }
3577
3578             if (looking_at(buf, &i, "% ") ||
3579                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3580                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3581                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3582                     soughtPending = FALSE;
3583                     seekGraphUp = TRUE;
3584                     DrawSeekGraph();
3585                 }
3586                 if(suppressKibitz) next_out = i;
3587                 savingComment = FALSE;
3588                 suppressKibitz = 0;
3589                 switch (started) {
3590                   case STARTED_MOVES:
3591                   case STARTED_MOVES_NOHIDE:
3592                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3593                     parse[parse_pos + i - oldi] = NULLCHAR;
3594                     ParseGameHistory(parse);
3595 #if ZIPPY
3596                     if (appData.zippyPlay && first.initDone) {
3597                         FeedMovesToProgram(&first, forwardMostMove);
3598                         if (gameMode == IcsPlayingWhite) {
3599                             if (WhiteOnMove(forwardMostMove)) {
3600                                 if (first.sendTime) {
3601                                   if (first.useColors) {
3602                                     SendToProgram("black\n", &first);
3603                                   }
3604                                   SendTimeRemaining(&first, TRUE);
3605                                 }
3606                                 if (first.useColors) {
3607                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3608                                 }
3609                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3610                                 first.maybeThinking = TRUE;
3611                             } else {
3612                                 if (first.usePlayother) {
3613                                   if (first.sendTime) {
3614                                     SendTimeRemaining(&first, TRUE);
3615                                   }
3616                                   SendToProgram("playother\n", &first);
3617                                   firstMove = FALSE;
3618                                 } else {
3619                                   firstMove = TRUE;
3620                                 }
3621                             }
3622                         } else if (gameMode == IcsPlayingBlack) {
3623                             if (!WhiteOnMove(forwardMostMove)) {
3624                                 if (first.sendTime) {
3625                                   if (first.useColors) {
3626                                     SendToProgram("white\n", &first);
3627                                   }
3628                                   SendTimeRemaining(&first, FALSE);
3629                                 }
3630                                 if (first.useColors) {
3631                                   SendToProgram("black\n", &first);
3632                                 }
3633                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3634                                 first.maybeThinking = TRUE;
3635                             } else {
3636                                 if (first.usePlayother) {
3637                                   if (first.sendTime) {
3638                                     SendTimeRemaining(&first, FALSE);
3639                                   }
3640                                   SendToProgram("playother\n", &first);
3641                                   firstMove = FALSE;
3642                                 } else {
3643                                   firstMove = TRUE;
3644                                 }
3645                             }
3646                         }
3647                     }
3648 #endif
3649                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3650                         /* Moves came from oldmoves or moves command
3651                            while we weren't doing anything else.
3652                            */
3653                         currentMove = forwardMostMove;
3654                         ClearHighlights();/*!!could figure this out*/
3655                         flipView = appData.flipView;
3656                         DrawPosition(TRUE, boards[currentMove]);
3657                         DisplayBothClocks();
3658                         snprintf(str, MSG_SIZ, "%s %s %s",
3659                                 gameInfo.white, _("vs."),  gameInfo.black);
3660                         DisplayTitle(str);
3661                         gameMode = IcsIdle;
3662                     } else {
3663                         /* Moves were history of an active game */
3664                         if (gameInfo.resultDetails != NULL) {
3665                             free(gameInfo.resultDetails);
3666                             gameInfo.resultDetails = NULL;
3667                         }
3668                     }
3669                     HistorySet(parseList, backwardMostMove,
3670                                forwardMostMove, currentMove-1);
3671                     DisplayMove(currentMove - 1);
3672                     if (started == STARTED_MOVES) next_out = i;
3673                     started = STARTED_NONE;
3674                     ics_getting_history = H_FALSE;
3675                     break;
3676
3677                   case STARTED_OBSERVE:
3678                     started = STARTED_NONE;
3679                     SendToICS(ics_prefix);
3680                     SendToICS("refresh\n");
3681                     break;
3682
3683                   default:
3684                     break;
3685                 }
3686                 if(bookHit) { // [HGM] book: simulate book reply
3687                     static char bookMove[MSG_SIZ]; // a bit generous?
3688
3689                     programStats.nodes = programStats.depth = programStats.time =
3690                     programStats.score = programStats.got_only_move = 0;
3691                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3692
3693                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3694                     strcat(bookMove, bookHit);
3695                     HandleMachineMove(bookMove, &first);
3696                 }
3697                 continue;
3698             }
3699
3700             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3701                  started == STARTED_HOLDINGS ||
3702                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3703                 /* Accumulate characters in move list or board */
3704                 parse[parse_pos++] = buf[i];
3705             }
3706
3707             /* Start of game messages.  Mostly we detect start of game
3708                when the first board image arrives.  On some versions
3709                of the ICS, though, we need to do a "refresh" after starting
3710                to observe in order to get the current board right away. */
3711             if (looking_at(buf, &i, "Adding game * to observation list")) {
3712                 started = STARTED_OBSERVE;
3713                 continue;
3714             }
3715
3716             /* Handle auto-observe */
3717             if (appData.autoObserve &&
3718                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3719                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3720                 char *player;
3721                 /* Choose the player that was highlighted, if any. */
3722                 if (star_match[0][0] == '\033' ||
3723                     star_match[1][0] != '\033') {
3724                     player = star_match[0];
3725                 } else {
3726                     player = star_match[2];
3727                 }
3728                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3729                         ics_prefix, StripHighlightAndTitle(player));
3730                 SendToICS(str);
3731
3732                 /* Save ratings from notify string */
3733                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3734                 player1Rating = string_to_rating(star_match[1]);
3735                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3736                 player2Rating = string_to_rating(star_match[3]);
3737
3738                 if (appData.debugMode)
3739                   fprintf(debugFP,
3740                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3741                           player1Name, player1Rating,
3742                           player2Name, player2Rating);
3743
3744                 continue;
3745             }
3746
3747             /* Deal with automatic examine mode after a game,
3748                and with IcsObserving -> IcsExamining transition */
3749             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3750                 looking_at(buf, &i, "has made you an examiner of game *")) {
3751
3752                 int gamenum = atoi(star_match[0]);
3753                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3754                     gamenum == ics_gamenum) {
3755                     /* We were already playing or observing this game;
3756                        no need to refetch history */
3757                     gameMode = IcsExamining;
3758                     if (pausing) {
3759                         pauseExamForwardMostMove = forwardMostMove;
3760                     } else if (currentMove < forwardMostMove) {
3761                         ForwardInner(forwardMostMove);
3762                     }
3763                 } else {
3764                     /* I don't think this case really can happen */
3765                     SendToICS(ics_prefix);
3766                     SendToICS("refresh\n");
3767                 }
3768                 continue;
3769             }
3770
3771             /* Error messages */
3772 //          if (ics_user_moved) {
3773             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3774                 if (looking_at(buf, &i, "Illegal move") ||
3775                     looking_at(buf, &i, "Not a legal move") ||
3776                     looking_at(buf, &i, "Your king is in check") ||
3777                     looking_at(buf, &i, "It isn't your turn") ||
3778                     looking_at(buf, &i, "It is not your move")) {
3779                     /* Illegal move */
3780                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3781                         currentMove = forwardMostMove-1;
3782                         DisplayMove(currentMove - 1); /* before DMError */
3783                         DrawPosition(FALSE, boards[currentMove]);
3784                         SwitchClocks(forwardMostMove-1); // [HGM] race
3785                         DisplayBothClocks();
3786                     }
3787                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3788                     ics_user_moved = 0;
3789                     continue;
3790                 }
3791             }
3792
3793             if (looking_at(buf, &i, "still have time") ||
3794                 looking_at(buf, &i, "not out of time") ||
3795                 looking_at(buf, &i, "either player is out of time") ||
3796                 looking_at(buf, &i, "has timeseal; checking")) {
3797                 /* We must have called his flag a little too soon */
3798                 whiteFlag = blackFlag = FALSE;
3799                 continue;
3800             }
3801
3802             if (looking_at(buf, &i, "added * seconds to") ||
3803                 looking_at(buf, &i, "seconds were added to")) {
3804                 /* Update the clocks */
3805                 SendToICS(ics_prefix);
3806                 SendToICS("refresh\n");
3807                 continue;
3808             }
3809
3810             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3811                 ics_clock_paused = TRUE;
3812                 StopClocks();
3813                 continue;
3814             }
3815
3816             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3817                 ics_clock_paused = FALSE;
3818                 StartClocks();
3819                 continue;
3820             }
3821
3822             /* Grab player ratings from the Creating: message.
3823                Note we have to check for the special case when
3824                the ICS inserts things like [white] or [black]. */
3825             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3826                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3827                 /* star_matches:
3828                    0    player 1 name (not necessarily white)
3829                    1    player 1 rating
3830                    2    empty, white, or black (IGNORED)
3831                    3    player 2 name (not necessarily black)
3832                    4    player 2 rating
3833
3834                    The names/ratings are sorted out when the game
3835                    actually starts (below).
3836                 */
3837                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3838                 player1Rating = string_to_rating(star_match[1]);
3839                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3840                 player2Rating = string_to_rating(star_match[4]);
3841
3842                 if (appData.debugMode)
3843                   fprintf(debugFP,
3844                           "Ratings from 'Creating:' %s %d, %s %d\n",
3845                           player1Name, player1Rating,
3846                           player2Name, player2Rating);
3847
3848                 continue;
3849             }
3850
3851             /* Improved generic start/end-of-game messages */
3852             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3853                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3854                 /* If tkind == 0: */
3855                 /* star_match[0] is the game number */
3856                 /*           [1] is the white player's name */
3857                 /*           [2] is the black player's name */
3858                 /* For end-of-game: */
3859                 /*           [3] is the reason for the game end */
3860                 /*           [4] is a PGN end game-token, preceded by " " */
3861                 /* For start-of-game: */
3862                 /*           [3] begins with "Creating" or "Continuing" */
3863                 /*           [4] is " *" or empty (don't care). */
3864                 int gamenum = atoi(star_match[0]);
3865                 char *whitename, *blackname, *why, *endtoken;
3866                 ChessMove endtype = EndOfFile;
3867
3868                 if (tkind == 0) {
3869                   whitename = star_match[1];
3870                   blackname = star_match[2];
3871                   why = star_match[3];
3872                   endtoken = star_match[4];
3873                 } else {
3874                   whitename = star_match[1];
3875                   blackname = star_match[3];
3876                   why = star_match[5];
3877                   endtoken = star_match[6];
3878                 }
3879
3880                 /* Game start messages */
3881                 if (strncmp(why, "Creating ", 9) == 0 ||
3882                     strncmp(why, "Continuing ", 11) == 0) {
3883                     gs_gamenum = gamenum;
3884                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3885                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3886                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3887 #if ZIPPY
3888                     if (appData.zippyPlay) {
3889                         ZippyGameStart(whitename, blackname);
3890                     }
3891 #endif /*ZIPPY*/
3892                     partnerBoardValid = FALSE; // [HGM] bughouse
3893                     continue;
3894                 }
3895
3896                 /* Game end messages */
3897                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3898                     ics_gamenum != gamenum) {
3899                     continue;
3900                 }
3901                 while (endtoken[0] == ' ') endtoken++;
3902                 switch (endtoken[0]) {
3903                   case '*':
3904                   default:
3905                     endtype = GameUnfinished;
3906                     break;
3907                   case '0':
3908                     endtype = BlackWins;
3909                     break;
3910                   case '1':
3911                     if (endtoken[1] == '/')
3912                       endtype = GameIsDrawn;
3913                     else
3914                       endtype = WhiteWins;
3915                     break;
3916                 }
3917                 GameEnds(endtype, why, GE_ICS);
3918 #if ZIPPY
3919                 if (appData.zippyPlay && first.initDone) {
3920                     ZippyGameEnd(endtype, why);
3921                     if (first.pr == NoProc) {
3922                       /* Start the next process early so that we'll
3923                          be ready for the next challenge */
3924                       StartChessProgram(&first);
3925                     }
3926                     /* Send "new" early, in case this command takes
3927                        a long time to finish, so that we'll be ready
3928                        for the next challenge. */
3929                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3930                     Reset(TRUE, TRUE);
3931                 }
3932 #endif /*ZIPPY*/
3933                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3934                 continue;
3935             }
3936
3937             if (looking_at(buf, &i, "Removing game * from observation") ||
3938                 looking_at(buf, &i, "no longer observing game *") ||
3939                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3940                 if (gameMode == IcsObserving &&
3941                     atoi(star_match[0]) == ics_gamenum)
3942                   {
3943                       /* icsEngineAnalyze */
3944                       if (appData.icsEngineAnalyze) {
3945                             ExitAnalyzeMode();
3946                             ModeHighlight();
3947                       }
3948                       StopClocks();
3949                       gameMode = IcsIdle;
3950                       ics_gamenum = -1;
3951                       ics_user_moved = FALSE;
3952                   }
3953                 continue;
3954             }
3955
3956             if (looking_at(buf, &i, "no longer examining game *")) {
3957                 if (gameMode == IcsExamining &&
3958                     atoi(star_match[0]) == ics_gamenum)
3959                   {
3960                       gameMode = IcsIdle;
3961                       ics_gamenum = -1;
3962                       ics_user_moved = FALSE;
3963                   }
3964                 continue;
3965             }
3966
3967             /* Advance leftover_start past any newlines we find,
3968                so only partial lines can get reparsed */
3969             if (looking_at(buf, &i, "\n")) {
3970                 prevColor = curColor;
3971                 if (curColor != ColorNormal) {
3972                     if (oldi > next_out) {
3973                         SendToPlayer(&buf[next_out], oldi - next_out);
3974                         next_out = oldi;
3975                     }
3976                     Colorize(ColorNormal, FALSE);
3977                     curColor = ColorNormal;
3978                 }
3979                 if (started == STARTED_BOARD) {
3980                     started = STARTED_NONE;
3981                     parse[parse_pos] = NULLCHAR;
3982                     ParseBoard12(parse);
3983                     ics_user_moved = 0;
3984
3985                     /* Send premove here */
3986                     if (appData.premove) {
3987                       char str[MSG_SIZ];
3988                       if (currentMove == 0 &&
3989                           gameMode == IcsPlayingWhite &&
3990                           appData.premoveWhite) {
3991                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3992                         if (appData.debugMode)
3993                           fprintf(debugFP, "Sending premove:\n");
3994                         SendToICS(str);
3995                       } else if (currentMove == 1 &&
3996                                  gameMode == IcsPlayingBlack &&
3997                                  appData.premoveBlack) {
3998                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3999                         if (appData.debugMode)
4000                           fprintf(debugFP, "Sending premove:\n");
4001                         SendToICS(str);
4002                       } else if (gotPremove) {
4003                         gotPremove = 0;
4004                         ClearPremoveHighlights();
4005                         if (appData.debugMode)
4006                           fprintf(debugFP, "Sending premove:\n");
4007                           UserMoveEvent(premoveFromX, premoveFromY,
4008                                         premoveToX, premoveToY,
4009                                         premovePromoChar);
4010                       }
4011                     }
4012
4013                     /* Usually suppress following prompt */
4014                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4015                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4016                         if (looking_at(buf, &i, "*% ")) {
4017                             savingComment = FALSE;
4018                             suppressKibitz = 0;
4019                         }
4020                     }
4021                     next_out = i;
4022                 } else if (started == STARTED_HOLDINGS) {
4023                     int gamenum;
4024                     char new_piece[MSG_SIZ];
4025                     started = STARTED_NONE;
4026                     parse[parse_pos] = NULLCHAR;
4027                     if (appData.debugMode)
4028                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4029                                                         parse, currentMove);
4030                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4031                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4032                         if (gameInfo.variant == VariantNormal) {
4033                           /* [HGM] We seem to switch variant during a game!
4034                            * Presumably no holdings were displayed, so we have
4035                            * to move the position two files to the right to
4036                            * create room for them!
4037                            */
4038                           VariantClass newVariant;
4039                           switch(gameInfo.boardWidth) { // base guess on board width
4040                                 case 9:  newVariant = VariantShogi; break;
4041                                 case 10: newVariant = VariantGreat; break;
4042                                 default: newVariant = VariantCrazyhouse; break;
4043                           }
4044                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4045                           /* Get a move list just to see the header, which
4046                              will tell us whether this is really bug or zh */
4047                           if (ics_getting_history == H_FALSE) {
4048                             ics_getting_history = H_REQUESTED;
4049                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4050                             SendToICS(str);
4051                           }
4052                         }
4053                         new_piece[0] = NULLCHAR;
4054                         sscanf(parse, "game %d white [%s black [%s <- %s",
4055                                &gamenum, white_holding, black_holding,
4056                                new_piece);
4057                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4058                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4059                         /* [HGM] copy holdings to board holdings area */
4060                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4061                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4062                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4063 #if ZIPPY
4064                         if (appData.zippyPlay && first.initDone) {
4065                             ZippyHoldings(white_holding, black_holding,
4066                                           new_piece);
4067                         }
4068 #endif /*ZIPPY*/
4069                         if (tinyLayout || smallLayout) {
4070                             char wh[16], bh[16];
4071                             PackHolding(wh, white_holding);
4072                             PackHolding(bh, black_holding);
4073                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4074                                     gameInfo.white, gameInfo.black);
4075                         } else {
4076                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4077                                     gameInfo.white, white_holding, _("vs."),
4078                                     gameInfo.black, black_holding);
4079                         }
4080                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4081                         DrawPosition(FALSE, boards[currentMove]);
4082                         DisplayTitle(str);
4083                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4084                         sscanf(parse, "game %d white [%s black [%s <- %s",
4085                                &gamenum, white_holding, black_holding,
4086                                new_piece);
4087                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4088                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4089                         /* [HGM] copy holdings to partner-board holdings area */
4090                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4091                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4092                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4093                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4094                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4095                       }
4096                     }
4097                     /* Suppress following prompt */
4098                     if (looking_at(buf, &i, "*% ")) {
4099                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4100                         savingComment = FALSE;
4101                         suppressKibitz = 0;
4102                     }
4103                     next_out = i;
4104                 }
4105                 continue;
4106             }
4107
4108             i++;                /* skip unparsed character and loop back */
4109         }
4110
4111         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4112 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4113 //          SendToPlayer(&buf[next_out], i - next_out);
4114             started != STARTED_HOLDINGS && leftover_start > next_out) {
4115             SendToPlayer(&buf[next_out], leftover_start - next_out);
4116             next_out = i;
4117         }
4118
4119         leftover_len = buf_len - leftover_start;
4120         /* if buffer ends with something we couldn't parse,
4121            reparse it after appending the next read */
4122
4123     } else if (count == 0) {
4124         RemoveInputSource(isr);
4125         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4126     } else {
4127         DisplayFatalError(_("Error reading from ICS"), error, 1);
4128     }
4129 }
4130
4131
4132 /* Board style 12 looks like this:
4133
4134    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4135
4136  * The "<12> " is stripped before it gets to this routine.  The two
4137  * trailing 0's (flip state and clock ticking) are later addition, and
4138  * some chess servers may not have them, or may have only the first.
4139  * Additional trailing fields may be added in the future.
4140  */
4141
4142 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4143
4144 #define RELATION_OBSERVING_PLAYED    0
4145 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4146 #define RELATION_PLAYING_MYMOVE      1
4147 #define RELATION_PLAYING_NOTMYMOVE  -1
4148 #define RELATION_EXAMINING           2
4149 #define RELATION_ISOLATED_BOARD     -3
4150 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4151
4152 void
4153 ParseBoard12 (char *string)
4154 {
4155     GameMode newGameMode;
4156     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4157     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4158     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4159     char to_play, board_chars[200];
4160     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4161     char black[32], white[32];
4162     Board board;
4163     int prevMove = currentMove;
4164     int ticking = 2;
4165     ChessMove moveType;
4166     int fromX, fromY, toX, toY;
4167     char promoChar;
4168     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4169     char *bookHit = NULL; // [HGM] book
4170     Boolean weird = FALSE, reqFlag = FALSE;
4171
4172     fromX = fromY = toX = toY = -1;
4173
4174     newGame = FALSE;
4175
4176     if (appData.debugMode)
4177       fprintf(debugFP, _("Parsing board: %s\n"), string);
4178
4179     move_str[0] = NULLCHAR;
4180     elapsed_time[0] = NULLCHAR;
4181     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4182         int  i = 0, j;
4183         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4184             if(string[i] == ' ') { ranks++; files = 0; }
4185             else files++;
4186             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4187             i++;
4188         }
4189         for(j = 0; j <i; j++) board_chars[j] = string[j];
4190         board_chars[i] = '\0';
4191         string += i + 1;
4192     }
4193     n = sscanf(string, PATTERN, &to_play, &double_push,
4194                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4195                &gamenum, white, black, &relation, &basetime, &increment,
4196                &white_stren, &black_stren, &white_time, &black_time,
4197                &moveNum, str, elapsed_time, move_str, &ics_flip,
4198                &ticking);
4199
4200     if (n < 21) {
4201         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4202         DisplayError(str, 0);
4203         return;
4204     }
4205
4206     /* Convert the move number to internal form */
4207     moveNum = (moveNum - 1) * 2;
4208     if (to_play == 'B') moveNum++;
4209     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4210       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4211                         0, 1);
4212       return;
4213     }
4214
4215     switch (relation) {
4216       case RELATION_OBSERVING_PLAYED:
4217       case RELATION_OBSERVING_STATIC:
4218         if (gamenum == -1) {
4219             /* Old ICC buglet */
4220             relation = RELATION_OBSERVING_STATIC;
4221         }
4222         newGameMode = IcsObserving;
4223         break;
4224       case RELATION_PLAYING_MYMOVE:
4225       case RELATION_PLAYING_NOTMYMOVE:
4226         newGameMode =
4227           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4228             IcsPlayingWhite : IcsPlayingBlack;
4229         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4230         break;
4231       case RELATION_EXAMINING:
4232         newGameMode = IcsExamining;
4233         break;
4234       case RELATION_ISOLATED_BOARD:
4235       default:
4236         /* Just display this board.  If user was doing something else,
4237            we will forget about it until the next board comes. */
4238         newGameMode = IcsIdle;
4239         break;
4240       case RELATION_STARTING_POSITION:
4241         newGameMode = gameMode;
4242         break;
4243     }
4244
4245     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4246          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4247       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4248       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4249       char *toSqr;
4250       for (k = 0; k < ranks; k++) {
4251         for (j = 0; j < files; j++)
4252           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4253         if(gameInfo.holdingsWidth > 1) {
4254              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4255              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4256         }
4257       }
4258       CopyBoard(partnerBoard, board);
4259       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4260         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4261         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4262       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4263       if(toSqr = strchr(str, '-')) {
4264         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4265         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4266       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4267       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4268       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4269       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4270       if(twoBoards) {
4271           DisplayWhiteClock(white_time*fac, to_play == 'W');
4272           DisplayBlackClock(black_time*fac, to_play != 'W');
4273           activePartner = to_play;
4274           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4275                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4276       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4277                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4278       DisplayMessage(partnerStatus, "");
4279         partnerBoardValid = TRUE;
4280       return;
4281     }
4282
4283     /* Modify behavior for initial board display on move listing
4284        of wild games.
4285        */
4286     switch (ics_getting_history) {
4287       case H_FALSE:
4288       case H_REQUESTED:
4289         break;
4290       case H_GOT_REQ_HEADER:
4291       case H_GOT_UNREQ_HEADER:
4292         /* This is the initial position of the current game */
4293         gamenum = ics_gamenum;
4294         moveNum = 0;            /* old ICS bug workaround */
4295         if (to_play == 'B') {
4296           startedFromSetupPosition = TRUE;
4297           blackPlaysFirst = TRUE;
4298           moveNum = 1;
4299           if (forwardMostMove == 0) forwardMostMove = 1;
4300           if (backwardMostMove == 0) backwardMostMove = 1;
4301           if (currentMove == 0) currentMove = 1;
4302         }
4303         newGameMode = gameMode;
4304         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4305         break;
4306       case H_GOT_UNWANTED_HEADER:
4307         /* This is an initial board that we don't want */
4308         return;
4309       case H_GETTING_MOVES:
4310         /* Should not happen */
4311         DisplayError(_("Error gathering move list: extra board"), 0);
4312         ics_getting_history = H_FALSE;
4313         return;
4314     }
4315
4316    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4317                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4318                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4319      /* [HGM] We seem to have switched variant unexpectedly
4320       * Try to guess new variant from board size
4321       */
4322           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4323           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4324           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4325           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4326           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4327           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4328           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4329           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4330           /* Get a move list just to see the header, which
4331              will tell us whether this is really bug or zh */
4332           if (ics_getting_history == H_FALSE) {
4333             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4334             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4335             SendToICS(str);
4336           }
4337     }
4338
4339     /* Take action if this is the first board of a new game, or of a
4340        different game than is currently being displayed.  */
4341     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4342         relation == RELATION_ISOLATED_BOARD) {
4343
4344         /* Forget the old game and get the history (if any) of the new one */
4345         if (gameMode != BeginningOfGame) {
4346           Reset(TRUE, TRUE);
4347         }
4348         newGame = TRUE;
4349         if (appData.autoRaiseBoard) BoardToTop();
4350         prevMove = -3;
4351         if (gamenum == -1) {
4352             newGameMode = IcsIdle;
4353         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4354                    appData.getMoveList && !reqFlag) {
4355             /* Need to get game history */
4356             ics_getting_history = H_REQUESTED;
4357             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4358             SendToICS(str);
4359         }
4360
4361         /* Initially flip the board to have black on the bottom if playing
4362            black or if the ICS flip flag is set, but let the user change
4363            it with the Flip View button. */
4364         flipView = appData.autoFlipView ?
4365           (newGameMode == IcsPlayingBlack) || ics_flip :
4366           appData.flipView;
4367
4368         /* Done with values from previous mode; copy in new ones */
4369         gameMode = newGameMode;
4370         ModeHighlight();
4371         ics_gamenum = gamenum;
4372         if (gamenum == gs_gamenum) {
4373             int klen = strlen(gs_kind);
4374             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4375             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4376             gameInfo.event = StrSave(str);
4377         } else {
4378             gameInfo.event = StrSave("ICS game");
4379         }
4380         gameInfo.site = StrSave(appData.icsHost);
4381         gameInfo.date = PGNDate();
4382         gameInfo.round = StrSave("-");
4383         gameInfo.white = StrSave(white);
4384         gameInfo.black = StrSave(black);
4385         timeControl = basetime * 60 * 1000;
4386         timeControl_2 = 0;
4387         timeIncrement = increment * 1000;
4388         movesPerSession = 0;
4389         gameInfo.timeControl = TimeControlTagValue();
4390         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4391   if (appData.debugMode) {
4392     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4393     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4394     setbuf(debugFP, NULL);
4395   }
4396
4397         gameInfo.outOfBook = NULL;
4398
4399         /* Do we have the ratings? */
4400         if (strcmp(player1Name, white) == 0 &&
4401             strcmp(player2Name, black) == 0) {
4402             if (appData.debugMode)
4403               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4404                       player1Rating, player2Rating);
4405             gameInfo.whiteRating = player1Rating;
4406             gameInfo.blackRating = player2Rating;
4407         } else if (strcmp(player2Name, white) == 0 &&
4408                    strcmp(player1Name, black) == 0) {
4409             if (appData.debugMode)
4410               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4411                       player2Rating, player1Rating);
4412             gameInfo.whiteRating = player2Rating;
4413             gameInfo.blackRating = player1Rating;
4414         }
4415         player1Name[0] = player2Name[0] = NULLCHAR;
4416
4417         /* Silence shouts if requested */
4418         if (appData.quietPlay &&
4419             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4420             SendToICS(ics_prefix);
4421             SendToICS("set shout 0\n");
4422         }
4423     }
4424
4425     /* Deal with midgame name changes */
4426     if (!newGame) {
4427         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4428             if (gameInfo.white) free(gameInfo.white);
4429             gameInfo.white = StrSave(white);
4430         }
4431         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4432             if (gameInfo.black) free(gameInfo.black);
4433             gameInfo.black = StrSave(black);
4434         }
4435     }
4436
4437     /* Throw away game result if anything actually changes in examine mode */
4438     if (gameMode == IcsExamining && !newGame) {
4439         gameInfo.result = GameUnfinished;
4440         if (gameInfo.resultDetails != NULL) {
4441             free(gameInfo.resultDetails);
4442             gameInfo.resultDetails = NULL;
4443         }
4444     }
4445
4446     /* In pausing && IcsExamining mode, we ignore boards coming
4447        in if they are in a different variation than we are. */
4448     if (pauseExamInvalid) return;
4449     if (pausing && gameMode == IcsExamining) {
4450         if (moveNum <= pauseExamForwardMostMove) {
4451             pauseExamInvalid = TRUE;
4452             forwardMostMove = pauseExamForwardMostMove;
4453             return;
4454         }
4455     }
4456
4457   if (appData.debugMode) {
4458     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4459   }
4460     /* Parse the board */
4461     for (k = 0; k < ranks; k++) {
4462       for (j = 0; j < files; j++)
4463         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4464       if(gameInfo.holdingsWidth > 1) {
4465            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4466            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4467       }
4468     }
4469     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4470       board[5][BOARD_RGHT+1] = WhiteAngel;
4471       board[6][BOARD_RGHT+1] = WhiteMarshall;
4472       board[1][0] = BlackMarshall;
4473       board[2][0] = BlackAngel;
4474       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4475     }
4476     CopyBoard(boards[moveNum], board);
4477     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4478     if (moveNum == 0) {
4479         startedFromSetupPosition =
4480           !CompareBoards(board, initialPosition);
4481         if(startedFromSetupPosition)
4482             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4483     }
4484
4485     /* [HGM] Set castling rights. Take the outermost Rooks,
4486        to make it also work for FRC opening positions. Note that board12
4487        is really defective for later FRC positions, as it has no way to
4488        indicate which Rook can castle if they are on the same side of King.
4489        For the initial position we grant rights to the outermost Rooks,
4490        and remember thos rights, and we then copy them on positions
4491        later in an FRC game. This means WB might not recognize castlings with
4492        Rooks that have moved back to their original position as illegal,
4493        but in ICS mode that is not its job anyway.
4494     */
4495     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4496     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4497
4498         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4499             if(board[0][i] == WhiteRook) j = i;
4500         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4501         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4502             if(board[0][i] == WhiteRook) j = i;
4503         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4504         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4505             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4506         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4507         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4508             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4509         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4510
4511         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4512         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4513         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4514             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4515         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4516             if(board[BOARD_HEIGHT-1][k] == bKing)
4517                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4518         if(gameInfo.variant == VariantTwoKings) {
4519             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4520             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4521             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4522         }
4523     } else { int r;
4524         r = boards[moveNum][CASTLING][0] = initialRights[0];
4525         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4526         r = boards[moveNum][CASTLING][1] = initialRights[1];
4527         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4528         r = boards[moveNum][CASTLING][3] = initialRights[3];
4529         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4530         r = boards[moveNum][CASTLING][4] = initialRights[4];
4531         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4532         /* wildcastle kludge: always assume King has rights */
4533         r = boards[moveNum][CASTLING][2] = initialRights[2];
4534         r = boards[moveNum][CASTLING][5] = initialRights[5];
4535     }
4536     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4537     boards[moveNum][EP_STATUS] = EP_NONE;
4538     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4539     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4540     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4541
4542
4543     if (ics_getting_history == H_GOT_REQ_HEADER ||
4544         ics_getting_history == H_GOT_UNREQ_HEADER) {
4545         /* This was an initial position from a move list, not
4546            the current position */
4547         return;
4548     }
4549
4550     /* Update currentMove and known move number limits */
4551     newMove = newGame || moveNum > forwardMostMove;
4552
4553     if (newGame) {
4554         forwardMostMove = backwardMostMove = currentMove = moveNum;
4555         if (gameMode == IcsExamining && moveNum == 0) {
4556           /* Workaround for ICS limitation: we are not told the wild
4557              type when starting to examine a game.  But if we ask for
4558              the move list, the move list header will tell us */
4559             ics_getting_history = H_REQUESTED;
4560             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4561             SendToICS(str);
4562         }
4563     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4564                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4565 #if ZIPPY
4566         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4567         /* [HGM] applied this also to an engine that is silently watching        */
4568         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4569             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4570             gameInfo.variant == currentlyInitializedVariant) {
4571           takeback = forwardMostMove - moveNum;
4572           for (i = 0; i < takeback; i++) {
4573             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4574             SendToProgram("undo\n", &first);
4575           }
4576         }
4577 #endif
4578
4579         forwardMostMove = moveNum;
4580         if (!pausing || currentMove > forwardMostMove)
4581           currentMove = forwardMostMove;
4582     } else {
4583         /* New part of history that is not contiguous with old part */
4584         if (pausing && gameMode == IcsExamining) {
4585             pauseExamInvalid = TRUE;
4586             forwardMostMove = pauseExamForwardMostMove;
4587             return;
4588         }
4589         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4590 #if ZIPPY
4591             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4592                 // [HGM] when we will receive the move list we now request, it will be
4593                 // fed to the engine from the first move on. So if the engine is not
4594                 // in the initial position now, bring it there.
4595                 InitChessProgram(&first, 0);
4596             }
4597 #endif
4598             ics_getting_history = H_REQUESTED;
4599             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4600             SendToICS(str);
4601         }
4602         forwardMostMove = backwardMostMove = currentMove = moveNum;
4603     }
4604
4605     /* Update the clocks */
4606     if (strchr(elapsed_time, '.')) {
4607       /* Time is in ms */
4608       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4609       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4610     } else {
4611       /* Time is in seconds */
4612       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4613       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4614     }
4615
4616
4617 #if ZIPPY
4618     if (appData.zippyPlay && newGame &&
4619         gameMode != IcsObserving && gameMode != IcsIdle &&
4620         gameMode != IcsExamining)
4621       ZippyFirstBoard(moveNum, basetime, increment);
4622 #endif
4623
4624     /* Put the move on the move list, first converting
4625        to canonical algebraic form. */
4626     if (moveNum > 0) {
4627   if (appData.debugMode) {
4628     if (appData.debugMode) { int f = forwardMostMove;
4629         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4630                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4631                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4632     }
4633     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4634     fprintf(debugFP, "moveNum = %d\n", moveNum);
4635     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4636     setbuf(debugFP, NULL);
4637   }
4638         if (moveNum <= backwardMostMove) {
4639             /* We don't know what the board looked like before
4640                this move.  Punt. */
4641           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4642             strcat(parseList[moveNum - 1], " ");
4643             strcat(parseList[moveNum - 1], elapsed_time);
4644             moveList[moveNum - 1][0] = NULLCHAR;
4645         } else if (strcmp(move_str, "none") == 0) {
4646             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4647             /* Again, we don't know what the board looked like;
4648                this is really the start of the game. */
4649             parseList[moveNum - 1][0] = NULLCHAR;
4650             moveList[moveNum - 1][0] = NULLCHAR;
4651             backwardMostMove = moveNum;
4652             startedFromSetupPosition = TRUE;
4653             fromX = fromY = toX = toY = -1;
4654         } else {
4655           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4656           //                 So we parse the long-algebraic move string in stead of the SAN move
4657           int valid; char buf[MSG_SIZ], *prom;
4658
4659           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4660                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4661           // str looks something like "Q/a1-a2"; kill the slash
4662           if(str[1] == '/')
4663             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4664           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4665           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4666                 strcat(buf, prom); // long move lacks promo specification!
4667           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4668                 if(appData.debugMode)
4669                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4670                 safeStrCpy(move_str, buf, MSG_SIZ);
4671           }
4672           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4673                                 &fromX, &fromY, &toX, &toY, &promoChar)
4674                || ParseOneMove(buf, moveNum - 1, &moveType,
4675                                 &fromX, &fromY, &toX, &toY, &promoChar);
4676           // end of long SAN patch
4677           if (valid) {
4678             (void) CoordsToAlgebraic(boards[moveNum - 1],
4679                                      PosFlags(moveNum - 1),
4680                                      fromY, fromX, toY, toX, promoChar,
4681                                      parseList[moveNum-1]);
4682             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4683               case MT_NONE:
4684               case MT_STALEMATE:
4685               default:
4686                 break;
4687               case MT_CHECK:
4688                 if(gameInfo.variant != VariantShogi)
4689                     strcat(parseList[moveNum - 1], "+");
4690                 break;
4691               case MT_CHECKMATE:
4692               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4693                 strcat(parseList[moveNum - 1], "#");
4694                 break;
4695             }
4696             strcat(parseList[moveNum - 1], " ");
4697             strcat(parseList[moveNum - 1], elapsed_time);
4698             /* currentMoveString is set as a side-effect of ParseOneMove */
4699             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4700             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4701             strcat(moveList[moveNum - 1], "\n");
4702
4703             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4704                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4705               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4706                 ChessSquare old, new = boards[moveNum][k][j];
4707                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4708                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4709                   if(old == new) continue;
4710                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4711                   else if(new == WhiteWazir || new == BlackWazir) {
4712                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4713                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4714                       else boards[moveNum][k][j] = old; // preserve type of Gold
4715                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4716                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4717               }
4718           } else {
4719             /* Move from ICS was illegal!?  Punt. */
4720             if (appData.debugMode) {
4721               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4722               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4723             }
4724             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4725             strcat(parseList[moveNum - 1], " ");
4726             strcat(parseList[moveNum - 1], elapsed_time);
4727             moveList[moveNum - 1][0] = NULLCHAR;
4728             fromX = fromY = toX = toY = -1;
4729           }
4730         }
4731   if (appData.debugMode) {
4732     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4733     setbuf(debugFP, NULL);
4734   }
4735
4736 #if ZIPPY
4737         /* Send move to chess program (BEFORE animating it). */
4738         if (appData.zippyPlay && !newGame && newMove &&
4739            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4740
4741             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4742                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4743                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4744                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4745                             move_str);
4746                     DisplayError(str, 0);
4747                 } else {
4748                     if (first.sendTime) {
4749                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4750                     }
4751                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4752                     if (firstMove && !bookHit) {
4753                         firstMove = FALSE;
4754                         if (first.useColors) {
4755                           SendToProgram(gameMode == IcsPlayingWhite ?
4756                                         "white\ngo\n" :
4757                                         "black\ngo\n", &first);
4758                         } else {
4759                           SendToProgram("go\n", &first);
4760                         }
4761                         first.maybeThinking = TRUE;
4762                     }
4763                 }
4764             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4765               if (moveList[moveNum - 1][0] == NULLCHAR) {
4766                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4767                 DisplayError(str, 0);
4768               } else {
4769                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4770                 SendMoveToProgram(moveNum - 1, &first);
4771               }
4772             }
4773         }
4774 #endif
4775     }
4776
4777     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4778         /* If move comes from a remote source, animate it.  If it
4779            isn't remote, it will have already been animated. */
4780         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4781             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4782         }
4783         if (!pausing && appData.highlightLastMove) {
4784             SetHighlights(fromX, fromY, toX, toY);
4785         }
4786     }
4787
4788     /* Start the clocks */
4789     whiteFlag = blackFlag = FALSE;
4790     appData.clockMode = !(basetime == 0 && increment == 0);
4791     if (ticking == 0) {
4792       ics_clock_paused = TRUE;
4793       StopClocks();
4794     } else if (ticking == 1) {
4795       ics_clock_paused = FALSE;
4796     }
4797     if (gameMode == IcsIdle ||
4798         relation == RELATION_OBSERVING_STATIC ||
4799         relation == RELATION_EXAMINING ||
4800         ics_clock_paused)
4801       DisplayBothClocks();
4802     else
4803       StartClocks();
4804
4805     /* Display opponents and material strengths */
4806     if (gameInfo.variant != VariantBughouse &&
4807         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4808         if (tinyLayout || smallLayout) {
4809             if(gameInfo.variant == VariantNormal)
4810               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4811                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4812                     basetime, increment);
4813             else
4814               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4815                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4816                     basetime, increment, (int) gameInfo.variant);
4817         } else {
4818             if(gameInfo.variant == VariantNormal)
4819               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4820                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4821                     basetime, increment);
4822             else
4823               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4824                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4825                     basetime, increment, VariantName(gameInfo.variant));
4826         }
4827         DisplayTitle(str);
4828   if (appData.debugMode) {
4829     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4830   }
4831     }
4832
4833
4834     /* Display the board */
4835     if (!pausing && !appData.noGUI) {
4836
4837       if (appData.premove)
4838           if (!gotPremove ||
4839              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4840              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4841               ClearPremoveHighlights();
4842
4843       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4844         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4845       DrawPosition(j, boards[currentMove]);
4846
4847       DisplayMove(moveNum - 1);
4848       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4849             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4850               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4851         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4852       }
4853     }
4854
4855     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4856 #if ZIPPY
4857     if(bookHit) { // [HGM] book: simulate book reply
4858         static char bookMove[MSG_SIZ]; // a bit generous?
4859
4860         programStats.nodes = programStats.depth = programStats.time =
4861         programStats.score = programStats.got_only_move = 0;
4862         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4863
4864         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4865         strcat(bookMove, bookHit);
4866         HandleMachineMove(bookMove, &first);
4867     }
4868 #endif
4869 }
4870
4871 void
4872 GetMoveListEvent ()
4873 {
4874     char buf[MSG_SIZ];
4875     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4876         ics_getting_history = H_REQUESTED;
4877         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4878         SendToICS(buf);
4879     }
4880 }
4881
4882 void
4883 AnalysisPeriodicEvent (int force)
4884 {
4885     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4886          && !force) || !appData.periodicUpdates)
4887       return;
4888
4889     /* Send . command to Crafty to collect stats */
4890     SendToProgram(".\n", &first);
4891
4892     /* Don't send another until we get a response (this makes
4893        us stop sending to old Crafty's which don't understand
4894        the "." command (sending illegal cmds resets node count & time,
4895        which looks bad)) */
4896     programStats.ok_to_send = 0;
4897 }
4898
4899 void
4900 ics_update_width (int new_width)
4901 {
4902         ics_printf("set width %d\n", new_width);
4903 }
4904
4905 void
4906 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4907 {
4908     char buf[MSG_SIZ];
4909
4910     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4911         // null move in variant where engine does not understand it (for analysis purposes)
4912         SendBoard(cps, moveNum + 1); // send position after move in stead.
4913         return;
4914     }
4915     if (cps->useUsermove) {
4916       SendToProgram("usermove ", cps);
4917     }
4918     if (cps->useSAN) {
4919       char *space;
4920       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4921         int len = space - parseList[moveNum];
4922         memcpy(buf, parseList[moveNum], len);
4923         buf[len++] = '\n';
4924         buf[len] = NULLCHAR;
4925       } else {
4926         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4927       }
4928       SendToProgram(buf, cps);
4929     } else {
4930       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4931         AlphaRank(moveList[moveNum], 4);
4932         SendToProgram(moveList[moveNum], cps);
4933         AlphaRank(moveList[moveNum], 4); // and back
4934       } else
4935       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4936        * the engine. It would be nice to have a better way to identify castle
4937        * moves here. */
4938       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4939                                                                          && cps->useOOCastle) {
4940         int fromX = moveList[moveNum][0] - AAA;
4941         int fromY = moveList[moveNum][1] - ONE;
4942         int toX = moveList[moveNum][2] - AAA;
4943         int toY = moveList[moveNum][3] - ONE;
4944         if((boards[moveNum][fromY][fromX] == WhiteKing
4945             && boards[moveNum][toY][toX] == WhiteRook)
4946            || (boards[moveNum][fromY][fromX] == BlackKing
4947                && boards[moveNum][toY][toX] == BlackRook)) {
4948           if(toX > fromX) SendToProgram("O-O\n", cps);
4949           else SendToProgram("O-O-O\n", cps);
4950         }
4951         else SendToProgram(moveList[moveNum], cps);
4952       } else
4953       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4954         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4955           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4956           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4957                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4958         } else
4959           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4960                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4961         SendToProgram(buf, cps);
4962       }
4963       else SendToProgram(moveList[moveNum], cps);
4964       /* End of additions by Tord */
4965     }
4966
4967     /* [HGM] setting up the opening has brought engine in force mode! */
4968     /*       Send 'go' if we are in a mode where machine should play. */
4969     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4970         (gameMode == TwoMachinesPlay   ||
4971 #if ZIPPY
4972          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4973 #endif
4974          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4975         SendToProgram("go\n", cps);
4976   if (appData.debugMode) {
4977     fprintf(debugFP, "(extra)\n");
4978   }
4979     }
4980     setboardSpoiledMachineBlack = 0;
4981 }
4982
4983 void
4984 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4985 {
4986     char user_move[MSG_SIZ];
4987     char suffix[4];
4988
4989     if(gameInfo.variant == VariantSChess && promoChar) {
4990         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4991         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4992     } else suffix[0] = NULLCHAR;
4993
4994     switch (moveType) {
4995       default:
4996         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4997                 (int)moveType, fromX, fromY, toX, toY);
4998         DisplayError(user_move + strlen("say "), 0);
4999         break;
5000       case WhiteKingSideCastle:
5001       case BlackKingSideCastle:
5002       case WhiteQueenSideCastleWild:
5003       case BlackQueenSideCastleWild:
5004       /* PUSH Fabien */
5005       case WhiteHSideCastleFR:
5006       case BlackHSideCastleFR:
5007       /* POP Fabien */
5008         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5009         break;
5010       case WhiteQueenSideCastle:
5011       case BlackQueenSideCastle:
5012       case WhiteKingSideCastleWild:
5013       case BlackKingSideCastleWild:
5014       /* PUSH Fabien */
5015       case WhiteASideCastleFR:
5016       case BlackASideCastleFR:
5017       /* POP Fabien */
5018         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5019         break;
5020       case WhiteNonPromotion:
5021       case BlackNonPromotion:
5022         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5023         break;
5024       case WhitePromotion:
5025       case BlackPromotion:
5026         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5027           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5028                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5029                 PieceToChar(WhiteFerz));
5030         else if(gameInfo.variant == VariantGreat)
5031           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5032                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5033                 PieceToChar(WhiteMan));
5034         else
5035           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5036                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5037                 promoChar);
5038         break;
5039       case WhiteDrop:
5040       case BlackDrop:
5041       drop:
5042         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5043                  ToUpper(PieceToChar((ChessSquare) fromX)),
5044                  AAA + toX, ONE + toY);
5045         break;
5046       case IllegalMove:  /* could be a variant we don't quite understand */
5047         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5048       case NormalMove:
5049       case WhiteCapturesEnPassant:
5050       case BlackCapturesEnPassant:
5051         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5052                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5053         break;
5054     }
5055     SendToICS(user_move);
5056     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5057         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5058 }
5059
5060 void
5061 UploadGameEvent ()
5062 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5063     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5064     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5065     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5066       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5067       return;
5068     }
5069     if(gameMode != IcsExamining) { // is this ever not the case?
5070         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5071
5072         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5073           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5074         } else { // on FICS we must first go to general examine mode
5075           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5076         }
5077         if(gameInfo.variant != VariantNormal) {
5078             // try figure out wild number, as xboard names are not always valid on ICS
5079             for(i=1; i<=36; i++) {
5080               snprintf(buf, MSG_SIZ, "wild/%d", i);
5081                 if(StringToVariant(buf) == gameInfo.variant) break;
5082             }
5083             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5084             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5085             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5086         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5087         SendToICS(ics_prefix);
5088         SendToICS(buf);
5089         if(startedFromSetupPosition || backwardMostMove != 0) {
5090           fen = PositionToFEN(backwardMostMove, NULL);
5091           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5092             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5093             SendToICS(buf);
5094           } else { // FICS: everything has to set by separate bsetup commands
5095             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5096             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5097             SendToICS(buf);
5098             if(!WhiteOnMove(backwardMostMove)) {
5099                 SendToICS("bsetup tomove black\n");
5100             }
5101             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5102             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5103             SendToICS(buf);
5104             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5105             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5106             SendToICS(buf);
5107             i = boards[backwardMostMove][EP_STATUS];
5108             if(i >= 0) { // set e.p.
5109               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5110                 SendToICS(buf);
5111             }
5112             bsetup++;
5113           }
5114         }
5115       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5116             SendToICS("bsetup done\n"); // switch to normal examining.
5117     }
5118     for(i = backwardMostMove; i<last; i++) {
5119         char buf[20];
5120         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5121         SendToICS(buf);
5122     }
5123     SendToICS(ics_prefix);
5124     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5125 }
5126
5127 void
5128 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5129 {
5130     if (rf == DROP_RANK) {
5131       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5132       sprintf(move, "%c@%c%c\n",
5133                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5134     } else {
5135         if (promoChar == 'x' || promoChar == NULLCHAR) {
5136           sprintf(move, "%c%c%c%c\n",
5137                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5138         } else {
5139             sprintf(move, "%c%c%c%c%c\n",
5140                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5141         }
5142     }
5143 }
5144
5145 void
5146 ProcessICSInitScript (FILE *f)
5147 {
5148     char buf[MSG_SIZ];
5149
5150     while (fgets(buf, MSG_SIZ, f)) {
5151         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5152     }
5153
5154     fclose(f);
5155 }
5156
5157
5158 static int lastX, lastY, selectFlag, dragging;
5159
5160 void
5161 Sweep (int step)
5162 {
5163     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5164     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5165     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5166     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5167     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5168     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5169     do {
5170         promoSweep -= step;
5171         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5172         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5173         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5174         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5175         if(!step) step = -1;
5176     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5177             appData.testLegality && (promoSweep == king ||
5178             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5179     if(toX >= 0) {
5180         int victim = boards[currentMove][toY][toX];
5181         boards[currentMove][toY][toX] = promoSweep;
5182         DrawPosition(FALSE, boards[currentMove]);
5183         boards[currentMove][toY][toX] = victim;
5184     } else
5185     ChangeDragPiece(promoSweep);
5186 }
5187
5188 int
5189 PromoScroll (int x, int y)
5190 {
5191   int step = 0;
5192
5193   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5194   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5195   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5196   if(!step) return FALSE;
5197   lastX = x; lastY = y;
5198   if((promoSweep < BlackPawn) == flipView) step = -step;
5199   if(step > 0) selectFlag = 1;
5200   if(!selectFlag) Sweep(step);
5201   return FALSE;
5202 }
5203
5204 void
5205 NextPiece (int step)
5206 {
5207     ChessSquare piece = boards[currentMove][toY][toX];
5208     do {
5209         pieceSweep -= step;
5210         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5211         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5212         if(!step) step = -1;
5213     } while(PieceToChar(pieceSweep) == '.');
5214     boards[currentMove][toY][toX] = pieceSweep;
5215     DrawPosition(FALSE, boards[currentMove]);
5216     boards[currentMove][toY][toX] = piece;
5217 }
5218 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5219 void
5220 AlphaRank (char *move, int n)
5221 {
5222 //    char *p = move, c; int x, y;
5223
5224     if (appData.debugMode) {
5225         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5226     }
5227
5228     if(move[1]=='*' &&
5229        move[2]>='0' && move[2]<='9' &&
5230        move[3]>='a' && move[3]<='x'    ) {
5231         move[1] = '@';
5232         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5233         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5234     } else
5235     if(move[0]>='0' && move[0]<='9' &&
5236        move[1]>='a' && move[1]<='x' &&
5237        move[2]>='0' && move[2]<='9' &&
5238        move[3]>='a' && move[3]<='x'    ) {
5239         /* input move, Shogi -> normal */
5240         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5241         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5242         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5243         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5244     } else
5245     if(move[1]=='@' &&
5246        move[3]>='0' && move[3]<='9' &&
5247        move[2]>='a' && move[2]<='x'    ) {
5248         move[1] = '*';
5249         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5250         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5251     } else
5252     if(
5253        move[0]>='a' && move[0]<='x' &&
5254        move[3]>='0' && move[3]<='9' &&
5255        move[2]>='a' && move[2]<='x'    ) {
5256          /* output move, normal -> Shogi */
5257         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5258         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5259         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5260         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5261         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5262     }
5263     if (appData.debugMode) {
5264         fprintf(debugFP, "   out = '%s'\n", move);
5265     }
5266 }
5267
5268 char yy_textstr[8000];
5269
5270 /* Parser for moves from gnuchess, ICS, or user typein box */
5271 Boolean
5272 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5273 {
5274     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5275
5276     switch (*moveType) {
5277       case WhitePromotion:
5278       case BlackPromotion:
5279       case WhiteNonPromotion:
5280       case BlackNonPromotion:
5281       case NormalMove:
5282       case WhiteCapturesEnPassant:
5283       case BlackCapturesEnPassant:
5284       case WhiteKingSideCastle:
5285       case WhiteQueenSideCastle:
5286       case BlackKingSideCastle:
5287       case BlackQueenSideCastle:
5288       case WhiteKingSideCastleWild:
5289       case WhiteQueenSideCastleWild:
5290       case BlackKingSideCastleWild:
5291       case BlackQueenSideCastleWild:
5292       /* Code added by Tord: */
5293       case WhiteHSideCastleFR:
5294       case WhiteASideCastleFR:
5295       case BlackHSideCastleFR:
5296       case BlackASideCastleFR:
5297       /* End of code added by Tord */
5298       case IllegalMove:         /* bug or odd chess variant */
5299         *fromX = currentMoveString[0] - AAA;
5300         *fromY = currentMoveString[1] - ONE;
5301         *toX = currentMoveString[2] - AAA;
5302         *toY = currentMoveString[3] - ONE;
5303         *promoChar = currentMoveString[4];
5304         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5305             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5306     if (appData.debugMode) {
5307         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5308     }
5309             *fromX = *fromY = *toX = *toY = 0;
5310             return FALSE;
5311         }
5312         if (appData.testLegality) {
5313           return (*moveType != IllegalMove);
5314         } else {
5315           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5316                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5317         }
5318
5319       case WhiteDrop:
5320       case BlackDrop:
5321         *fromX = *moveType == WhiteDrop ?
5322           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5323           (int) CharToPiece(ToLower(currentMoveString[0]));
5324         *fromY = DROP_RANK;
5325         *toX = currentMoveString[2] - AAA;
5326         *toY = currentMoveString[3] - ONE;
5327         *promoChar = NULLCHAR;
5328         return TRUE;
5329
5330       case AmbiguousMove:
5331       case ImpossibleMove:
5332       case EndOfFile:
5333       case ElapsedTime:
5334       case Comment:
5335       case PGNTag:
5336       case NAG:
5337       case WhiteWins:
5338       case BlackWins:
5339       case GameIsDrawn:
5340       default:
5341     if (appData.debugMode) {
5342         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5343     }
5344         /* bug? */
5345         *fromX = *fromY = *toX = *toY = 0;
5346         *promoChar = NULLCHAR;
5347         return FALSE;
5348     }
5349 }
5350
5351 Boolean pushed = FALSE;
5352 char *lastParseAttempt;
5353
5354 void
5355 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5356 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5357   int fromX, fromY, toX, toY; char promoChar;
5358   ChessMove moveType;
5359   Boolean valid;
5360   int nr = 0;
5361
5362   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5363     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5364     pushed = TRUE;
5365   }
5366   endPV = forwardMostMove;
5367   do {
5368     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5369     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5370     lastParseAttempt = pv;
5371     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5372     if(!valid && nr == 0 &&
5373        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5374         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5375         // Hande case where played move is different from leading PV move
5376         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5377         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5378         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5379         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5380           endPV += 2; // if position different, keep this
5381           moveList[endPV-1][0] = fromX + AAA;
5382           moveList[endPV-1][1] = fromY + ONE;
5383           moveList[endPV-1][2] = toX + AAA;
5384           moveList[endPV-1][3] = toY + ONE;
5385           parseList[endPV-1][0] = NULLCHAR;
5386           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5387         }
5388       }
5389     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5390     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5391     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5392     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5393         valid++; // allow comments in PV
5394         continue;
5395     }
5396     nr++;
5397     if(endPV+1 > framePtr) break; // no space, truncate
5398     if(!valid) break;
5399     endPV++;
5400     CopyBoard(boards[endPV], boards[endPV-1]);
5401     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5402     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5403     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5404     CoordsToAlgebraic(boards[endPV - 1],
5405                              PosFlags(endPV - 1),
5406                              fromY, fromX, toY, toX, promoChar,
5407                              parseList[endPV - 1]);
5408   } while(valid);
5409   if(atEnd == 2) return; // used hidden, for PV conversion
5410   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5411   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5412   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5413                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5414   DrawPosition(TRUE, boards[currentMove]);
5415 }
5416
5417 int
5418 MultiPV (ChessProgramState *cps)
5419 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5420         int i;
5421         for(i=0; i<cps->nrOptions; i++)
5422             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5423                 return i;
5424         return -1;
5425 }
5426
5427 Boolean
5428 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5429 {
5430         int startPV, multi, lineStart, origIndex = index;
5431         char *p, buf2[MSG_SIZ];
5432
5433         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5434         lastX = x; lastY = y;
5435         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5436         lineStart = startPV = index;
5437         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5438         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5439         index = startPV;
5440         do{ while(buf[index] && buf[index] != '\n') index++;
5441         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5442         buf[index] = 0;
5443         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5444                 int n = first.option[multi].value;
5445                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5446                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5447                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5448                 first.option[multi].value = n;
5449                 *start = *end = 0;
5450                 return FALSE;
5451         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5452                 ExcludeClick(origIndex - lineStart);
5453                 return FALSE;
5454         }
5455         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5456         *start = startPV; *end = index-1;
5457         return TRUE;
5458 }
5459
5460 char *
5461 PvToSAN (char *pv)
5462 {
5463         static char buf[10*MSG_SIZ];
5464         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5465         *buf = NULLCHAR;
5466         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5467         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5468         for(i = forwardMostMove; i<endPV; i++){
5469             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5470             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5471             k += strlen(buf+k);
5472         }
5473         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5474         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5475         endPV = savedEnd;
5476         return buf;
5477 }
5478
5479 Boolean
5480 LoadPV (int x, int y)
5481 { // called on right mouse click to load PV
5482   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5483   lastX = x; lastY = y;
5484   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5485   return TRUE;
5486 }
5487
5488 void
5489 UnLoadPV ()
5490 {
5491   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5492   if(endPV < 0) return;
5493   if(appData.autoCopyPV) CopyFENToClipboard();
5494   endPV = -1;
5495   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5496         Boolean saveAnimate = appData.animate;
5497         if(pushed) {
5498             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5499                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5500             } else storedGames--; // abandon shelved tail of original game
5501         }
5502         pushed = FALSE;
5503         forwardMostMove = currentMove;
5504         currentMove = oldFMM;
5505         appData.animate = FALSE;
5506         ToNrEvent(forwardMostMove);
5507         appData.animate = saveAnimate;
5508   }
5509   currentMove = forwardMostMove;
5510   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5511   ClearPremoveHighlights();
5512   DrawPosition(TRUE, boards[currentMove]);
5513 }
5514
5515 void
5516 MovePV (int x, int y, int h)
5517 { // step through PV based on mouse coordinates (called on mouse move)
5518   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5519
5520   // we must somehow check if right button is still down (might be released off board!)
5521   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5522   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5523   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5524   if(!step) return;
5525   lastX = x; lastY = y;
5526
5527   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5528   if(endPV < 0) return;
5529   if(y < margin) step = 1; else
5530   if(y > h - margin) step = -1;
5531   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5532   currentMove += step;
5533   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5534   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5535                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5536   DrawPosition(FALSE, boards[currentMove]);
5537 }
5538
5539
5540 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5541 // All positions will have equal probability, but the current method will not provide a unique
5542 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5543 #define DARK 1
5544 #define LITE 2
5545 #define ANY 3
5546
5547 int squaresLeft[4];
5548 int piecesLeft[(int)BlackPawn];
5549 int seed, nrOfShuffles;
5550
5551 void
5552 GetPositionNumber ()
5553 {       // sets global variable seed
5554         int i;
5555
5556         seed = appData.defaultFrcPosition;
5557         if(seed < 0) { // randomize based on time for negative FRC position numbers
5558                 for(i=0; i<50; i++) seed += random();
5559                 seed = random() ^ random() >> 8 ^ random() << 8;
5560                 if(seed<0) seed = -seed;
5561         }
5562 }
5563
5564 int
5565 put (Board board, int pieceType, int rank, int n, int shade)
5566 // put the piece on the (n-1)-th empty squares of the given shade
5567 {
5568         int i;
5569
5570         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5571                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5572                         board[rank][i] = (ChessSquare) pieceType;
5573                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5574                         squaresLeft[ANY]--;
5575                         piecesLeft[pieceType]--;
5576                         return i;
5577                 }
5578         }
5579         return -1;
5580 }
5581
5582
5583 void
5584 AddOnePiece (Board board, int pieceType, int rank, int shade)
5585 // calculate where the next piece goes, (any empty square), and put it there
5586 {
5587         int i;
5588
5589         i = seed % squaresLeft[shade];
5590         nrOfShuffles *= squaresLeft[shade];
5591         seed /= squaresLeft[shade];
5592         put(board, pieceType, rank, i, shade);
5593 }
5594
5595 void
5596 AddTwoPieces (Board board, int pieceType, int rank)
5597 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5598 {
5599         int i, n=squaresLeft[ANY], j=n-1, k;
5600
5601         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5602         i = seed % k;  // pick one
5603         nrOfShuffles *= k;
5604         seed /= k;
5605         while(i >= j) i -= j--;
5606         j = n - 1 - j; i += j;
5607         put(board, pieceType, rank, j, ANY);
5608         put(board, pieceType, rank, i, ANY);
5609 }
5610
5611 void
5612 SetUpShuffle (Board board, int number)
5613 {
5614         int i, p, first=1;
5615
5616         GetPositionNumber(); nrOfShuffles = 1;
5617
5618         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5619         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5620         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5621
5622         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5623
5624         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5625             p = (int) board[0][i];
5626             if(p < (int) BlackPawn) piecesLeft[p] ++;
5627             board[0][i] = EmptySquare;
5628         }
5629
5630         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5631             // shuffles restricted to allow normal castling put KRR first
5632             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5633                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5634             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5635                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5636             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5637                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5638             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5639                 put(board, WhiteRook, 0, 0, ANY);
5640             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5641         }
5642
5643         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5644             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5645             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5646                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5647                 while(piecesLeft[p] >= 2) {
5648                     AddOnePiece(board, p, 0, LITE);
5649                     AddOnePiece(board, p, 0, DARK);
5650                 }
5651                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5652             }
5653
5654         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5655             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5656             // but we leave King and Rooks for last, to possibly obey FRC restriction
5657             if(p == (int)WhiteRook) continue;
5658             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5659             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5660         }
5661
5662         // now everything is placed, except perhaps King (Unicorn) and Rooks
5663
5664         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5665             // Last King gets castling rights
5666             while(piecesLeft[(int)WhiteUnicorn]) {
5667                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5668                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5669             }
5670
5671             while(piecesLeft[(int)WhiteKing]) {
5672                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5673                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5674             }
5675
5676
5677         } else {
5678             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5679             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5680         }
5681
5682         // Only Rooks can be left; simply place them all
5683         while(piecesLeft[(int)WhiteRook]) {
5684                 i = put(board, WhiteRook, 0, 0, ANY);
5685                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5686                         if(first) {
5687                                 first=0;
5688                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5689                         }
5690                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5691                 }
5692         }
5693         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5694             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5695         }
5696
5697         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5698 }
5699
5700 int
5701 SetCharTable (char *table, const char * map)
5702 /* [HGM] moved here from winboard.c because of its general usefulness */
5703 /*       Basically a safe strcpy that uses the last character as King */
5704 {
5705     int result = FALSE; int NrPieces;
5706
5707     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5708                     && NrPieces >= 12 && !(NrPieces&1)) {
5709         int i; /* [HGM] Accept even length from 12 to 34 */
5710
5711         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5712         for( i=0; i<NrPieces/2-1; i++ ) {
5713             table[i] = map[i];
5714             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5715         }
5716         table[(int) WhiteKing]  = map[NrPieces/2-1];
5717         table[(int) BlackKing]  = map[NrPieces-1];
5718
5719         result = TRUE;
5720     }
5721
5722     return result;
5723 }
5724
5725 void
5726 Prelude (Board board)
5727 {       // [HGM] superchess: random selection of exo-pieces
5728         int i, j, k; ChessSquare p;
5729         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5730
5731         GetPositionNumber(); // use FRC position number
5732
5733         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5734             SetCharTable(pieceToChar, appData.pieceToCharTable);
5735             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5736                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5737         }
5738
5739         j = seed%4;                 seed /= 4;
5740         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5741         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5742         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5743         j = seed%3 + (seed%3 >= j); seed /= 3;
5744         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5745         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5746         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5747         j = seed%3;                 seed /= 3;
5748         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5749         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5750         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5751         j = seed%2 + (seed%2 >= j); seed /= 2;
5752         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5753         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5754         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5755         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5756         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5757         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5758         put(board, exoPieces[0],    0, 0, ANY);
5759         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5760 }
5761
5762 void
5763 InitPosition (int redraw)
5764 {
5765     ChessSquare (* pieces)[BOARD_FILES];
5766     int i, j, pawnRow, overrule,
5767     oldx = gameInfo.boardWidth,
5768     oldy = gameInfo.boardHeight,
5769     oldh = gameInfo.holdingsWidth;
5770     static int oldv;
5771
5772     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5773
5774     /* [AS] Initialize pv info list [HGM] and game status */
5775     {
5776         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5777             pvInfoList[i].depth = 0;
5778             boards[i][EP_STATUS] = EP_NONE;
5779             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5780         }
5781
5782         initialRulePlies = 0; /* 50-move counter start */
5783
5784         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5785         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5786     }
5787
5788
5789     /* [HGM] logic here is completely changed. In stead of full positions */
5790     /* the initialized data only consist of the two backranks. The switch */
5791     /* selects which one we will use, which is than copied to the Board   */
5792     /* initialPosition, which for the rest is initialized by Pawns and    */
5793     /* empty squares. This initial position is then copied to boards[0],  */
5794     /* possibly after shuffling, so that it remains available.            */
5795
5796     gameInfo.holdingsWidth = 0; /* default board sizes */
5797     gameInfo.boardWidth    = 8;
5798     gameInfo.boardHeight   = 8;
5799     gameInfo.holdingsSize  = 0;
5800     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5801     for(i=0; i<BOARD_FILES-2; i++)
5802       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5803     initialPosition[EP_STATUS] = EP_NONE;
5804     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5805     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5806          SetCharTable(pieceNickName, appData.pieceNickNames);
5807     else SetCharTable(pieceNickName, "............");
5808     pieces = FIDEArray;
5809
5810     switch (gameInfo.variant) {
5811     case VariantFischeRandom:
5812       shuffleOpenings = TRUE;
5813     default:
5814       break;
5815     case VariantShatranj:
5816       pieces = ShatranjArray;
5817       nrCastlingRights = 0;
5818       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5819       break;
5820     case VariantMakruk:
5821       pieces = makrukArray;
5822       nrCastlingRights = 0;
5823       startedFromSetupPosition = TRUE;
5824       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5825       break;
5826     case VariantTwoKings:
5827       pieces = twoKingsArray;
5828       break;
5829     case VariantGrand:
5830       pieces = GrandArray;
5831       nrCastlingRights = 0;
5832       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5833       gameInfo.boardWidth = 10;
5834       gameInfo.boardHeight = 10;
5835       gameInfo.holdingsSize = 7;
5836       break;
5837     case VariantCapaRandom:
5838       shuffleOpenings = TRUE;
5839     case VariantCapablanca:
5840       pieces = CapablancaArray;
5841       gameInfo.boardWidth = 10;
5842       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5843       break;
5844     case VariantGothic:
5845       pieces = GothicArray;
5846       gameInfo.boardWidth = 10;
5847       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5848       break;
5849     case VariantSChess:
5850       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5851       gameInfo.holdingsSize = 7;
5852       break;
5853     case VariantJanus:
5854       pieces = JanusArray;
5855       gameInfo.boardWidth = 10;
5856       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5857       nrCastlingRights = 6;
5858         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5859         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5860         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5861         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5862         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5863         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5864       break;
5865     case VariantFalcon:
5866       pieces = FalconArray;
5867       gameInfo.boardWidth = 10;
5868       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5869       break;
5870     case VariantXiangqi:
5871       pieces = XiangqiArray;
5872       gameInfo.boardWidth  = 9;
5873       gameInfo.boardHeight = 10;
5874       nrCastlingRights = 0;
5875       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5876       break;
5877     case VariantShogi:
5878       pieces = ShogiArray;
5879       gameInfo.boardWidth  = 9;
5880       gameInfo.boardHeight = 9;
5881       gameInfo.holdingsSize = 7;
5882       nrCastlingRights = 0;
5883       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5884       break;
5885     case VariantCourier:
5886       pieces = CourierArray;
5887       gameInfo.boardWidth  = 12;
5888       nrCastlingRights = 0;
5889       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5890       break;
5891     case VariantKnightmate:
5892       pieces = KnightmateArray;
5893       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5894       break;
5895     case VariantSpartan:
5896       pieces = SpartanArray;
5897       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5898       break;
5899     case VariantFairy:
5900       pieces = fairyArray;
5901       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5902       break;
5903     case VariantGreat:
5904       pieces = GreatArray;
5905       gameInfo.boardWidth = 10;
5906       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5907       gameInfo.holdingsSize = 8;
5908       break;
5909     case VariantSuper:
5910       pieces = FIDEArray;
5911       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5912       gameInfo.holdingsSize = 8;
5913       startedFromSetupPosition = TRUE;
5914       break;
5915     case VariantCrazyhouse:
5916     case VariantBughouse:
5917       pieces = FIDEArray;
5918       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5919       gameInfo.holdingsSize = 5;
5920       break;
5921     case VariantWildCastle:
5922       pieces = FIDEArray;
5923       /* !!?shuffle with kings guaranteed to be on d or e file */
5924       shuffleOpenings = 1;
5925       break;
5926     case VariantNoCastle:
5927       pieces = FIDEArray;
5928       nrCastlingRights = 0;
5929       /* !!?unconstrained back-rank shuffle */
5930       shuffleOpenings = 1;
5931       break;
5932     }
5933
5934     overrule = 0;
5935     if(appData.NrFiles >= 0) {
5936         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5937         gameInfo.boardWidth = appData.NrFiles;
5938     }
5939     if(appData.NrRanks >= 0) {
5940         gameInfo.boardHeight = appData.NrRanks;
5941     }
5942     if(appData.holdingsSize >= 0) {
5943         i = appData.holdingsSize;
5944         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5945         gameInfo.holdingsSize = i;
5946     }
5947     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5948     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5949         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5950
5951     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5952     if(pawnRow < 1) pawnRow = 1;
5953     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5954
5955     /* User pieceToChar list overrules defaults */
5956     if(appData.pieceToCharTable != NULL)
5957         SetCharTable(pieceToChar, appData.pieceToCharTable);
5958
5959     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5960
5961         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5962             s = (ChessSquare) 0; /* account holding counts in guard band */
5963         for( i=0; i<BOARD_HEIGHT; i++ )
5964             initialPosition[i][j] = s;
5965
5966         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5967         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5968         initialPosition[pawnRow][j] = WhitePawn;
5969         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5970         if(gameInfo.variant == VariantXiangqi) {
5971             if(j&1) {
5972                 initialPosition[pawnRow][j] =
5973                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5974                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5975                    initialPosition[2][j] = WhiteCannon;
5976                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5977                 }
5978             }
5979         }
5980         if(gameInfo.variant == VariantGrand) {
5981             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5982                initialPosition[0][j] = WhiteRook;
5983                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5984             }
5985         }
5986         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5987     }
5988     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5989
5990             j=BOARD_LEFT+1;
5991             initialPosition[1][j] = WhiteBishop;
5992             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5993             j=BOARD_RGHT-2;
5994             initialPosition[1][j] = WhiteRook;
5995             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5996     }
5997
5998     if( nrCastlingRights == -1) {
5999         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6000         /*       This sets default castling rights from none to normal corners   */
6001         /* Variants with other castling rights must set them themselves above    */
6002         nrCastlingRights = 6;
6003
6004         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6005         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6006         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6007         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6008         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6009         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6010      }
6011
6012      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6013      if(gameInfo.variant == VariantGreat) { // promotion commoners
6014         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6015         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6016         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6017         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6018      }
6019      if( gameInfo.variant == VariantSChess ) {
6020       initialPosition[1][0] = BlackMarshall;
6021       initialPosition[2][0] = BlackAngel;
6022       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6023       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6024       initialPosition[1][1] = initialPosition[2][1] = 
6025       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6026      }
6027   if (appData.debugMode) {
6028     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6029   }
6030     if(shuffleOpenings) {
6031         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6032         startedFromSetupPosition = TRUE;
6033     }
6034     if(startedFromPositionFile) {
6035       /* [HGM] loadPos: use PositionFile for every new game */
6036       CopyBoard(initialPosition, filePosition);
6037       for(i=0; i<nrCastlingRights; i++)
6038           initialRights[i] = filePosition[CASTLING][i];
6039       startedFromSetupPosition = TRUE;
6040     }
6041
6042     CopyBoard(boards[0], initialPosition);
6043
6044     if(oldx != gameInfo.boardWidth ||
6045        oldy != gameInfo.boardHeight ||
6046        oldv != gameInfo.variant ||
6047        oldh != gameInfo.holdingsWidth
6048                                          )
6049             InitDrawingSizes(-2 ,0);
6050
6051     oldv = gameInfo.variant;
6052     if (redraw)
6053       DrawPosition(TRUE, boards[currentMove]);
6054 }
6055
6056 void
6057 SendBoard (ChessProgramState *cps, int moveNum)
6058 {
6059     char message[MSG_SIZ];
6060
6061     if (cps->useSetboard) {
6062       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6063       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6064       SendToProgram(message, cps);
6065       free(fen);
6066
6067     } else {
6068       ChessSquare *bp;
6069       int i, j, left=0, right=BOARD_WIDTH;
6070       /* Kludge to set black to move, avoiding the troublesome and now
6071        * deprecated "black" command.
6072        */
6073       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6074         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6075
6076       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6077
6078       SendToProgram("edit\n", cps);
6079       SendToProgram("#\n", cps);
6080       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6081         bp = &boards[moveNum][i][left];
6082         for (j = left; j < right; j++, bp++) {
6083           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6084           if ((int) *bp < (int) BlackPawn) {
6085             if(j == BOARD_RGHT+1)
6086                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6087             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6088             if(message[0] == '+' || message[0] == '~') {
6089               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6090                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6091                         AAA + j, ONE + i);
6092             }
6093             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6094                 message[1] = BOARD_RGHT   - 1 - j + '1';
6095                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6096             }
6097             SendToProgram(message, cps);
6098           }
6099         }
6100       }
6101
6102       SendToProgram("c\n", cps);
6103       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6104         bp = &boards[moveNum][i][left];
6105         for (j = left; j < right; j++, bp++) {
6106           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6107           if (((int) *bp != (int) EmptySquare)
6108               && ((int) *bp >= (int) BlackPawn)) {
6109             if(j == BOARD_LEFT-2)
6110                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6111             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6112                     AAA + j, ONE + i);
6113             if(message[0] == '+' || message[0] == '~') {
6114               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6115                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6116                         AAA + j, ONE + i);
6117             }
6118             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6119                 message[1] = BOARD_RGHT   - 1 - j + '1';
6120                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6121             }
6122             SendToProgram(message, cps);
6123           }
6124         }
6125       }
6126
6127       SendToProgram(".\n", cps);
6128     }
6129     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6130 }
6131
6132 char exclusionHeader[MSG_SIZ];
6133 int exCnt, excludePtr;
6134 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6135 static Exclusion excluTab[200];
6136 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6137
6138 static void
6139 WriteMap (int s)
6140 {
6141     int j;
6142     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6143     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6144 }
6145
6146 static void
6147 ClearMap ()
6148 {
6149     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6150     excludePtr = 24; exCnt = 0;
6151     WriteMap(0);
6152 }
6153
6154 static void
6155 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6156 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6157     char buf[2*MOVE_LEN], *p;
6158     Exclusion *e = excluTab;
6159     int i;
6160     for(i=0; i<exCnt; i++)
6161         if(e[i].ff == fromX && e[i].fr == fromY &&
6162            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6163     if(i == exCnt) { // was not in exclude list; add it
6164         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6165         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6166             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6167             return; // abort
6168         }
6169         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6170         excludePtr++; e[i].mark = excludePtr++;
6171         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6172         exCnt++;
6173     }
6174     exclusionHeader[e[i].mark] = state;
6175 }
6176
6177 static int
6178 ExcludeOneMove (int fromY, int fromX, int toY, int toX, signed char promoChar, char state)
6179 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6180     char buf[MSG_SIZ];
6181     int j, k;
6182     ChessMove moveType;
6183     if(promoChar == -1) { // kludge to indicate best move
6184         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6185             return 1; // if unparsable, abort
6186     }
6187     // update exclusion map (resolving toggle by consulting existing state)
6188     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6189     j = k%8; k >>= 3;
6190     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6191     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6192          excludeMap[k] |=   1<<j;
6193     else excludeMap[k] &= ~(1<<j);
6194     // update header
6195     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6196     // inform engine
6197     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6198     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6199     SendToProgram(buf, &first);
6200     return (state == '+');
6201 }
6202
6203 static void
6204 ExcludeClick (int index)
6205 {
6206     int i, j;
6207     Exclusion *e = excluTab;
6208     if(index < 25) { // none, best or tail clicked
6209         if(index < 13) { // none: include all
6210             WriteMap(0); // clear map
6211             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6212             SendToProgram("include all\n", &first); // and inform engine
6213         } else if(index > 18) { // tail
6214             if(exclusionHeader[19] == '-') { // tail was excluded
6215                 SendToProgram("include all\n", &first);
6216                 WriteMap(0); // clear map completely
6217                 // now re-exclude selected moves
6218                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6219                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6220             } else { // tail was included or in mixed state
6221                 SendToProgram("exclude all\n", &first);
6222                 WriteMap(0xFF); // fill map completely
6223                 // now re-include selected moves
6224                 j = 0; // count them
6225                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6226                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6227                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6228             }
6229         } else { // best
6230             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6231         }
6232     } else {
6233         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6234             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6235             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6236             break;
6237         }
6238     }
6239 }
6240
6241 ChessSquare
6242 DefaultPromoChoice (int white)
6243 {
6244     ChessSquare result;
6245     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6246         result = WhiteFerz; // no choice
6247     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6248         result= WhiteKing; // in Suicide Q is the last thing we want
6249     else if(gameInfo.variant == VariantSpartan)
6250         result = white ? WhiteQueen : WhiteAngel;
6251     else result = WhiteQueen;
6252     if(!white) result = WHITE_TO_BLACK result;
6253     return result;
6254 }
6255
6256 static int autoQueen; // [HGM] oneclick
6257
6258 int
6259 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6260 {
6261     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6262     /* [HGM] add Shogi promotions */
6263     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6264     ChessSquare piece;
6265     ChessMove moveType;
6266     Boolean premove;
6267
6268     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6269     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6270
6271     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6272       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6273         return FALSE;
6274
6275     piece = boards[currentMove][fromY][fromX];
6276     if(gameInfo.variant == VariantShogi) {
6277         promotionZoneSize = BOARD_HEIGHT/3;
6278         highestPromotingPiece = (int)WhiteFerz;
6279     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6280         promotionZoneSize = 3;
6281     }
6282
6283     // Treat Lance as Pawn when it is not representing Amazon
6284     if(gameInfo.variant != VariantSuper) {
6285         if(piece == WhiteLance) piece = WhitePawn; else
6286         if(piece == BlackLance) piece = BlackPawn;
6287     }
6288
6289     // next weed out all moves that do not touch the promotion zone at all
6290     if((int)piece >= BlackPawn) {
6291         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6292              return FALSE;
6293         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6294     } else {
6295         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6296            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6297     }
6298
6299     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6300
6301     // weed out mandatory Shogi promotions
6302     if(gameInfo.variant == VariantShogi) {
6303         if(piece >= BlackPawn) {
6304             if(toY == 0 && piece == BlackPawn ||
6305                toY == 0 && piece == BlackQueen ||
6306                toY <= 1 && piece == BlackKnight) {
6307                 *promoChoice = '+';
6308                 return FALSE;
6309             }
6310         } else {
6311             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6312                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6313                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6314                 *promoChoice = '+';
6315                 return FALSE;
6316             }
6317         }
6318     }
6319
6320     // weed out obviously illegal Pawn moves
6321     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6322         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6323         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6324         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6325         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6326         // note we are not allowed to test for valid (non-)capture, due to premove
6327     }
6328
6329     // we either have a choice what to promote to, or (in Shogi) whether to promote
6330     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6331         *promoChoice = PieceToChar(BlackFerz);  // no choice
6332         return FALSE;
6333     }
6334     // no sense asking what we must promote to if it is going to explode...
6335     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6336         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6337         return FALSE;
6338     }
6339     // give caller the default choice even if we will not make it
6340     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6341     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6342     if(        sweepSelect && gameInfo.variant != VariantGreat
6343                            && gameInfo.variant != VariantGrand
6344                            && gameInfo.variant != VariantSuper) return FALSE;
6345     if(autoQueen) return FALSE; // predetermined
6346
6347     // suppress promotion popup on illegal moves that are not premoves
6348     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6349               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6350     if(appData.testLegality && !premove) {
6351         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6352                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6353         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6354             return FALSE;
6355     }
6356
6357     return TRUE;
6358 }
6359
6360 int
6361 InPalace (int row, int column)
6362 {   /* [HGM] for Xiangqi */
6363     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6364          column < (BOARD_WIDTH + 4)/2 &&
6365          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6366     return FALSE;
6367 }
6368
6369 int
6370 PieceForSquare (int x, int y)
6371 {
6372   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6373      return -1;
6374   else
6375      return boards[currentMove][y][x];
6376 }
6377
6378 int
6379 OKToStartUserMove (int x, int y)
6380 {
6381     ChessSquare from_piece;
6382     int white_piece;
6383
6384     if (matchMode) return FALSE;
6385     if (gameMode == EditPosition) return TRUE;
6386
6387     if (x >= 0 && y >= 0)
6388       from_piece = boards[currentMove][y][x];
6389     else
6390       from_piece = EmptySquare;
6391
6392     if (from_piece == EmptySquare) return FALSE;
6393
6394     white_piece = (int)from_piece >= (int)WhitePawn &&
6395       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6396
6397     switch (gameMode) {
6398       case AnalyzeFile:
6399       case TwoMachinesPlay:
6400       case EndOfGame:
6401         return FALSE;
6402
6403       case IcsObserving:
6404       case IcsIdle:
6405         return FALSE;
6406
6407       case MachinePlaysWhite:
6408       case IcsPlayingBlack:
6409         if (appData.zippyPlay) return FALSE;
6410         if (white_piece) {
6411             DisplayMoveError(_("You are playing Black"));
6412             return FALSE;
6413         }
6414         break;
6415
6416       case MachinePlaysBlack:
6417       case IcsPlayingWhite:
6418         if (appData.zippyPlay) return FALSE;
6419         if (!white_piece) {
6420             DisplayMoveError(_("You are playing White"));
6421             return FALSE;
6422         }
6423         break;
6424
6425       case PlayFromGameFile:
6426             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6427       case EditGame:
6428         if (!white_piece && WhiteOnMove(currentMove)) {
6429             DisplayMoveError(_("It is White's turn"));
6430             return FALSE;
6431         }
6432         if (white_piece && !WhiteOnMove(currentMove)) {
6433             DisplayMoveError(_("It is Black's turn"));
6434             return FALSE;
6435         }
6436         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6437             /* Editing correspondence game history */
6438             /* Could disallow this or prompt for confirmation */
6439             cmailOldMove = -1;
6440         }
6441         break;
6442
6443       case BeginningOfGame:
6444         if (appData.icsActive) return FALSE;
6445         if (!appData.noChessProgram) {
6446             if (!white_piece) {
6447                 DisplayMoveError(_("You are playing White"));
6448                 return FALSE;
6449             }
6450         }
6451         break;
6452
6453       case Training:
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         break;
6463
6464       default:
6465       case IcsExamining:
6466         break;
6467     }
6468     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6469         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6470         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6471         && gameMode != AnalyzeFile && gameMode != Training) {
6472         DisplayMoveError(_("Displayed position is not current"));
6473         return FALSE;
6474     }
6475     return TRUE;
6476 }
6477
6478 Boolean
6479 OnlyMove (int *x, int *y, Boolean captures) 
6480 {
6481     DisambiguateClosure cl;
6482     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6483     switch(gameMode) {
6484       case MachinePlaysBlack:
6485       case IcsPlayingWhite:
6486       case BeginningOfGame:
6487         if(!WhiteOnMove(currentMove)) return FALSE;
6488         break;
6489       case MachinePlaysWhite:
6490       case IcsPlayingBlack:
6491         if(WhiteOnMove(currentMove)) return FALSE;
6492         break;
6493       case EditGame:
6494         break;
6495       default:
6496         return FALSE;
6497     }
6498     cl.pieceIn = EmptySquare;
6499     cl.rfIn = *y;
6500     cl.ffIn = *x;
6501     cl.rtIn = -1;
6502     cl.ftIn = -1;
6503     cl.promoCharIn = NULLCHAR;
6504     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6505     if( cl.kind == NormalMove ||
6506         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6507         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6508         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6509       fromX = cl.ff;
6510       fromY = cl.rf;
6511       *x = cl.ft;
6512       *y = cl.rt;
6513       return TRUE;
6514     }
6515     if(cl.kind != ImpossibleMove) return FALSE;
6516     cl.pieceIn = EmptySquare;
6517     cl.rfIn = -1;
6518     cl.ffIn = -1;
6519     cl.rtIn = *y;
6520     cl.ftIn = *x;
6521     cl.promoCharIn = NULLCHAR;
6522     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6523     if( cl.kind == NormalMove ||
6524         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6525         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6526         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6527       fromX = cl.ff;
6528       fromY = cl.rf;
6529       *x = cl.ft;
6530       *y = cl.rt;
6531       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6532       return TRUE;
6533     }
6534     return FALSE;
6535 }
6536
6537 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6538 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6539 int lastLoadGameUseList = FALSE;
6540 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6541 ChessMove lastLoadGameStart = EndOfFile;
6542 int doubleClick;
6543
6544 void
6545 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6546 {
6547     ChessMove moveType;
6548     ChessSquare pdown, pup;
6549     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6550
6551
6552     /* Check if the user is playing in turn.  This is complicated because we
6553        let the user "pick up" a piece before it is his turn.  So the piece he
6554        tried to pick up may have been captured by the time he puts it down!
6555        Therefore we use the color the user is supposed to be playing in this
6556        test, not the color of the piece that is currently on the starting
6557        square---except in EditGame mode, where the user is playing both
6558        sides; fortunately there the capture race can't happen.  (It can
6559        now happen in IcsExamining mode, but that's just too bad.  The user
6560        will get a somewhat confusing message in that case.)
6561        */
6562
6563     switch (gameMode) {
6564       case AnalyzeFile:
6565       case TwoMachinesPlay:
6566       case EndOfGame:
6567       case IcsObserving:
6568       case IcsIdle:
6569         /* We switched into a game mode where moves are not accepted,
6570            perhaps while the mouse button was down. */
6571         return;
6572
6573       case MachinePlaysWhite:
6574         /* User is moving for Black */
6575         if (WhiteOnMove(currentMove)) {
6576             DisplayMoveError(_("It is White's turn"));
6577             return;
6578         }
6579         break;
6580
6581       case MachinePlaysBlack:
6582         /* User is moving for White */
6583         if (!WhiteOnMove(currentMove)) {
6584             DisplayMoveError(_("It is Black's turn"));
6585             return;
6586         }
6587         break;
6588
6589       case PlayFromGameFile:
6590             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6591       case EditGame:
6592       case IcsExamining:
6593       case BeginningOfGame:
6594       case AnalyzeMode:
6595       case Training:
6596         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6597         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6598             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6599             /* User is moving for Black */
6600             if (WhiteOnMove(currentMove)) {
6601                 DisplayMoveError(_("It is White's turn"));
6602                 return;
6603             }
6604         } else {
6605             /* User is moving for White */
6606             if (!WhiteOnMove(currentMove)) {
6607                 DisplayMoveError(_("It is Black's turn"));
6608                 return;
6609             }
6610         }
6611         break;
6612
6613       case IcsPlayingBlack:
6614         /* User is moving for Black */
6615         if (WhiteOnMove(currentMove)) {
6616             if (!appData.premove) {
6617                 DisplayMoveError(_("It is White's turn"));
6618             } else if (toX >= 0 && toY >= 0) {
6619                 premoveToX = toX;
6620                 premoveToY = toY;
6621                 premoveFromX = fromX;
6622                 premoveFromY = fromY;
6623                 premovePromoChar = promoChar;
6624                 gotPremove = 1;
6625                 if (appData.debugMode)
6626                     fprintf(debugFP, "Got premove: fromX %d,"
6627                             "fromY %d, toX %d, toY %d\n",
6628                             fromX, fromY, toX, toY);
6629             }
6630             return;
6631         }
6632         break;
6633
6634       case IcsPlayingWhite:
6635         /* User is moving for White */
6636         if (!WhiteOnMove(currentMove)) {
6637             if (!appData.premove) {
6638                 DisplayMoveError(_("It is Black's turn"));
6639             } else if (toX >= 0 && toY >= 0) {
6640                 premoveToX = toX;
6641                 premoveToY = toY;
6642                 premoveFromX = fromX;
6643                 premoveFromY = fromY;
6644                 premovePromoChar = promoChar;
6645                 gotPremove = 1;
6646                 if (appData.debugMode)
6647                     fprintf(debugFP, "Got premove: fromX %d,"
6648                             "fromY %d, toX %d, toY %d\n",
6649                             fromX, fromY, toX, toY);
6650             }
6651             return;
6652         }
6653         break;
6654
6655       default:
6656         break;
6657
6658       case EditPosition:
6659         /* EditPosition, empty square, or different color piece;
6660            click-click move is possible */
6661         if (toX == -2 || toY == -2) {
6662             boards[0][fromY][fromX] = EmptySquare;
6663             DrawPosition(FALSE, boards[currentMove]);
6664             return;
6665         } else if (toX >= 0 && toY >= 0) {
6666             boards[0][toY][toX] = boards[0][fromY][fromX];
6667             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6668                 if(boards[0][fromY][0] != EmptySquare) {
6669                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6670                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6671                 }
6672             } else
6673             if(fromX == BOARD_RGHT+1) {
6674                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6675                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6676                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6677                 }
6678             } else
6679             boards[0][fromY][fromX] = gatingPiece;
6680             DrawPosition(FALSE, boards[currentMove]);
6681             return;
6682         }
6683         return;
6684     }
6685
6686     if(toX < 0 || toY < 0) return;
6687     pdown = boards[currentMove][fromY][fromX];
6688     pup = boards[currentMove][toY][toX];
6689
6690     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6691     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6692          if( pup != EmptySquare ) return;
6693          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6694            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6695                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6696            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6697            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6698            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6699            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6700          fromY = DROP_RANK;
6701     }
6702
6703     /* [HGM] always test for legality, to get promotion info */
6704     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6705                                          fromY, fromX, toY, toX, promoChar);
6706
6707     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6708
6709     /* [HGM] but possibly ignore an IllegalMove result */
6710     if (appData.testLegality) {
6711         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6712             DisplayMoveError(_("Illegal move"));
6713             return;
6714         }
6715     }
6716
6717     if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6718         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6719              ClearPremoveHighlights(); // was included
6720         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6721         return;
6722     }
6723
6724     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6725 }
6726
6727 /* Common tail of UserMoveEvent and DropMenuEvent */
6728 int
6729 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6730 {
6731     char *bookHit = 0;
6732
6733     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6734         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6735         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6736         if(WhiteOnMove(currentMove)) {
6737             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6738         } else {
6739             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6740         }
6741     }
6742
6743     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6744        move type in caller when we know the move is a legal promotion */
6745     if(moveType == NormalMove && promoChar)
6746         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6747
6748     /* [HGM] <popupFix> The following if has been moved here from
6749        UserMoveEvent(). Because it seemed to belong here (why not allow
6750        piece drops in training games?), and because it can only be
6751        performed after it is known to what we promote. */
6752     if (gameMode == Training) {
6753       /* compare the move played on the board to the next move in the
6754        * game. If they match, display the move and the opponent's response.
6755        * If they don't match, display an error message.
6756        */
6757       int saveAnimate;
6758       Board testBoard;
6759       CopyBoard(testBoard, boards[currentMove]);
6760       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6761
6762       if (CompareBoards(testBoard, boards[currentMove+1])) {
6763         ForwardInner(currentMove+1);
6764
6765         /* Autoplay the opponent's response.
6766          * if appData.animate was TRUE when Training mode was entered,
6767          * the response will be animated.
6768          */
6769         saveAnimate = appData.animate;
6770         appData.animate = animateTraining;
6771         ForwardInner(currentMove+1);
6772         appData.animate = saveAnimate;
6773
6774         /* check for the end of the game */
6775         if (currentMove >= forwardMostMove) {
6776           gameMode = PlayFromGameFile;
6777           ModeHighlight();
6778           SetTrainingModeOff();
6779           DisplayInformation(_("End of game"));
6780         }
6781       } else {
6782         DisplayError(_("Incorrect move"), 0);
6783       }
6784       return 1;
6785     }
6786
6787   /* Ok, now we know that the move is good, so we can kill
6788      the previous line in Analysis Mode */
6789   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6790                                 && currentMove < forwardMostMove) {
6791     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6792     else forwardMostMove = currentMove;
6793   }
6794
6795   ClearMap();
6796
6797   /* If we need the chess program but it's dead, restart it */
6798   ResurrectChessProgram();
6799
6800   /* A user move restarts a paused game*/
6801   if (pausing)
6802     PauseEvent();
6803
6804   thinkOutput[0] = NULLCHAR;
6805
6806   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6807
6808   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6809     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6810     return 1;
6811   }
6812
6813   if (gameMode == BeginningOfGame) {
6814     if (appData.noChessProgram) {
6815       gameMode = EditGame;
6816       SetGameInfo();
6817     } else {
6818       char buf[MSG_SIZ];
6819       gameMode = MachinePlaysBlack;
6820       StartClocks();
6821       SetGameInfo();
6822       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6823       DisplayTitle(buf);
6824       if (first.sendName) {
6825         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6826         SendToProgram(buf, &first);
6827       }
6828       StartClocks();
6829     }
6830     ModeHighlight();
6831   }
6832
6833   /* Relay move to ICS or chess engine */
6834   if (appData.icsActive) {
6835     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6836         gameMode == IcsExamining) {
6837       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6838         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6839         SendToICS("draw ");
6840         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6841       }
6842       // also send plain move, in case ICS does not understand atomic claims
6843       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6844       ics_user_moved = 1;
6845     }
6846   } else {
6847     if (first.sendTime && (gameMode == BeginningOfGame ||
6848                            gameMode == MachinePlaysWhite ||
6849                            gameMode == MachinePlaysBlack)) {
6850       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6851     }
6852     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6853          // [HGM] book: if program might be playing, let it use book
6854         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6855         first.maybeThinking = TRUE;
6856     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6857         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6858         SendBoard(&first, currentMove+1);
6859     } else SendMoveToProgram(forwardMostMove-1, &first);
6860     if (currentMove == cmailOldMove + 1) {
6861       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6862     }
6863   }
6864
6865   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6866
6867   switch (gameMode) {
6868   case EditGame:
6869     if(appData.testLegality)
6870     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6871     case MT_NONE:
6872     case MT_CHECK:
6873       break;
6874     case MT_CHECKMATE:
6875     case MT_STAINMATE:
6876       if (WhiteOnMove(currentMove)) {
6877         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6878       } else {
6879         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6880       }
6881       break;
6882     case MT_STALEMATE:
6883       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6884       break;
6885     }
6886     break;
6887
6888   case MachinePlaysBlack:
6889   case MachinePlaysWhite:
6890     /* disable certain menu options while machine is thinking */
6891     SetMachineThinkingEnables();
6892     break;
6893
6894   default:
6895     break;
6896   }
6897
6898   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6899   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6900
6901   if(bookHit) { // [HGM] book: simulate book reply
6902         static char bookMove[MSG_SIZ]; // a bit generous?
6903
6904         programStats.nodes = programStats.depth = programStats.time =
6905         programStats.score = programStats.got_only_move = 0;
6906         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6907
6908         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6909         strcat(bookMove, bookHit);
6910         HandleMachineMove(bookMove, &first);
6911   }
6912   return 1;
6913 }
6914
6915 void
6916 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6917 {
6918     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6919     Markers *m = (Markers *) closure;
6920     if(rf == fromY && ff == fromX)
6921         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6922                          || kind == WhiteCapturesEnPassant
6923                          || kind == BlackCapturesEnPassant);
6924     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6925 }
6926
6927 void
6928 MarkTargetSquares (int clear)
6929 {
6930   int x, y;
6931   if(clear) // no reason to ever suppress clearing
6932     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6933   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6934      !appData.testLegality || gameMode == EditPosition) return;
6935   if(!clear) {
6936     int capt = 0;
6937     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6938     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6939       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6940       if(capt)
6941       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6942     }
6943   }
6944   DrawPosition(FALSE, NULL);
6945 }
6946
6947 int
6948 Explode (Board board, int fromX, int fromY, int toX, int toY)
6949 {
6950     if(gameInfo.variant == VariantAtomic &&
6951        (board[toY][toX] != EmptySquare ||                     // capture?
6952         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6953                          board[fromY][fromX] == BlackPawn   )
6954       )) {
6955         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6956         return TRUE;
6957     }
6958     return FALSE;
6959 }
6960
6961 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6962
6963 int
6964 CanPromote (ChessSquare piece, int y)
6965 {
6966         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6967         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6968         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6969            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6970            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6971                                                   gameInfo.variant == VariantMakruk) return FALSE;
6972         return (piece == BlackPawn && y == 1 ||
6973                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6974                 piece == BlackLance && y == 1 ||
6975                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6976 }
6977
6978 void
6979 LeftClick (ClickType clickType, int xPix, int yPix)
6980 {
6981     int x, y;
6982     Boolean saveAnimate;
6983     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
6984     char promoChoice = NULLCHAR;
6985     ChessSquare piece;
6986     static TimeMark lastClickTime, prevClickTime;
6987
6988     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
6989
6990     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
6991
6992     if (clickType == Press) ErrorPopDown();
6993
6994     x = EventToSquare(xPix, BOARD_WIDTH);
6995     y = EventToSquare(yPix, BOARD_HEIGHT);
6996     if (!flipView && y >= 0) {
6997         y = BOARD_HEIGHT - 1 - y;
6998     }
6999     if (flipView && x >= 0) {
7000         x = BOARD_WIDTH - 1 - x;
7001     }
7002
7003     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7004         defaultPromoChoice = promoSweep;
7005         promoSweep = EmptySquare;   // terminate sweep
7006         promoDefaultAltered = TRUE;
7007         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7008     }
7009
7010     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7011         if(clickType == Release) return; // ignore upclick of click-click destination
7012         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7013         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7014         if(gameInfo.holdingsWidth &&
7015                 (WhiteOnMove(currentMove)
7016                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7017                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7018             // click in right holdings, for determining promotion piece
7019             ChessSquare p = boards[currentMove][y][x];
7020             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7021             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7022             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7023                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7024                 fromX = fromY = -1;
7025                 return;
7026             }
7027         }
7028         DrawPosition(FALSE, boards[currentMove]);
7029         return;
7030     }
7031
7032     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7033     if(clickType == Press
7034             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7035               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7036               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7037         return;
7038
7039     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7040         // could be static click on premove from-square: abort premove
7041         gotPremove = 0;
7042         ClearPremoveHighlights();
7043     }
7044
7045     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7046         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7047
7048     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7049         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7050                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7051         defaultPromoChoice = DefaultPromoChoice(side);
7052     }
7053
7054     autoQueen = appData.alwaysPromoteToQueen;
7055
7056     if (fromX == -1) {
7057       int originalY = y;
7058       gatingPiece = EmptySquare;
7059       if (clickType != Press) {
7060         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7061             DragPieceEnd(xPix, yPix); dragging = 0;
7062             DrawPosition(FALSE, NULL);
7063         }
7064         return;
7065       }
7066       doubleClick = FALSE;
7067       fromX = x; fromY = y; toX = toY = -1;
7068       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7069          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7070          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7071             /* First square */
7072             if (OKToStartUserMove(fromX, fromY)) {
7073                 second = 0;
7074                 MarkTargetSquares(0);
7075                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7076                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7077                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7078                     promoSweep = defaultPromoChoice;
7079                     selectFlag = 0; lastX = xPix; lastY = yPix;
7080                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7081                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7082                 }
7083                 if (appData.highlightDragging) {
7084                     SetHighlights(fromX, fromY, -1, -1);
7085                 } else {
7086                     ClearHighlights();
7087                 }
7088             } else fromX = fromY = -1;
7089             return;
7090         }
7091     }
7092
7093     /* fromX != -1 */
7094     if (clickType == Press && gameMode != EditPosition) {
7095         ChessSquare fromP;
7096         ChessSquare toP;
7097         int frc;
7098
7099         // ignore off-board to clicks
7100         if(y < 0 || x < 0) return;
7101
7102         /* Check if clicking again on the same color piece */
7103         fromP = boards[currentMove][fromY][fromX];
7104         toP = boards[currentMove][y][x];
7105         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7106         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7107              WhitePawn <= toP && toP <= WhiteKing &&
7108              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7109              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7110             (BlackPawn <= fromP && fromP <= BlackKing &&
7111              BlackPawn <= toP && toP <= BlackKing &&
7112              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7113              !(fromP == BlackKing && toP == BlackRook && frc))) {
7114             /* Clicked again on same color piece -- changed his mind */
7115             second = (x == fromX && y == fromY);
7116             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7117                 second = FALSE; // first double-click rather than scond click
7118                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7119             }
7120             promoDefaultAltered = FALSE;
7121             MarkTargetSquares(1);
7122            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7123             if (appData.highlightDragging) {
7124                 SetHighlights(x, y, -1, -1);
7125             } else {
7126                 ClearHighlights();
7127             }
7128             if (OKToStartUserMove(x, y)) {
7129                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7130                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7131                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7132                  gatingPiece = boards[currentMove][fromY][fromX];
7133                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7134                 fromX = x;
7135                 fromY = y; dragging = 1;
7136                 MarkTargetSquares(0);
7137                 DragPieceBegin(xPix, yPix, FALSE);
7138                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7139                     promoSweep = defaultPromoChoice;
7140                     selectFlag = 0; lastX = xPix; lastY = yPix;
7141                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7142                 }
7143             }
7144            }
7145            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7146            second = FALSE; 
7147         }
7148         // ignore clicks on holdings
7149         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7150     }
7151
7152     if (clickType == Release && x == fromX && y == fromY) {
7153         DragPieceEnd(xPix, yPix); dragging = 0;
7154         if(clearFlag) {
7155             // a deferred attempt to click-click move an empty square on top of a piece
7156             boards[currentMove][y][x] = EmptySquare;
7157             ClearHighlights();
7158             DrawPosition(FALSE, boards[currentMove]);
7159             fromX = fromY = -1; clearFlag = 0;
7160             return;
7161         }
7162         if (appData.animateDragging) {
7163             /* Undo animation damage if any */
7164             DrawPosition(FALSE, NULL);
7165         }
7166         if (second || sweepSelecting) {
7167             /* Second up/down in same square; just abort move */
7168             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7169             second = sweepSelecting = 0;
7170             fromX = fromY = -1;
7171             gatingPiece = EmptySquare;
7172             ClearHighlights();
7173             gotPremove = 0;
7174             ClearPremoveHighlights();
7175         } else {
7176             /* First upclick in same square; start click-click mode */
7177             SetHighlights(x, y, -1, -1);
7178         }
7179         return;
7180     }
7181
7182     clearFlag = 0;
7183
7184     /* we now have a different from- and (possibly off-board) to-square */
7185     /* Completed move */
7186     if(!sweepSelecting) {
7187         toX = x;
7188         toY = y;
7189         saveAnimate = appData.animate;
7190     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7191
7192     if (clickType == Press) {
7193         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7194             // must be Edit Position mode with empty-square selected
7195             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7196             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7197             return;
7198         }
7199         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7200           if(appData.sweepSelect) {
7201             ChessSquare piece = boards[currentMove][fromY][fromX];
7202             promoSweep = defaultPromoChoice;
7203             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7204             selectFlag = 0; lastX = xPix; lastY = yPix;
7205             Sweep(0); // Pawn that is going to promote: preview promotion piece
7206             sweepSelecting = 1;
7207             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7208             MarkTargetSquares(1);
7209           }
7210           return; // promo popup appears on up-click
7211         }
7212         /* Finish clickclick move */
7213         if (appData.animate || appData.highlightLastMove) {
7214             SetHighlights(fromX, fromY, toX, toY);
7215         } else {
7216             ClearHighlights();
7217         }
7218     } else {
7219         /* Finish drag move */
7220         if (appData.highlightLastMove) {
7221             SetHighlights(fromX, fromY, toX, toY);
7222         } else {
7223             ClearHighlights();
7224         }
7225         DragPieceEnd(xPix, yPix); dragging = 0;
7226         /* Don't animate move and drag both */
7227         appData.animate = FALSE;
7228     }
7229     MarkTargetSquares(1);
7230
7231     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7232     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7233         ChessSquare piece = boards[currentMove][fromY][fromX];
7234         if(gameMode == EditPosition && piece != EmptySquare &&
7235            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7236             int n;
7237
7238             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7239                 n = PieceToNumber(piece - (int)BlackPawn);
7240                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7241                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7242                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7243             } else
7244             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7245                 n = PieceToNumber(piece);
7246                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7247                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7248                 boards[currentMove][n][BOARD_WIDTH-2]++;
7249             }
7250             boards[currentMove][fromY][fromX] = EmptySquare;
7251         }
7252         ClearHighlights();
7253         fromX = fromY = -1;
7254         DrawPosition(TRUE, boards[currentMove]);
7255         return;
7256     }
7257
7258     // off-board moves should not be highlighted
7259     if(x < 0 || y < 0) ClearHighlights();
7260
7261     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7262
7263     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7264         SetHighlights(fromX, fromY, toX, toY);
7265         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7266             // [HGM] super: promotion to captured piece selected from holdings
7267             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7268             promotionChoice = TRUE;
7269             // kludge follows to temporarily execute move on display, without promoting yet
7270             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7271             boards[currentMove][toY][toX] = p;
7272             DrawPosition(FALSE, boards[currentMove]);
7273             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7274             boards[currentMove][toY][toX] = q;
7275             DisplayMessage("Click in holdings to choose piece", "");
7276             return;
7277         }
7278         PromotionPopUp();
7279     } else {
7280         int oldMove = currentMove;
7281         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7282         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7283         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7284         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7285            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7286             DrawPosition(TRUE, boards[currentMove]);
7287         fromX = fromY = -1;
7288     }
7289     appData.animate = saveAnimate;
7290     if (appData.animate || appData.animateDragging) {
7291         /* Undo animation damage if needed */
7292         DrawPosition(FALSE, NULL);
7293     }
7294 }
7295
7296 int
7297 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7298 {   // front-end-free part taken out of PieceMenuPopup
7299     int whichMenu; int xSqr, ySqr;
7300
7301     if(seekGraphUp) { // [HGM] seekgraph
7302         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7303         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7304         return -2;
7305     }
7306
7307     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7308          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7309         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7310         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7311         if(action == Press)   {
7312             originalFlip = flipView;
7313             flipView = !flipView; // temporarily flip board to see game from partners perspective
7314             DrawPosition(TRUE, partnerBoard);
7315             DisplayMessage(partnerStatus, "");
7316             partnerUp = TRUE;
7317         } else if(action == Release) {
7318             flipView = originalFlip;
7319             DrawPosition(TRUE, boards[currentMove]);
7320             partnerUp = FALSE;
7321         }
7322         return -2;
7323     }
7324
7325     xSqr = EventToSquare(x, BOARD_WIDTH);
7326     ySqr = EventToSquare(y, BOARD_HEIGHT);
7327     if (action == Release) {
7328         if(pieceSweep != EmptySquare) {
7329             EditPositionMenuEvent(pieceSweep, toX, toY);
7330             pieceSweep = EmptySquare;
7331         } else UnLoadPV(); // [HGM] pv
7332     }
7333     if (action != Press) return -2; // return code to be ignored
7334     switch (gameMode) {
7335       case IcsExamining:
7336         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7337       case EditPosition:
7338         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7339         if (xSqr < 0 || ySqr < 0) return -1;
7340         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7341         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7342         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7343         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7344         NextPiece(0);
7345         return 2; // grab
7346       case IcsObserving:
7347         if(!appData.icsEngineAnalyze) return -1;
7348       case IcsPlayingWhite:
7349       case IcsPlayingBlack:
7350         if(!appData.zippyPlay) goto noZip;
7351       case AnalyzeMode:
7352       case AnalyzeFile:
7353       case MachinePlaysWhite:
7354       case MachinePlaysBlack:
7355       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7356         if (!appData.dropMenu) {
7357           LoadPV(x, y);
7358           return 2; // flag front-end to grab mouse events
7359         }
7360         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7361            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7362       case EditGame:
7363       noZip:
7364         if (xSqr < 0 || ySqr < 0) return -1;
7365         if (!appData.dropMenu || appData.testLegality &&
7366             gameInfo.variant != VariantBughouse &&
7367             gameInfo.variant != VariantCrazyhouse) return -1;
7368         whichMenu = 1; // drop menu
7369         break;
7370       default:
7371         return -1;
7372     }
7373
7374     if (((*fromX = xSqr) < 0) ||
7375         ((*fromY = ySqr) < 0)) {
7376         *fromX = *fromY = -1;
7377         return -1;
7378     }
7379     if (flipView)
7380       *fromX = BOARD_WIDTH - 1 - *fromX;
7381     else
7382       *fromY = BOARD_HEIGHT - 1 - *fromY;
7383
7384     return whichMenu;
7385 }
7386
7387 void
7388 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7389 {
7390 //    char * hint = lastHint;
7391     FrontEndProgramStats stats;
7392
7393     stats.which = cps == &first ? 0 : 1;
7394     stats.depth = cpstats->depth;
7395     stats.nodes = cpstats->nodes;
7396     stats.score = cpstats->score;
7397     stats.time = cpstats->time;
7398     stats.pv = cpstats->movelist;
7399     stats.hint = lastHint;
7400     stats.an_move_index = 0;
7401     stats.an_move_count = 0;
7402
7403     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7404         stats.hint = cpstats->move_name;
7405         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7406         stats.an_move_count = cpstats->nr_moves;
7407     }
7408
7409     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
7410
7411     SetProgramStats( &stats );
7412 }
7413
7414 void
7415 ClearEngineOutputPane (int which)
7416 {
7417     static FrontEndProgramStats dummyStats;
7418     dummyStats.which = which;
7419     dummyStats.pv = "#";
7420     SetProgramStats( &dummyStats );
7421 }
7422
7423 #define MAXPLAYERS 500
7424
7425 char *
7426 TourneyStandings (int display)
7427 {
7428     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7429     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7430     char result, *p, *names[MAXPLAYERS];
7431
7432     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7433         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7434     names[0] = p = strdup(appData.participants);
7435     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7436
7437     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7438
7439     while(result = appData.results[nr]) {
7440         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7441         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7442         wScore = bScore = 0;
7443         switch(result) {
7444           case '+': wScore = 2; break;
7445           case '-': bScore = 2; break;
7446           case '=': wScore = bScore = 1; break;
7447           case ' ':
7448           case '*': return strdup("busy"); // tourney not finished
7449         }
7450         score[w] += wScore;
7451         score[b] += bScore;
7452         games[w]++;
7453         games[b]++;
7454         nr++;
7455     }
7456     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7457     for(w=0; w<nPlayers; w++) {
7458         bScore = -1;
7459         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7460         ranking[w] = b; points[w] = bScore; score[b] = -2;
7461     }
7462     p = malloc(nPlayers*34+1);
7463     for(w=0; w<nPlayers && w<display; w++)
7464         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7465     free(names[0]);
7466     return p;
7467 }
7468
7469 void
7470 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7471 {       // count all piece types
7472         int p, f, r;
7473         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7474         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7475         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7476                 p = board[r][f];
7477                 pCnt[p]++;
7478                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7479                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7480                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7481                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7482                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7483                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7484         }
7485 }
7486
7487 int
7488 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7489 {
7490         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7491         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7492
7493         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7494         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7495         if(myPawns == 2 && nMine == 3) // KPP
7496             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7497         if(myPawns == 1 && nMine == 2) // KP
7498             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7499         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7500             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7501         if(myPawns) return FALSE;
7502         if(pCnt[WhiteRook+side])
7503             return pCnt[BlackRook-side] ||
7504                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7505                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7506                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7507         if(pCnt[WhiteCannon+side]) {
7508             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7509             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7510         }
7511         if(pCnt[WhiteKnight+side])
7512             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7513         return FALSE;
7514 }
7515
7516 int
7517 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7518 {
7519         VariantClass v = gameInfo.variant;
7520
7521         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7522         if(v == VariantShatranj) return TRUE; // always winnable through baring
7523         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7524         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7525
7526         if(v == VariantXiangqi) {
7527                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7528
7529                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7530                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7531                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7532                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7533                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7534                 if(stale) // we have at least one last-rank P plus perhaps C
7535                     return majors // KPKX
7536                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7537                 else // KCA*E*
7538                     return pCnt[WhiteFerz+side] // KCAK
7539                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7540                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7541                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7542
7543         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7544                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7545
7546                 if(nMine == 1) return FALSE; // bare King
7547                 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
7548                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7549                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7550                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7551                 if(pCnt[WhiteKnight+side])
7552                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7553                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7554                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7555                 if(nBishops)
7556                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7557                 if(pCnt[WhiteAlfil+side])
7558                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7559                 if(pCnt[WhiteWazir+side])
7560                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7561         }
7562
7563         return TRUE;
7564 }
7565
7566 int
7567 CompareWithRights (Board b1, Board b2)
7568 {
7569     int rights = 0;
7570     if(!CompareBoards(b1, b2)) return FALSE;
7571     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7572     /* compare castling rights */
7573     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7574            rights++; /* King lost rights, while rook still had them */
7575     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7576         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7577            rights++; /* but at least one rook lost them */
7578     }
7579     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7580            rights++;
7581     if( b1[CASTLING][5] != NoRights ) {
7582         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7583            rights++;
7584     }
7585     return rights == 0;
7586 }
7587
7588 int
7589 Adjudicate (ChessProgramState *cps)
7590 {       // [HGM] some adjudications useful with buggy engines
7591         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7592         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7593         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7594         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7595         int k, count = 0; static int bare = 1;
7596         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7597         Boolean canAdjudicate = !appData.icsActive;
7598
7599         // most tests only when we understand the game, i.e. legality-checking on
7600             if( appData.testLegality )
7601             {   /* [HGM] Some more adjudications for obstinate engines */
7602                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7603                 static int moveCount = 6;
7604                 ChessMove result;
7605                 char *reason = NULL;
7606
7607                 /* Count what is on board. */
7608                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7609
7610                 /* Some material-based adjudications that have to be made before stalemate test */
7611                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7612                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7613                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7614                      if(canAdjudicate && appData.checkMates) {
7615                          if(engineOpponent)
7616                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7617                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7618                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7619                          return 1;
7620                      }
7621                 }
7622
7623                 /* Bare King in Shatranj (loses) or Losers (wins) */
7624                 if( nrW == 1 || nrB == 1) {
7625                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7626                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7627                      if(canAdjudicate && appData.checkMates) {
7628                          if(engineOpponent)
7629                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7630                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7631                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7632                          return 1;
7633                      }
7634                   } else
7635                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7636                   {    /* bare King */
7637                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7638                         if(canAdjudicate && appData.checkMates) {
7639                             /* but only adjudicate if adjudication enabled */
7640                             if(engineOpponent)
7641                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7642                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7643                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7644                             return 1;
7645                         }
7646                   }
7647                 } else bare = 1;
7648
7649
7650             // don't wait for engine to announce game end if we can judge ourselves
7651             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7652               case MT_CHECK:
7653                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7654                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7655                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7656                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7657                             checkCnt++;
7658                         if(checkCnt >= 2) {
7659                             reason = "Xboard adjudication: 3rd check";
7660                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7661                             break;
7662                         }
7663                     }
7664                 }
7665               case MT_NONE:
7666               default:
7667                 break;
7668               case MT_STALEMATE:
7669               case MT_STAINMATE:
7670                 reason = "Xboard adjudication: Stalemate";
7671                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7672                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7673                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7674                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7675                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7676                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7677                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7678                                                                         EP_CHECKMATE : EP_WINS);
7679                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7680                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7681                 }
7682                 break;
7683               case MT_CHECKMATE:
7684                 reason = "Xboard adjudication: Checkmate";
7685                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7686                 break;
7687             }
7688
7689                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7690                     case EP_STALEMATE:
7691                         result = GameIsDrawn; break;
7692                     case EP_CHECKMATE:
7693                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7694                     case EP_WINS:
7695                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7696                     default:
7697                         result = EndOfFile;
7698                 }
7699                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7700                     if(engineOpponent)
7701                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7702                     GameEnds( result, reason, GE_XBOARD );
7703                     return 1;
7704                 }
7705
7706                 /* Next absolutely insufficient mating material. */
7707                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7708                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7709                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7710
7711                      /* always flag draws, for judging claims */
7712                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7713
7714                      if(canAdjudicate && appData.materialDraws) {
7715                          /* but only adjudicate them if adjudication enabled */
7716                          if(engineOpponent) {
7717                            SendToProgram("force\n", engineOpponent); // suppress reply
7718                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7719                          }
7720                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7721                          return 1;
7722                      }
7723                 }
7724
7725                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7726                 if(gameInfo.variant == VariantXiangqi ?
7727                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7728                  : nrW + nrB == 4 &&
7729                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7730                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7731                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7732                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7733                    ) ) {
7734                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7735                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7736                           if(engineOpponent) {
7737                             SendToProgram("force\n", engineOpponent); // suppress reply
7738                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7739                           }
7740                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7741                           return 1;
7742                      }
7743                 } else moveCount = 6;
7744             }
7745
7746         // Repetition draws and 50-move rule can be applied independently of legality testing
7747
7748                 /* Check for rep-draws */
7749                 count = 0;
7750                 for(k = forwardMostMove-2;
7751                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7752                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7753                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7754                     k-=2)
7755                 {   int rights=0;
7756                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7757                         /* compare castling rights */
7758                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7759                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7760                                 rights++; /* King lost rights, while rook still had them */
7761                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7762                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7763                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7764                                    rights++; /* but at least one rook lost them */
7765                         }
7766                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7767                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7768                                 rights++;
7769                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7770                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7771                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7772                                    rights++;
7773                         }
7774                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7775                             && appData.drawRepeats > 1) {
7776                              /* adjudicate after user-specified nr of repeats */
7777                              int result = GameIsDrawn;
7778                              char *details = "XBoard adjudication: repetition draw";
7779                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7780                                 // [HGM] xiangqi: check for forbidden perpetuals
7781                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7782                                 for(m=forwardMostMove; m>k; m-=2) {
7783                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7784                                         ourPerpetual = 0; // the current mover did not always check
7785                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7786                                         hisPerpetual = 0; // the opponent did not always check
7787                                 }
7788                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7789                                                                         ourPerpetual, hisPerpetual);
7790                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7791                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7792                                     details = "Xboard adjudication: perpetual checking";
7793                                 } else
7794                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7795                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7796                                 } else
7797                                 // Now check for perpetual chases
7798                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7799                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7800                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7801                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7802                                         static char resdet[MSG_SIZ];
7803                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7804                                         details = resdet;
7805                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7806                                     } else
7807                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7808                                         break; // Abort repetition-checking loop.
7809                                 }
7810                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7811                              }
7812                              if(engineOpponent) {
7813                                SendToProgram("force\n", engineOpponent); // suppress reply
7814                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7815                              }
7816                              GameEnds( result, details, GE_XBOARD );
7817                              return 1;
7818                         }
7819                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7820                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7821                     }
7822                 }
7823
7824                 /* Now we test for 50-move draws. Determine ply count */
7825                 count = forwardMostMove;
7826                 /* look for last irreversble move */
7827                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7828                     count--;
7829                 /* if we hit starting position, add initial plies */
7830                 if( count == backwardMostMove )
7831                     count -= initialRulePlies;
7832                 count = forwardMostMove - count;
7833                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7834                         // adjust reversible move counter for checks in Xiangqi
7835                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7836                         if(i < backwardMostMove) i = backwardMostMove;
7837                         while(i <= forwardMostMove) {
7838                                 lastCheck = inCheck; // check evasion does not count
7839                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7840                                 if(inCheck || lastCheck) count--; // check does not count
7841                                 i++;
7842                         }
7843                 }
7844                 if( count >= 100)
7845                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7846                          /* this is used to judge if draw claims are legal */
7847                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7848                          if(engineOpponent) {
7849                            SendToProgram("force\n", engineOpponent); // suppress reply
7850                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7851                          }
7852                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7853                          return 1;
7854                 }
7855
7856                 /* if draw offer is pending, treat it as a draw claim
7857                  * when draw condition present, to allow engines a way to
7858                  * claim draws before making their move to avoid a race
7859                  * condition occurring after their move
7860                  */
7861                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7862                          char *p = NULL;
7863                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7864                              p = "Draw claim: 50-move rule";
7865                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7866                              p = "Draw claim: 3-fold repetition";
7867                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7868                              p = "Draw claim: insufficient mating material";
7869                          if( p != NULL && canAdjudicate) {
7870                              if(engineOpponent) {
7871                                SendToProgram("force\n", engineOpponent); // suppress reply
7872                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7873                              }
7874                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7875                              return 1;
7876                          }
7877                 }
7878
7879                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7880                     if(engineOpponent) {
7881                       SendToProgram("force\n", engineOpponent); // suppress reply
7882                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7883                     }
7884                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7885                     return 1;
7886                 }
7887         return 0;
7888 }
7889
7890 char *
7891 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7892 {   // [HGM] book: this routine intercepts moves to simulate book replies
7893     char *bookHit = NULL;
7894
7895     //first determine if the incoming move brings opponent into his book
7896     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7897         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7898     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7899     if(bookHit != NULL && !cps->bookSuspend) {
7900         // make sure opponent is not going to reply after receiving move to book position
7901         SendToProgram("force\n", cps);
7902         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7903     }
7904     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7905     // now arrange restart after book miss
7906     if(bookHit) {
7907         // after a book hit we never send 'go', and the code after the call to this routine
7908         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7909         char buf[MSG_SIZ], *move = bookHit;
7910         if(cps->useSAN) {
7911             int fromX, fromY, toX, toY;
7912             char promoChar;
7913             ChessMove moveType;
7914             move = buf + 30;
7915             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7916                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7917                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7918                                     PosFlags(forwardMostMove),
7919                                     fromY, fromX, toY, toX, promoChar, move);
7920             } else {
7921                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7922                 bookHit = NULL;
7923             }
7924         }
7925         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7926         SendToProgram(buf, cps);
7927         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7928     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7929         SendToProgram("go\n", cps);
7930         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7931     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7932         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7933             SendToProgram("go\n", cps);
7934         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7935     }
7936     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7937 }
7938
7939 int
7940 LoadError (char *errmess, ChessProgramState *cps)
7941 {   // unloads engine and switches back to -ncp mode if it was first
7942     if(cps->initDone) return FALSE;
7943     cps->isr = NULL; // this should suppress further error popups from breaking pipes
7944     DestroyChildProcess(cps->pr, 9 ); // just to be sure
7945     cps->pr = NoProc; 
7946     if(cps == &first) {
7947         appData.noChessProgram = TRUE;
7948         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
7949         gameMode = BeginningOfGame; ModeHighlight();
7950         SetNCPMode();
7951     }
7952     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
7953     DisplayMessage("", ""); // erase waiting message
7954     if(errmess) DisplayError(errmess, 0); // announce reason, if given
7955     return TRUE;
7956 }
7957
7958 char *savedMessage;
7959 ChessProgramState *savedState;
7960 void
7961 DeferredBookMove (void)
7962 {
7963         if(savedState->lastPing != savedState->lastPong)
7964                     ScheduleDelayedEvent(DeferredBookMove, 10);
7965         else
7966         HandleMachineMove(savedMessage, savedState);
7967 }
7968
7969 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7970
7971 void
7972 HandleMachineMove (char *message, ChessProgramState *cps)
7973 {
7974     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7975     char realname[MSG_SIZ];
7976     int fromX, fromY, toX, toY;
7977     ChessMove moveType;
7978     char promoChar;
7979     char *p, *pv=buf1;
7980     int machineWhite, oldError;
7981     char *bookHit;
7982
7983     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7984         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7985         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7986             DisplayError(_("Invalid pairing from pairing engine"), 0);
7987             return;
7988         }
7989         pairingReceived = 1;
7990         NextMatchGame();
7991         return; // Skim the pairing messages here.
7992     }
7993
7994     oldError = cps->userError; cps->userError = 0;
7995
7996 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7997     /*
7998      * Kludge to ignore BEL characters
7999      */
8000     while (*message == '\007') message++;
8001
8002     /*
8003      * [HGM] engine debug message: ignore lines starting with '#' character
8004      */
8005     if(cps->debug && *message == '#') return;
8006
8007     /*
8008      * Look for book output
8009      */
8010     if (cps == &first && bookRequested) {
8011         if (message[0] == '\t' || message[0] == ' ') {
8012             /* Part of the book output is here; append it */
8013             strcat(bookOutput, message);
8014             strcat(bookOutput, "  \n");
8015             return;
8016         } else if (bookOutput[0] != NULLCHAR) {
8017             /* All of book output has arrived; display it */
8018             char *p = bookOutput;
8019             while (*p != NULLCHAR) {
8020                 if (*p == '\t') *p = ' ';
8021                 p++;
8022             }
8023             DisplayInformation(bookOutput);
8024             bookRequested = FALSE;
8025             /* Fall through to parse the current output */
8026         }
8027     }
8028
8029     /*
8030      * Look for machine move.
8031      */
8032     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8033         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8034     {
8035         /* This method is only useful on engines that support ping */
8036         if (cps->lastPing != cps->lastPong) {
8037           if (gameMode == BeginningOfGame) {
8038             /* Extra move from before last new; ignore */
8039             if (appData.debugMode) {
8040                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8041             }
8042           } else {
8043             if (appData.debugMode) {
8044                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8045                         cps->which, gameMode);
8046             }
8047
8048             SendToProgram("undo\n", cps);
8049           }
8050           return;
8051         }
8052
8053         switch (gameMode) {
8054           case BeginningOfGame:
8055             /* Extra move from before last reset; ignore */
8056             if (appData.debugMode) {
8057                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8058             }
8059             return;
8060
8061           case EndOfGame:
8062           case IcsIdle:
8063           default:
8064             /* Extra move after we tried to stop.  The mode test is
8065                not a reliable way of detecting this problem, but it's
8066                the best we can do on engines that don't support ping.
8067             */
8068             if (appData.debugMode) {
8069                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8070                         cps->which, gameMode);
8071             }
8072             SendToProgram("undo\n", cps);
8073             return;
8074
8075           case MachinePlaysWhite:
8076           case IcsPlayingWhite:
8077             machineWhite = TRUE;
8078             break;
8079
8080           case MachinePlaysBlack:
8081           case IcsPlayingBlack:
8082             machineWhite = FALSE;
8083             break;
8084
8085           case TwoMachinesPlay:
8086             machineWhite = (cps->twoMachinesColor[0] == 'w');
8087             break;
8088         }
8089         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8090             if (appData.debugMode) {
8091                 fprintf(debugFP,
8092                         "Ignoring move out of turn by %s, gameMode %d"
8093                         ", forwardMost %d\n",
8094                         cps->which, gameMode, forwardMostMove);
8095             }
8096             return;
8097         }
8098
8099         if(cps->alphaRank) AlphaRank(machineMove, 4);
8100         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8101                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8102             /* Machine move could not be parsed; ignore it. */
8103           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8104                     machineMove, _(cps->which));
8105             DisplayError(buf1, 0);
8106             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8107                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8108             if (gameMode == TwoMachinesPlay) {
8109               GameEnds(machineWhite ? BlackWins : WhiteWins,
8110                        buf1, GE_XBOARD);
8111             }
8112             return;
8113         }
8114
8115         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8116         /* So we have to redo legality test with true e.p. status here,  */
8117         /* to make sure an illegal e.p. capture does not slip through,   */
8118         /* to cause a forfeit on a justified illegal-move complaint      */
8119         /* of the opponent.                                              */
8120         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8121            ChessMove moveType;
8122            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8123                              fromY, fromX, toY, toX, promoChar);
8124             if(moveType == IllegalMove) {
8125               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8126                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8127                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8128                            buf1, GE_XBOARD);
8129                 return;
8130            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8131            /* [HGM] Kludge to handle engines that send FRC-style castling
8132               when they shouldn't (like TSCP-Gothic) */
8133            switch(moveType) {
8134              case WhiteASideCastleFR:
8135              case BlackASideCastleFR:
8136                toX+=2;
8137                currentMoveString[2]++;
8138                break;
8139              case WhiteHSideCastleFR:
8140              case BlackHSideCastleFR:
8141                toX--;
8142                currentMoveString[2]--;
8143                break;
8144              default: ; // nothing to do, but suppresses warning of pedantic compilers
8145            }
8146         }
8147         hintRequested = FALSE;
8148         lastHint[0] = NULLCHAR;
8149         bookRequested = FALSE;
8150         /* Program may be pondering now */
8151         cps->maybeThinking = TRUE;
8152         if (cps->sendTime == 2) cps->sendTime = 1;
8153         if (cps->offeredDraw) cps->offeredDraw--;
8154
8155         /* [AS] Save move info*/
8156         pvInfoList[ forwardMostMove ].score = programStats.score;
8157         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8158         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8159
8160         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8161
8162         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8163         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8164             int count = 0;
8165
8166             while( count < adjudicateLossPlies ) {
8167                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8168
8169                 if( count & 1 ) {
8170                     score = -score; /* Flip score for winning side */
8171                 }
8172
8173                 if( score > adjudicateLossThreshold ) {
8174                     break;
8175                 }
8176
8177                 count++;
8178             }
8179
8180             if( count >= adjudicateLossPlies ) {
8181                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8182
8183                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8184                     "Xboard adjudication",
8185                     GE_XBOARD );
8186
8187                 return;
8188             }
8189         }
8190
8191         if(Adjudicate(cps)) {
8192             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8193             return; // [HGM] adjudicate: for all automatic game ends
8194         }
8195
8196 #if ZIPPY
8197         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8198             first.initDone) {
8199           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8200                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8201                 SendToICS("draw ");
8202                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8203           }
8204           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8205           ics_user_moved = 1;
8206           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8207                 char buf[3*MSG_SIZ];
8208
8209                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8210                         programStats.score / 100.,
8211                         programStats.depth,
8212                         programStats.time / 100.,
8213                         (unsigned int)programStats.nodes,
8214                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8215                         programStats.movelist);
8216                 SendToICS(buf);
8217 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8218           }
8219         }
8220 #endif
8221
8222         /* [AS] Clear stats for next move */
8223         ClearProgramStats();
8224         thinkOutput[0] = NULLCHAR;
8225         hiddenThinkOutputState = 0;
8226
8227         bookHit = NULL;
8228         if (gameMode == TwoMachinesPlay) {
8229             /* [HGM] relaying draw offers moved to after reception of move */
8230             /* and interpreting offer as claim if it brings draw condition */
8231             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8232                 SendToProgram("draw\n", cps->other);
8233             }
8234             if (cps->other->sendTime) {
8235                 SendTimeRemaining(cps->other,
8236                                   cps->other->twoMachinesColor[0] == 'w');
8237             }
8238             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8239             if (firstMove && !bookHit) {
8240                 firstMove = FALSE;
8241                 if (cps->other->useColors) {
8242                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8243                 }
8244                 SendToProgram("go\n", cps->other);
8245             }
8246             cps->other->maybeThinking = TRUE;
8247         }
8248
8249         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8250
8251         if (!pausing && appData.ringBellAfterMoves) {
8252             RingBell();
8253         }
8254
8255         /*
8256          * Reenable menu items that were disabled while
8257          * machine was thinking
8258          */
8259         if (gameMode != TwoMachinesPlay)
8260             SetUserThinkingEnables();
8261
8262         // [HGM] book: after book hit opponent has received move and is now in force mode
8263         // force the book reply into it, and then fake that it outputted this move by jumping
8264         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8265         if(bookHit) {
8266                 static char bookMove[MSG_SIZ]; // a bit generous?
8267
8268                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8269                 strcat(bookMove, bookHit);
8270                 message = bookMove;
8271                 cps = cps->other;
8272                 programStats.nodes = programStats.depth = programStats.time =
8273                 programStats.score = programStats.got_only_move = 0;
8274                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8275
8276                 if(cps->lastPing != cps->lastPong) {
8277                     savedMessage = message; // args for deferred call
8278                     savedState = cps;
8279                     ScheduleDelayedEvent(DeferredBookMove, 10);
8280                     return;
8281                 }
8282                 goto FakeBookMove;
8283         }
8284
8285         return;
8286     }
8287
8288     /* Set special modes for chess engines.  Later something general
8289      *  could be added here; for now there is just one kludge feature,
8290      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8291      *  when "xboard" is given as an interactive command.
8292      */
8293     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8294         cps->useSigint = FALSE;
8295         cps->useSigterm = FALSE;
8296     }
8297     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8298       ParseFeatures(message+8, cps);
8299       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8300     }
8301
8302     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8303                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8304       int dummy, s=6; char buf[MSG_SIZ];
8305       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8306       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8307       if(startedFromSetupPosition) return;
8308       ParseFEN(boards[0], &dummy, message+s);
8309       DrawPosition(TRUE, boards[0]);
8310       startedFromSetupPosition = TRUE;
8311       return;
8312     }
8313     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8314      * want this, I was asked to put it in, and obliged.
8315      */
8316     if (!strncmp(message, "setboard ", 9)) {
8317         Board initial_position;
8318
8319         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8320
8321         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8322             DisplayError(_("Bad FEN received from engine"), 0);
8323             return ;
8324         } else {
8325            Reset(TRUE, FALSE);
8326            CopyBoard(boards[0], initial_position);
8327            initialRulePlies = FENrulePlies;
8328            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8329            else gameMode = MachinePlaysBlack;
8330            DrawPosition(FALSE, boards[currentMove]);
8331         }
8332         return;
8333     }
8334
8335     /*
8336      * Look for communication commands
8337      */
8338     if (!strncmp(message, "telluser ", 9)) {
8339         if(message[9] == '\\' && message[10] == '\\')
8340             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8341         PlayTellSound();
8342         DisplayNote(message + 9);
8343         return;
8344     }
8345     if (!strncmp(message, "tellusererror ", 14)) {
8346         cps->userError = 1;
8347         if(message[14] == '\\' && message[15] == '\\')
8348             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8349         PlayTellSound();
8350         DisplayError(message + 14, 0);
8351         return;
8352     }
8353     if (!strncmp(message, "tellopponent ", 13)) {
8354       if (appData.icsActive) {
8355         if (loggedOn) {
8356           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8357           SendToICS(buf1);
8358         }
8359       } else {
8360         DisplayNote(message + 13);
8361       }
8362       return;
8363     }
8364     if (!strncmp(message, "tellothers ", 11)) {
8365       if (appData.icsActive) {
8366         if (loggedOn) {
8367           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8368           SendToICS(buf1);
8369         }
8370       }
8371       return;
8372     }
8373     if (!strncmp(message, "tellall ", 8)) {
8374       if (appData.icsActive) {
8375         if (loggedOn) {
8376           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8377           SendToICS(buf1);
8378         }
8379       } else {
8380         DisplayNote(message + 8);
8381       }
8382       return;
8383     }
8384     if (strncmp(message, "warning", 7) == 0) {
8385         /* Undocumented feature, use tellusererror in new code */
8386         DisplayError(message, 0);
8387         return;
8388     }
8389     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8390         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8391         strcat(realname, " query");
8392         AskQuestion(realname, buf2, buf1, cps->pr);
8393         return;
8394     }
8395     /* Commands from the engine directly to ICS.  We don't allow these to be
8396      *  sent until we are logged on. Crafty kibitzes have been known to
8397      *  interfere with the login process.
8398      */
8399     if (loggedOn) {
8400         if (!strncmp(message, "tellics ", 8)) {
8401             SendToICS(message + 8);
8402             SendToICS("\n");
8403             return;
8404         }
8405         if (!strncmp(message, "tellicsnoalias ", 15)) {
8406             SendToICS(ics_prefix);
8407             SendToICS(message + 15);
8408             SendToICS("\n");
8409             return;
8410         }
8411         /* The following are for backward compatibility only */
8412         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8413             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8414             SendToICS(ics_prefix);
8415             SendToICS(message);
8416             SendToICS("\n");
8417             return;
8418         }
8419     }
8420     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8421         return;
8422     }
8423     /*
8424      * If the move is illegal, cancel it and redraw the board.
8425      * Also deal with other error cases.  Matching is rather loose
8426      * here to accommodate engines written before the spec.
8427      */
8428     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8429         strncmp(message, "Error", 5) == 0) {
8430         if (StrStr(message, "name") ||
8431             StrStr(message, "rating") || StrStr(message, "?") ||
8432             StrStr(message, "result") || StrStr(message, "board") ||
8433             StrStr(message, "bk") || StrStr(message, "computer") ||
8434             StrStr(message, "variant") || StrStr(message, "hint") ||
8435             StrStr(message, "random") || StrStr(message, "depth") ||
8436             StrStr(message, "accepted")) {
8437             return;
8438         }
8439         if (StrStr(message, "protover")) {
8440           /* Program is responding to input, so it's apparently done
8441              initializing, and this error message indicates it is
8442              protocol version 1.  So we don't need to wait any longer
8443              for it to initialize and send feature commands. */
8444           FeatureDone(cps, 1);
8445           cps->protocolVersion = 1;
8446           return;
8447         }
8448         cps->maybeThinking = FALSE;
8449
8450         if (StrStr(message, "draw")) {
8451             /* Program doesn't have "draw" command */
8452             cps->sendDrawOffers = 0;
8453             return;
8454         }
8455         if (cps->sendTime != 1 &&
8456             (StrStr(message, "time") || StrStr(message, "otim"))) {
8457           /* Program apparently doesn't have "time" or "otim" command */
8458           cps->sendTime = 0;
8459           return;
8460         }
8461         if (StrStr(message, "analyze")) {
8462             cps->analysisSupport = FALSE;
8463             cps->analyzing = FALSE;
8464 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8465             EditGameEvent(); // [HGM] try to preserve loaded game
8466             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8467             DisplayError(buf2, 0);
8468             return;
8469         }
8470         if (StrStr(message, "(no matching move)st")) {
8471           /* Special kludge for GNU Chess 4 only */
8472           cps->stKludge = TRUE;
8473           SendTimeControl(cps, movesPerSession, timeControl,
8474                           timeIncrement, appData.searchDepth,
8475                           searchTime);
8476           return;
8477         }
8478         if (StrStr(message, "(no matching move)sd")) {
8479           /* Special kludge for GNU Chess 4 only */
8480           cps->sdKludge = TRUE;
8481           SendTimeControl(cps, movesPerSession, timeControl,
8482                           timeIncrement, appData.searchDepth,
8483                           searchTime);
8484           return;
8485         }
8486         if (!StrStr(message, "llegal")) {
8487             return;
8488         }
8489         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8490             gameMode == IcsIdle) return;
8491         if (forwardMostMove <= backwardMostMove) return;
8492         if (pausing) PauseEvent();
8493       if(appData.forceIllegal) {
8494             // [HGM] illegal: machine refused move; force position after move into it
8495           SendToProgram("force\n", cps);
8496           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8497                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8498                 // when black is to move, while there might be nothing on a2 or black
8499                 // might already have the move. So send the board as if white has the move.
8500                 // But first we must change the stm of the engine, as it refused the last move
8501                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8502                 if(WhiteOnMove(forwardMostMove)) {
8503                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8504                     SendBoard(cps, forwardMostMove); // kludgeless board
8505                 } else {
8506                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8507                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8508                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8509                 }
8510           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8511             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8512                  gameMode == TwoMachinesPlay)
8513               SendToProgram("go\n", cps);
8514             return;
8515       } else
8516         if (gameMode == PlayFromGameFile) {
8517             /* Stop reading this game file */
8518             gameMode = EditGame;
8519             ModeHighlight();
8520         }
8521         /* [HGM] illegal-move claim should forfeit game when Xboard */
8522         /* only passes fully legal moves                            */
8523         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8524             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8525                                 "False illegal-move claim", GE_XBOARD );
8526             return; // do not take back move we tested as valid
8527         }
8528         currentMove = forwardMostMove-1;
8529         DisplayMove(currentMove-1); /* before DisplayMoveError */
8530         SwitchClocks(forwardMostMove-1); // [HGM] race
8531         DisplayBothClocks();
8532         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8533                 parseList[currentMove], _(cps->which));
8534         DisplayMoveError(buf1);
8535         DrawPosition(FALSE, boards[currentMove]);
8536
8537         SetUserThinkingEnables();
8538         return;
8539     }
8540     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8541         /* Program has a broken "time" command that
8542            outputs a string not ending in newline.
8543            Don't use it. */
8544         cps->sendTime = 0;
8545     }
8546
8547     /*
8548      * If chess program startup fails, exit with an error message.
8549      * Attempts to recover here are futile. [HGM] Well, we try anyway
8550      */
8551     if ((StrStr(message, "unknown host") != NULL)
8552         || (StrStr(message, "No remote directory") != NULL)
8553         || (StrStr(message, "not found") != NULL)
8554         || (StrStr(message, "No such file") != NULL)
8555         || (StrStr(message, "can't alloc") != NULL)
8556         || (StrStr(message, "Permission denied") != NULL)) {
8557
8558         cps->maybeThinking = FALSE;
8559         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8560                 _(cps->which), cps->program, cps->host, message);
8561         RemoveInputSource(cps->isr);
8562         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8563             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8564             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8565         }
8566         return;
8567     }
8568
8569     /*
8570      * Look for hint output
8571      */
8572     if (sscanf(message, "Hint: %s", buf1) == 1) {
8573         if (cps == &first && hintRequested) {
8574             hintRequested = FALSE;
8575             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8576                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8577                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8578                                     PosFlags(forwardMostMove),
8579                                     fromY, fromX, toY, toX, promoChar, buf1);
8580                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8581                 DisplayInformation(buf2);
8582             } else {
8583                 /* Hint move could not be parsed!? */
8584               snprintf(buf2, sizeof(buf2),
8585                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8586                         buf1, _(cps->which));
8587                 DisplayError(buf2, 0);
8588             }
8589         } else {
8590           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8591         }
8592         return;
8593     }
8594
8595     /*
8596      * Ignore other messages if game is not in progress
8597      */
8598     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8599         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8600
8601     /*
8602      * look for win, lose, draw, or draw offer
8603      */
8604     if (strncmp(message, "1-0", 3) == 0) {
8605         char *p, *q, *r = "";
8606         p = strchr(message, '{');
8607         if (p) {
8608             q = strchr(p, '}');
8609             if (q) {
8610                 *q = NULLCHAR;
8611                 r = p + 1;
8612             }
8613         }
8614         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8615         return;
8616     } else if (strncmp(message, "0-1", 3) == 0) {
8617         char *p, *q, *r = "";
8618         p = strchr(message, '{');
8619         if (p) {
8620             q = strchr(p, '}');
8621             if (q) {
8622                 *q = NULLCHAR;
8623                 r = p + 1;
8624             }
8625         }
8626         /* Kludge for Arasan 4.1 bug */
8627         if (strcmp(r, "Black resigns") == 0) {
8628             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8629             return;
8630         }
8631         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8632         return;
8633     } else if (strncmp(message, "1/2", 3) == 0) {
8634         char *p, *q, *r = "";
8635         p = strchr(message, '{');
8636         if (p) {
8637             q = strchr(p, '}');
8638             if (q) {
8639                 *q = NULLCHAR;
8640                 r = p + 1;
8641             }
8642         }
8643
8644         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8645         return;
8646
8647     } else if (strncmp(message, "White resign", 12) == 0) {
8648         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8649         return;
8650     } else if (strncmp(message, "Black resign", 12) == 0) {
8651         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8652         return;
8653     } else if (strncmp(message, "White matches", 13) == 0 ||
8654                strncmp(message, "Black matches", 13) == 0   ) {
8655         /* [HGM] ignore GNUShogi noises */
8656         return;
8657     } else if (strncmp(message, "White", 5) == 0 &&
8658                message[5] != '(' &&
8659                StrStr(message, "Black") == NULL) {
8660         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8661         return;
8662     } else if (strncmp(message, "Black", 5) == 0 &&
8663                message[5] != '(') {
8664         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8665         return;
8666     } else if (strcmp(message, "resign") == 0 ||
8667                strcmp(message, "computer resigns") == 0) {
8668         switch (gameMode) {
8669           case MachinePlaysBlack:
8670           case IcsPlayingBlack:
8671             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8672             break;
8673           case MachinePlaysWhite:
8674           case IcsPlayingWhite:
8675             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8676             break;
8677           case TwoMachinesPlay:
8678             if (cps->twoMachinesColor[0] == 'w')
8679               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8680             else
8681               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8682             break;
8683           default:
8684             /* can't happen */
8685             break;
8686         }
8687         return;
8688     } else if (strncmp(message, "opponent mates", 14) == 0) {
8689         switch (gameMode) {
8690           case MachinePlaysBlack:
8691           case IcsPlayingBlack:
8692             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8693             break;
8694           case MachinePlaysWhite:
8695           case IcsPlayingWhite:
8696             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8697             break;
8698           case TwoMachinesPlay:
8699             if (cps->twoMachinesColor[0] == 'w')
8700               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8701             else
8702               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8703             break;
8704           default:
8705             /* can't happen */
8706             break;
8707         }
8708         return;
8709     } else if (strncmp(message, "computer mates", 14) == 0) {
8710         switch (gameMode) {
8711           case MachinePlaysBlack:
8712           case IcsPlayingBlack:
8713             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8714             break;
8715           case MachinePlaysWhite:
8716           case IcsPlayingWhite:
8717             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8718             break;
8719           case TwoMachinesPlay:
8720             if (cps->twoMachinesColor[0] == 'w')
8721               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8722             else
8723               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8724             break;
8725           default:
8726             /* can't happen */
8727             break;
8728         }
8729         return;
8730     } else if (strncmp(message, "checkmate", 9) == 0) {
8731         if (WhiteOnMove(forwardMostMove)) {
8732             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8733         } else {
8734             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8735         }
8736         return;
8737     } else if (strstr(message, "Draw") != NULL ||
8738                strstr(message, "game is a draw") != NULL) {
8739         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8740         return;
8741     } else if (strstr(message, "offer") != NULL &&
8742                strstr(message, "draw") != NULL) {
8743 #if ZIPPY
8744         if (appData.zippyPlay && first.initDone) {
8745             /* Relay offer to ICS */
8746             SendToICS(ics_prefix);
8747             SendToICS("draw\n");
8748         }
8749 #endif
8750         cps->offeredDraw = 2; /* valid until this engine moves twice */
8751         if (gameMode == TwoMachinesPlay) {
8752             if (cps->other->offeredDraw) {
8753                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8754             /* [HGM] in two-machine mode we delay relaying draw offer      */
8755             /* until after we also have move, to see if it is really claim */
8756             }
8757         } else if (gameMode == MachinePlaysWhite ||
8758                    gameMode == MachinePlaysBlack) {
8759           if (userOfferedDraw) {
8760             DisplayInformation(_("Machine accepts your draw offer"));
8761             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8762           } else {
8763             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8764           }
8765         }
8766     }
8767
8768
8769     /*
8770      * Look for thinking output
8771      */
8772     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8773           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8774                                 ) {
8775         int plylev, mvleft, mvtot, curscore, time;
8776         char mvname[MOVE_LEN];
8777         u64 nodes; // [DM]
8778         char plyext;
8779         int ignore = FALSE;
8780         int prefixHint = FALSE;
8781         mvname[0] = NULLCHAR;
8782
8783         switch (gameMode) {
8784           case MachinePlaysBlack:
8785           case IcsPlayingBlack:
8786             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8787             break;
8788           case MachinePlaysWhite:
8789           case IcsPlayingWhite:
8790             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8791             break;
8792           case AnalyzeMode:
8793           case AnalyzeFile:
8794             break;
8795           case IcsObserving: /* [DM] icsEngineAnalyze */
8796             if (!appData.icsEngineAnalyze) ignore = TRUE;
8797             break;
8798           case TwoMachinesPlay:
8799             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8800                 ignore = TRUE;
8801             }
8802             break;
8803           default:
8804             ignore = TRUE;
8805             break;
8806         }
8807
8808         if (!ignore) {
8809             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8810             buf1[0] = NULLCHAR;
8811             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8812                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8813
8814                 if (plyext != ' ' && plyext != '\t') {
8815                     time *= 100;
8816                 }
8817
8818                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8819                 if( cps->scoreIsAbsolute &&
8820                     ( gameMode == MachinePlaysBlack ||
8821                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8822                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8823                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8824                      !WhiteOnMove(currentMove)
8825                     ) )
8826                 {
8827                     curscore = -curscore;
8828                 }
8829
8830                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8831
8832                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8833                         char buf[MSG_SIZ];
8834                         FILE *f;
8835                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8836                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8837                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8838                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8839                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8840                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8841                                 fclose(f);
8842                         } else DisplayError(_("failed writing PV"), 0);
8843                 }
8844
8845                 tempStats.depth = plylev;
8846                 tempStats.nodes = nodes;
8847                 tempStats.time = time;
8848                 tempStats.score = curscore;
8849                 tempStats.got_only_move = 0;
8850
8851                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8852                         int ticklen;
8853
8854                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8855                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8856                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8857                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8858                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8859                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8860                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8861                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8862                 }
8863
8864                 /* Buffer overflow protection */
8865                 if (pv[0] != NULLCHAR) {
8866                     if (strlen(pv) >= sizeof(tempStats.movelist)
8867                         && appData.debugMode) {
8868                         fprintf(debugFP,
8869                                 "PV is too long; using the first %u bytes.\n",
8870                                 (unsigned) sizeof(tempStats.movelist) - 1);
8871                     }
8872
8873                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8874                 } else {
8875                     sprintf(tempStats.movelist, " no PV\n");
8876                 }
8877
8878                 if (tempStats.seen_stat) {
8879                     tempStats.ok_to_send = 1;
8880                 }
8881
8882                 if (strchr(tempStats.movelist, '(') != NULL) {
8883                     tempStats.line_is_book = 1;
8884                     tempStats.nr_moves = 0;
8885                     tempStats.moves_left = 0;
8886                 } else {
8887                     tempStats.line_is_book = 0;
8888                 }
8889
8890                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8891                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8892
8893                 SendProgramStatsToFrontend( cps, &tempStats );
8894
8895                 /*
8896                     [AS] Protect the thinkOutput buffer from overflow... this
8897                     is only useful if buf1 hasn't overflowed first!
8898                 */
8899                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8900                          plylev,
8901                          (gameMode == TwoMachinesPlay ?
8902                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8903                          ((double) curscore) / 100.0,
8904                          prefixHint ? lastHint : "",
8905                          prefixHint ? " " : "" );
8906
8907                 if( buf1[0] != NULLCHAR ) {
8908                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8909
8910                     if( strlen(pv) > max_len ) {
8911                         if( appData.debugMode) {
8912                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8913                         }
8914                         pv[max_len+1] = '\0';
8915                     }
8916
8917                     strcat( thinkOutput, pv);
8918                 }
8919
8920                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8921                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8922                     DisplayMove(currentMove - 1);
8923                 }
8924                 return;
8925
8926             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8927                 /* crafty (9.25+) says "(only move) <move>"
8928                  * if there is only 1 legal move
8929                  */
8930                 sscanf(p, "(only move) %s", buf1);
8931                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8932                 sprintf(programStats.movelist, "%s (only move)", buf1);
8933                 programStats.depth = 1;
8934                 programStats.nr_moves = 1;
8935                 programStats.moves_left = 1;
8936                 programStats.nodes = 1;
8937                 programStats.time = 1;
8938                 programStats.got_only_move = 1;
8939
8940                 /* Not really, but we also use this member to
8941                    mean "line isn't going to change" (Crafty
8942                    isn't searching, so stats won't change) */
8943                 programStats.line_is_book = 1;
8944
8945                 SendProgramStatsToFrontend( cps, &programStats );
8946
8947                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8948                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8949                     DisplayMove(currentMove - 1);
8950                 }
8951                 return;
8952             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8953                               &time, &nodes, &plylev, &mvleft,
8954                               &mvtot, mvname) >= 5) {
8955                 /* The stat01: line is from Crafty (9.29+) in response
8956                    to the "." command */
8957                 programStats.seen_stat = 1;
8958                 cps->maybeThinking = TRUE;
8959
8960                 if (programStats.got_only_move || !appData.periodicUpdates)
8961                   return;
8962
8963                 programStats.depth = plylev;
8964                 programStats.time = time;
8965                 programStats.nodes = nodes;
8966                 programStats.moves_left = mvleft;
8967                 programStats.nr_moves = mvtot;
8968                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8969                 programStats.ok_to_send = 1;
8970                 programStats.movelist[0] = '\0';
8971
8972                 SendProgramStatsToFrontend( cps, &programStats );
8973
8974                 return;
8975
8976             } else if (strncmp(message,"++",2) == 0) {
8977                 /* Crafty 9.29+ outputs this */
8978                 programStats.got_fail = 2;
8979                 return;
8980
8981             } else if (strncmp(message,"--",2) == 0) {
8982                 /* Crafty 9.29+ outputs this */
8983                 programStats.got_fail = 1;
8984                 return;
8985
8986             } else if (thinkOutput[0] != NULLCHAR &&
8987                        strncmp(message, "    ", 4) == 0) {
8988                 unsigned message_len;
8989
8990                 p = message;
8991                 while (*p && *p == ' ') p++;
8992
8993                 message_len = strlen( p );
8994
8995                 /* [AS] Avoid buffer overflow */
8996                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8997                     strcat(thinkOutput, " ");
8998                     strcat(thinkOutput, p);
8999                 }
9000
9001                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9002                     strcat(programStats.movelist, " ");
9003                     strcat(programStats.movelist, p);
9004                 }
9005
9006                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9007                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9008                     DisplayMove(currentMove - 1);
9009                 }
9010                 return;
9011             }
9012         }
9013         else {
9014             buf1[0] = NULLCHAR;
9015
9016             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9017                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9018             {
9019                 ChessProgramStats cpstats;
9020
9021                 if (plyext != ' ' && plyext != '\t') {
9022                     time *= 100;
9023                 }
9024
9025                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9026                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9027                     curscore = -curscore;
9028                 }
9029
9030                 cpstats.depth = plylev;
9031                 cpstats.nodes = nodes;
9032                 cpstats.time = time;
9033                 cpstats.score = curscore;
9034                 cpstats.got_only_move = 0;
9035                 cpstats.movelist[0] = '\0';
9036
9037                 if (buf1[0] != NULLCHAR) {
9038                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9039                 }
9040
9041                 cpstats.ok_to_send = 0;
9042                 cpstats.line_is_book = 0;
9043                 cpstats.nr_moves = 0;
9044                 cpstats.moves_left = 0;
9045
9046                 SendProgramStatsToFrontend( cps, &cpstats );
9047             }
9048         }
9049     }
9050 }
9051
9052
9053 /* Parse a game score from the character string "game", and
9054    record it as the history of the current game.  The game
9055    score is NOT assumed to start from the standard position.
9056    The display is not updated in any way.
9057    */
9058 void
9059 ParseGameHistory (char *game)
9060 {
9061     ChessMove moveType;
9062     int fromX, fromY, toX, toY, boardIndex;
9063     char promoChar;
9064     char *p, *q;
9065     char buf[MSG_SIZ];
9066
9067     if (appData.debugMode)
9068       fprintf(debugFP, "Parsing game history: %s\n", game);
9069
9070     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9071     gameInfo.site = StrSave(appData.icsHost);
9072     gameInfo.date = PGNDate();
9073     gameInfo.round = StrSave("-");
9074
9075     /* Parse out names of players */
9076     while (*game == ' ') game++;
9077     p = buf;
9078     while (*game != ' ') *p++ = *game++;
9079     *p = NULLCHAR;
9080     gameInfo.white = StrSave(buf);
9081     while (*game == ' ') game++;
9082     p = buf;
9083     while (*game != ' ' && *game != '\n') *p++ = *game++;
9084     *p = NULLCHAR;
9085     gameInfo.black = StrSave(buf);
9086
9087     /* Parse moves */
9088     boardIndex = blackPlaysFirst ? 1 : 0;
9089     yynewstr(game);
9090     for (;;) {
9091         yyboardindex = boardIndex;
9092         moveType = (ChessMove) Myylex();
9093         switch (moveType) {
9094           case IllegalMove:             /* maybe suicide chess, etc. */
9095   if (appData.debugMode) {
9096     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9097     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9098     setbuf(debugFP, NULL);
9099   }
9100           case WhitePromotion:
9101           case BlackPromotion:
9102           case WhiteNonPromotion:
9103           case BlackNonPromotion:
9104           case NormalMove:
9105           case WhiteCapturesEnPassant:
9106           case BlackCapturesEnPassant:
9107           case WhiteKingSideCastle:
9108           case WhiteQueenSideCastle:
9109           case BlackKingSideCastle:
9110           case BlackQueenSideCastle:
9111           case WhiteKingSideCastleWild:
9112           case WhiteQueenSideCastleWild:
9113           case BlackKingSideCastleWild:
9114           case BlackQueenSideCastleWild:
9115           /* PUSH Fabien */
9116           case WhiteHSideCastleFR:
9117           case WhiteASideCastleFR:
9118           case BlackHSideCastleFR:
9119           case BlackASideCastleFR:
9120           /* POP Fabien */
9121             fromX = currentMoveString[0] - AAA;
9122             fromY = currentMoveString[1] - ONE;
9123             toX = currentMoveString[2] - AAA;
9124             toY = currentMoveString[3] - ONE;
9125             promoChar = currentMoveString[4];
9126             break;
9127           case WhiteDrop:
9128           case BlackDrop:
9129             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9130             fromX = moveType == WhiteDrop ?
9131               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9132             (int) CharToPiece(ToLower(currentMoveString[0]));
9133             fromY = DROP_RANK;
9134             toX = currentMoveString[2] - AAA;
9135             toY = currentMoveString[3] - ONE;
9136             promoChar = NULLCHAR;
9137             break;
9138           case AmbiguousMove:
9139             /* bug? */
9140             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9141   if (appData.debugMode) {
9142     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9143     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9144     setbuf(debugFP, NULL);
9145   }
9146             DisplayError(buf, 0);
9147             return;
9148           case ImpossibleMove:
9149             /* bug? */
9150             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9151   if (appData.debugMode) {
9152     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9153     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9154     setbuf(debugFP, NULL);
9155   }
9156             DisplayError(buf, 0);
9157             return;
9158           case EndOfFile:
9159             if (boardIndex < backwardMostMove) {
9160                 /* Oops, gap.  How did that happen? */
9161                 DisplayError(_("Gap in move list"), 0);
9162                 return;
9163             }
9164             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9165             if (boardIndex > forwardMostMove) {
9166                 forwardMostMove = boardIndex;
9167             }
9168             return;
9169           case ElapsedTime:
9170             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9171                 strcat(parseList[boardIndex-1], " ");
9172                 strcat(parseList[boardIndex-1], yy_text);
9173             }
9174             continue;
9175           case Comment:
9176           case PGNTag:
9177           case NAG:
9178           default:
9179             /* ignore */
9180             continue;
9181           case WhiteWins:
9182           case BlackWins:
9183           case GameIsDrawn:
9184           case GameUnfinished:
9185             if (gameMode == IcsExamining) {
9186                 if (boardIndex < backwardMostMove) {
9187                     /* Oops, gap.  How did that happen? */
9188                     return;
9189                 }
9190                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9191                 return;
9192             }
9193             gameInfo.result = moveType;
9194             p = strchr(yy_text, '{');
9195             if (p == NULL) p = strchr(yy_text, '(');
9196             if (p == NULL) {
9197                 p = yy_text;
9198                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9199             } else {
9200                 q = strchr(p, *p == '{' ? '}' : ')');
9201                 if (q != NULL) *q = NULLCHAR;
9202                 p++;
9203             }
9204             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9205             gameInfo.resultDetails = StrSave(p);
9206             continue;
9207         }
9208         if (boardIndex >= forwardMostMove &&
9209             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9210             backwardMostMove = blackPlaysFirst ? 1 : 0;
9211             return;
9212         }
9213         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9214                                  fromY, fromX, toY, toX, promoChar,
9215                                  parseList[boardIndex]);
9216         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9217         /* currentMoveString is set as a side-effect of yylex */
9218         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9219         strcat(moveList[boardIndex], "\n");
9220         boardIndex++;
9221         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9222         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9223           case MT_NONE:
9224           case MT_STALEMATE:
9225           default:
9226             break;
9227           case MT_CHECK:
9228             if(gameInfo.variant != VariantShogi)
9229                 strcat(parseList[boardIndex - 1], "+");
9230             break;
9231           case MT_CHECKMATE:
9232           case MT_STAINMATE:
9233             strcat(parseList[boardIndex - 1], "#");
9234             break;
9235         }
9236     }
9237 }
9238
9239
9240 /* Apply a move to the given board  */
9241 void
9242 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9243 {
9244   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9245   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9246
9247     /* [HGM] compute & store e.p. status and castling rights for new position */
9248     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9249
9250       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9251       oldEP = (signed char)board[EP_STATUS];
9252       board[EP_STATUS] = EP_NONE;
9253
9254   if (fromY == DROP_RANK) {
9255         /* must be first */
9256         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9257             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9258             return;
9259         }
9260         piece = board[toY][toX] = (ChessSquare) fromX;
9261   } else {
9262       int i;
9263
9264       if( board[toY][toX] != EmptySquare )
9265            board[EP_STATUS] = EP_CAPTURE;
9266
9267       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9268            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9269                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9270       } else
9271       if( board[fromY][fromX] == WhitePawn ) {
9272            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9273                board[EP_STATUS] = EP_PAWN_MOVE;
9274            if( toY-fromY==2) {
9275                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9276                         gameInfo.variant != VariantBerolina || toX < fromX)
9277                       board[EP_STATUS] = toX | berolina;
9278                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9279                         gameInfo.variant != VariantBerolina || toX > fromX)
9280                       board[EP_STATUS] = toX;
9281            }
9282       } else
9283       if( board[fromY][fromX] == BlackPawn ) {
9284            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9285                board[EP_STATUS] = EP_PAWN_MOVE;
9286            if( toY-fromY== -2) {
9287                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9288                         gameInfo.variant != VariantBerolina || toX < fromX)
9289                       board[EP_STATUS] = toX | berolina;
9290                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9291                         gameInfo.variant != VariantBerolina || toX > fromX)
9292                       board[EP_STATUS] = toX;
9293            }
9294        }
9295
9296        for(i=0; i<nrCastlingRights; i++) {
9297            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9298               board[CASTLING][i] == toX   && castlingRank[i] == toY
9299              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9300        }
9301
9302      if (fromX == toX && fromY == toY) return;
9303
9304      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9305      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9306      if(gameInfo.variant == VariantKnightmate)
9307          king += (int) WhiteUnicorn - (int) WhiteKing;
9308
9309     /* Code added by Tord: */
9310     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9311     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9312         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9313       board[fromY][fromX] = EmptySquare;
9314       board[toY][toX] = EmptySquare;
9315       if((toX > fromX) != (piece == WhiteRook)) {
9316         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9317       } else {
9318         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9319       }
9320     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9321                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9322       board[fromY][fromX] = EmptySquare;
9323       board[toY][toX] = EmptySquare;
9324       if((toX > fromX) != (piece == BlackRook)) {
9325         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9326       } else {
9327         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9328       }
9329     /* End of code added by Tord */
9330
9331     } else if (board[fromY][fromX] == king
9332         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9333         && toY == fromY && toX > fromX+1) {
9334         board[fromY][fromX] = EmptySquare;
9335         board[toY][toX] = king;
9336         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9337         board[fromY][BOARD_RGHT-1] = EmptySquare;
9338     } else if (board[fromY][fromX] == king
9339         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9340                && toY == fromY && toX < fromX-1) {
9341         board[fromY][fromX] = EmptySquare;
9342         board[toY][toX] = king;
9343         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9344         board[fromY][BOARD_LEFT] = EmptySquare;
9345     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9346                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9347                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9348                ) {
9349         /* white pawn promotion */
9350         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9351         if(gameInfo.variant==VariantBughouse ||
9352            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9353             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9354         board[fromY][fromX] = EmptySquare;
9355     } else if ((fromY >= BOARD_HEIGHT>>1)
9356                && (toX != fromX)
9357                && gameInfo.variant != VariantXiangqi
9358                && gameInfo.variant != VariantBerolina
9359                && (board[fromY][fromX] == WhitePawn)
9360                && (board[toY][toX] == EmptySquare)) {
9361         board[fromY][fromX] = EmptySquare;
9362         board[toY][toX] = WhitePawn;
9363         captured = board[toY - 1][toX];
9364         board[toY - 1][toX] = EmptySquare;
9365     } else if ((fromY == BOARD_HEIGHT-4)
9366                && (toX == fromX)
9367                && gameInfo.variant == VariantBerolina
9368                && (board[fromY][fromX] == WhitePawn)
9369                && (board[toY][toX] == EmptySquare)) {
9370         board[fromY][fromX] = EmptySquare;
9371         board[toY][toX] = WhitePawn;
9372         if(oldEP & EP_BEROLIN_A) {
9373                 captured = board[fromY][fromX-1];
9374                 board[fromY][fromX-1] = EmptySquare;
9375         }else{  captured = board[fromY][fromX+1];
9376                 board[fromY][fromX+1] = EmptySquare;
9377         }
9378     } else if (board[fromY][fromX] == king
9379         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9380                && toY == fromY && toX > fromX+1) {
9381         board[fromY][fromX] = EmptySquare;
9382         board[toY][toX] = king;
9383         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9384         board[fromY][BOARD_RGHT-1] = EmptySquare;
9385     } else if (board[fromY][fromX] == king
9386         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9387                && toY == fromY && toX < fromX-1) {
9388         board[fromY][fromX] = EmptySquare;
9389         board[toY][toX] = king;
9390         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9391         board[fromY][BOARD_LEFT] = EmptySquare;
9392     } else if (fromY == 7 && fromX == 3
9393                && board[fromY][fromX] == BlackKing
9394                && toY == 7 && toX == 5) {
9395         board[fromY][fromX] = EmptySquare;
9396         board[toY][toX] = BlackKing;
9397         board[fromY][7] = EmptySquare;
9398         board[toY][4] = BlackRook;
9399     } else if (fromY == 7 && fromX == 3
9400                && board[fromY][fromX] == BlackKing
9401                && toY == 7 && toX == 1) {
9402         board[fromY][fromX] = EmptySquare;
9403         board[toY][toX] = BlackKing;
9404         board[fromY][0] = EmptySquare;
9405         board[toY][2] = BlackRook;
9406     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9407                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9408                && toY < promoRank && promoChar
9409                ) {
9410         /* black pawn promotion */
9411         board[toY][toX] = CharToPiece(ToLower(promoChar));
9412         if(gameInfo.variant==VariantBughouse ||
9413            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9414             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9415         board[fromY][fromX] = EmptySquare;
9416     } else if ((fromY < BOARD_HEIGHT>>1)
9417                && (toX != fromX)
9418                && gameInfo.variant != VariantXiangqi
9419                && gameInfo.variant != VariantBerolina
9420                && (board[fromY][fromX] == BlackPawn)
9421                && (board[toY][toX] == EmptySquare)) {
9422         board[fromY][fromX] = EmptySquare;
9423         board[toY][toX] = BlackPawn;
9424         captured = board[toY + 1][toX];
9425         board[toY + 1][toX] = EmptySquare;
9426     } else if ((fromY == 3)
9427                && (toX == fromX)
9428                && gameInfo.variant == VariantBerolina
9429                && (board[fromY][fromX] == BlackPawn)
9430                && (board[toY][toX] == EmptySquare)) {
9431         board[fromY][fromX] = EmptySquare;
9432         board[toY][toX] = BlackPawn;
9433         if(oldEP & EP_BEROLIN_A) {
9434                 captured = board[fromY][fromX-1];
9435                 board[fromY][fromX-1] = EmptySquare;
9436         }else{  captured = board[fromY][fromX+1];
9437                 board[fromY][fromX+1] = EmptySquare;
9438         }
9439     } else {
9440         board[toY][toX] = board[fromY][fromX];
9441         board[fromY][fromX] = EmptySquare;
9442     }
9443   }
9444
9445     if (gameInfo.holdingsWidth != 0) {
9446
9447       /* !!A lot more code needs to be written to support holdings  */
9448       /* [HGM] OK, so I have written it. Holdings are stored in the */
9449       /* penultimate board files, so they are automaticlly stored   */
9450       /* in the game history.                                       */
9451       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9452                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9453         /* Delete from holdings, by decreasing count */
9454         /* and erasing image if necessary            */
9455         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9456         if(p < (int) BlackPawn) { /* white drop */
9457              p -= (int)WhitePawn;
9458                  p = PieceToNumber((ChessSquare)p);
9459              if(p >= gameInfo.holdingsSize) p = 0;
9460              if(--board[p][BOARD_WIDTH-2] <= 0)
9461                   board[p][BOARD_WIDTH-1] = EmptySquare;
9462              if((int)board[p][BOARD_WIDTH-2] < 0)
9463                         board[p][BOARD_WIDTH-2] = 0;
9464         } else {                  /* black drop */
9465              p -= (int)BlackPawn;
9466                  p = PieceToNumber((ChessSquare)p);
9467              if(p >= gameInfo.holdingsSize) p = 0;
9468              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9469                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9470              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9471                         board[BOARD_HEIGHT-1-p][1] = 0;
9472         }
9473       }
9474       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9475           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9476         /* [HGM] holdings: Add to holdings, if holdings exist */
9477         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9478                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9479                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9480         }
9481         p = (int) captured;
9482         if (p >= (int) BlackPawn) {
9483           p -= (int)BlackPawn;
9484           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9485                   /* in Shogi restore piece to its original  first */
9486                   captured = (ChessSquare) (DEMOTED captured);
9487                   p = DEMOTED p;
9488           }
9489           p = PieceToNumber((ChessSquare)p);
9490           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9491           board[p][BOARD_WIDTH-2]++;
9492           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9493         } else {
9494           p -= (int)WhitePawn;
9495           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9496                   captured = (ChessSquare) (DEMOTED captured);
9497                   p = DEMOTED p;
9498           }
9499           p = PieceToNumber((ChessSquare)p);
9500           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9501           board[BOARD_HEIGHT-1-p][1]++;
9502           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9503         }
9504       }
9505     } else if (gameInfo.variant == VariantAtomic) {
9506       if (captured != EmptySquare) {
9507         int y, x;
9508         for (y = toY-1; y <= toY+1; y++) {
9509           for (x = toX-1; x <= toX+1; x++) {
9510             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9511                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9512               board[y][x] = EmptySquare;
9513             }
9514           }
9515         }
9516         board[toY][toX] = EmptySquare;
9517       }
9518     }
9519     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9520         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9521     } else
9522     if(promoChar == '+') {
9523         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9524         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9525     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9526         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9527         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9528            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9529         board[toY][toX] = newPiece;
9530     }
9531     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9532                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9533         // [HGM] superchess: take promotion piece out of holdings
9534         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9535         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9536             if(!--board[k][BOARD_WIDTH-2])
9537                 board[k][BOARD_WIDTH-1] = EmptySquare;
9538         } else {
9539             if(!--board[BOARD_HEIGHT-1-k][1])
9540                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9541         }
9542     }
9543
9544 }
9545
9546 /* Updates forwardMostMove */
9547 void
9548 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9549 {
9550 //    forwardMostMove++; // [HGM] bare: moved downstream
9551
9552     (void) CoordsToAlgebraic(boards[forwardMostMove],
9553                              PosFlags(forwardMostMove),
9554                              fromY, fromX, toY, toX, promoChar,
9555                              parseList[forwardMostMove]);
9556
9557     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9558         int timeLeft; static int lastLoadFlag=0; int king, piece;
9559         piece = boards[forwardMostMove][fromY][fromX];
9560         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9561         if(gameInfo.variant == VariantKnightmate)
9562             king += (int) WhiteUnicorn - (int) WhiteKing;
9563         if(forwardMostMove == 0) {
9564             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9565                 fprintf(serverMoves, "%s;", UserName());
9566             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9567                 fprintf(serverMoves, "%s;", second.tidy);
9568             fprintf(serverMoves, "%s;", first.tidy);
9569             if(gameMode == MachinePlaysWhite)
9570                 fprintf(serverMoves, "%s;", UserName());
9571             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9572                 fprintf(serverMoves, "%s;", second.tidy);
9573         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9574         lastLoadFlag = loadFlag;
9575         // print base move
9576         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9577         // print castling suffix
9578         if( toY == fromY && piece == king ) {
9579             if(toX-fromX > 1)
9580                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9581             if(fromX-toX >1)
9582                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9583         }
9584         // e.p. suffix
9585         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9586              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9587              boards[forwardMostMove][toY][toX] == EmptySquare
9588              && fromX != toX && fromY != toY)
9589                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9590         // promotion suffix
9591         if(promoChar != NULLCHAR)
9592                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9593         if(!loadFlag) {
9594                 char buf[MOVE_LEN*2], *p; int len;
9595             fprintf(serverMoves, "/%d/%d",
9596                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9597             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9598             else                      timeLeft = blackTimeRemaining/1000;
9599             fprintf(serverMoves, "/%d", timeLeft);
9600                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9601                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9602                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9603             fprintf(serverMoves, "/%s", buf);
9604         }
9605         fflush(serverMoves);
9606     }
9607
9608     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9609         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9610       return;
9611     }
9612     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9613     if (commentList[forwardMostMove+1] != NULL) {
9614         free(commentList[forwardMostMove+1]);
9615         commentList[forwardMostMove+1] = NULL;
9616     }
9617     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9618     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9619     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9620     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9621     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9622     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9623     adjustedClock = FALSE;
9624     gameInfo.result = GameUnfinished;
9625     if (gameInfo.resultDetails != NULL) {
9626         free(gameInfo.resultDetails);
9627         gameInfo.resultDetails = NULL;
9628     }
9629     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9630                               moveList[forwardMostMove - 1]);
9631     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9632       case MT_NONE:
9633       case MT_STALEMATE:
9634       default:
9635         break;
9636       case MT_CHECK:
9637         if(gameInfo.variant != VariantShogi)
9638             strcat(parseList[forwardMostMove - 1], "+");
9639         break;
9640       case MT_CHECKMATE:
9641       case MT_STAINMATE:
9642         strcat(parseList[forwardMostMove - 1], "#");
9643         break;
9644     }
9645
9646 }
9647
9648 /* Updates currentMove if not pausing */
9649 void
9650 ShowMove (int fromX, int fromY, int toX, int toY)
9651 {
9652     int instant = (gameMode == PlayFromGameFile) ?
9653         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9654     if(appData.noGUI) return;
9655     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9656         if (!instant) {
9657             if (forwardMostMove == currentMove + 1) {
9658                 AnimateMove(boards[forwardMostMove - 1],
9659                             fromX, fromY, toX, toY);
9660             }
9661             if (appData.highlightLastMove) {
9662                 SetHighlights(fromX, fromY, toX, toY);
9663             }
9664         }
9665         currentMove = forwardMostMove;
9666     }
9667
9668     if (instant) return;
9669
9670     DisplayMove(currentMove - 1);
9671     DrawPosition(FALSE, boards[currentMove]);
9672     DisplayBothClocks();
9673     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9674 }
9675
9676 void
9677 SendEgtPath (ChessProgramState *cps)
9678 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9679         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9680
9681         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9682
9683         while(*p) {
9684             char c, *q = name+1, *r, *s;
9685
9686             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9687             while(*p && *p != ',') *q++ = *p++;
9688             *q++ = ':'; *q = 0;
9689             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9690                 strcmp(name, ",nalimov:") == 0 ) {
9691                 // take nalimov path from the menu-changeable option first, if it is defined
9692               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9693                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9694             } else
9695             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9696                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9697                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9698                 s = r = StrStr(s, ":") + 1; // beginning of path info
9699                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9700                 c = *r; *r = 0;             // temporarily null-terminate path info
9701                     *--q = 0;               // strip of trailig ':' from name
9702                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9703                 *r = c;
9704                 SendToProgram(buf,cps);     // send egtbpath command for this format
9705             }
9706             if(*p == ',') p++; // read away comma to position for next format name
9707         }
9708 }
9709
9710 void
9711 InitChessProgram (ChessProgramState *cps, int setup)
9712 /* setup needed to setup FRC opening position */
9713 {
9714     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9715     if (appData.noChessProgram) return;
9716     hintRequested = FALSE;
9717     bookRequested = FALSE;
9718
9719     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9720     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9721     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9722     if(cps->memSize) { /* [HGM] memory */
9723       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9724         SendToProgram(buf, cps);
9725     }
9726     SendEgtPath(cps); /* [HGM] EGT */
9727     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9728       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9729         SendToProgram(buf, cps);
9730     }
9731
9732     SendToProgram(cps->initString, cps);
9733     if (gameInfo.variant != VariantNormal &&
9734         gameInfo.variant != VariantLoadable
9735         /* [HGM] also send variant if board size non-standard */
9736         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9737                                             ) {
9738       char *v = VariantName(gameInfo.variant);
9739       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9740         /* [HGM] in protocol 1 we have to assume all variants valid */
9741         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9742         DisplayFatalError(buf, 0, 1);
9743         return;
9744       }
9745
9746       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9747       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9748       if( gameInfo.variant == VariantXiangqi )
9749            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9750       if( gameInfo.variant == VariantShogi )
9751            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9752       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9753            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9754       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9755           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9756            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9757       if( gameInfo.variant == VariantCourier )
9758            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9759       if( gameInfo.variant == VariantSuper )
9760            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9761       if( gameInfo.variant == VariantGreat )
9762            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9763       if( gameInfo.variant == VariantSChess )
9764            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9765       if( gameInfo.variant == VariantGrand )
9766            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9767
9768       if(overruled) {
9769         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9770                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9771            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9772            if(StrStr(cps->variants, b) == NULL) {
9773                // specific sized variant not known, check if general sizing allowed
9774                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9775                    if(StrStr(cps->variants, "boardsize") == NULL) {
9776                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9777                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9778                        DisplayFatalError(buf, 0, 1);
9779                        return;
9780                    }
9781                    /* [HGM] here we really should compare with the maximum supported board size */
9782                }
9783            }
9784       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9785       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9786       SendToProgram(buf, cps);
9787     }
9788     currentlyInitializedVariant = gameInfo.variant;
9789
9790     /* [HGM] send opening position in FRC to first engine */
9791     if(setup) {
9792           SendToProgram("force\n", cps);
9793           SendBoard(cps, 0);
9794           /* engine is now in force mode! Set flag to wake it up after first move. */
9795           setboardSpoiledMachineBlack = 1;
9796     }
9797
9798     if (cps->sendICS) {
9799       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9800       SendToProgram(buf, cps);
9801     }
9802     cps->maybeThinking = FALSE;
9803     cps->offeredDraw = 0;
9804     if (!appData.icsActive) {
9805         SendTimeControl(cps, movesPerSession, timeControl,
9806                         timeIncrement, appData.searchDepth,
9807                         searchTime);
9808     }
9809     if (appData.showThinking
9810         // [HGM] thinking: four options require thinking output to be sent
9811         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9812                                 ) {
9813         SendToProgram("post\n", cps);
9814     }
9815     SendToProgram("hard\n", cps);
9816     if (!appData.ponderNextMove) {
9817         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9818            it without being sure what state we are in first.  "hard"
9819            is not a toggle, so that one is OK.
9820          */
9821         SendToProgram("easy\n", cps);
9822     }
9823     if (cps->usePing) {
9824       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9825       SendToProgram(buf, cps);
9826     }
9827     cps->initDone = TRUE;
9828     ClearEngineOutputPane(cps == &second);
9829 }
9830
9831
9832 void
9833 StartChessProgram (ChessProgramState *cps)
9834 {
9835     char buf[MSG_SIZ];
9836     int err;
9837
9838     if (appData.noChessProgram) return;
9839     cps->initDone = FALSE;
9840
9841     if (strcmp(cps->host, "localhost") == 0) {
9842         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9843     } else if (*appData.remoteShell == NULLCHAR) {
9844         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9845     } else {
9846         if (*appData.remoteUser == NULLCHAR) {
9847           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9848                     cps->program);
9849         } else {
9850           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9851                     cps->host, appData.remoteUser, cps->program);
9852         }
9853         err = StartChildProcess(buf, "", &cps->pr);
9854     }
9855
9856     if (err != 0) {
9857       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9858         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9859         if(cps != &first) return;
9860         appData.noChessProgram = TRUE;
9861         ThawUI();
9862         SetNCPMode();
9863 //      DisplayFatalError(buf, err, 1);
9864 //      cps->pr = NoProc;
9865 //      cps->isr = NULL;
9866         return;
9867     }
9868
9869     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9870     if (cps->protocolVersion > 1) {
9871       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9872       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9873       cps->comboCnt = 0;  //                and values of combo boxes
9874       SendToProgram(buf, cps);
9875     } else {
9876       SendToProgram("xboard\n", cps);
9877     }
9878 }
9879
9880 void
9881 TwoMachinesEventIfReady P((void))
9882 {
9883   static int curMess = 0;
9884   if (first.lastPing != first.lastPong) {
9885     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9886     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9887     return;
9888   }
9889   if (second.lastPing != second.lastPong) {
9890     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9891     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9892     return;
9893   }
9894   DisplayMessage("", ""); curMess = 0;
9895   ThawUI();
9896   TwoMachinesEvent();
9897 }
9898
9899 char *
9900 MakeName (char *template)
9901 {
9902     time_t clock;
9903     struct tm *tm;
9904     static char buf[MSG_SIZ];
9905     char *p = buf;
9906     int i;
9907
9908     clock = time((time_t *)NULL);
9909     tm = localtime(&clock);
9910
9911     while(*p++ = *template++) if(p[-1] == '%') {
9912         switch(*template++) {
9913           case 0:   *p = 0; return buf;
9914           case 'Y': i = tm->tm_year+1900; break;
9915           case 'y': i = tm->tm_year-100; break;
9916           case 'M': i = tm->tm_mon+1; break;
9917           case 'd': i = tm->tm_mday; break;
9918           case 'h': i = tm->tm_hour; break;
9919           case 'm': i = tm->tm_min; break;
9920           case 's': i = tm->tm_sec; break;
9921           default:  i = 0;
9922         }
9923         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9924     }
9925     return buf;
9926 }
9927
9928 int
9929 CountPlayers (char *p)
9930 {
9931     int n = 0;
9932     while(p = strchr(p, '\n')) p++, n++; // count participants
9933     return n;
9934 }
9935
9936 FILE *
9937 WriteTourneyFile (char *results, FILE *f)
9938 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9939     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9940     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9941         // create a file with tournament description
9942         fprintf(f, "-participants {%s}\n", appData.participants);
9943         fprintf(f, "-seedBase %d\n", appData.seedBase);
9944         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9945         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9946         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9947         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9948         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9949         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9950         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9951         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9952         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9953         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9954         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9955         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9956         if(searchTime > 0)
9957                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9958         else {
9959                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9960                 fprintf(f, "-tc %s\n", appData.timeControl);
9961                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9962         }
9963         fprintf(f, "-results \"%s\"\n", results);
9964     }
9965     return f;
9966 }
9967
9968 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9969
9970 void
9971 Substitute (char *participants, int expunge)
9972 {
9973     int i, changed, changes=0, nPlayers=0;
9974     char *p, *q, *r, buf[MSG_SIZ];
9975     if(participants == NULL) return;
9976     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9977     r = p = participants; q = appData.participants;
9978     while(*p && *p == *q) {
9979         if(*p == '\n') r = p+1, nPlayers++;
9980         p++; q++;
9981     }
9982     if(*p) { // difference
9983         while(*p && *p++ != '\n');
9984         while(*q && *q++ != '\n');
9985       changed = nPlayers;
9986         changes = 1 + (strcmp(p, q) != 0);
9987     }
9988     if(changes == 1) { // a single engine mnemonic was changed
9989         q = r; while(*q) nPlayers += (*q++ == '\n');
9990         p = buf; while(*r && (*p = *r++) != '\n') p++;
9991         *p = NULLCHAR;
9992         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9993         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9994         if(mnemonic[i]) { // The substitute is valid
9995             FILE *f;
9996             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9997                 flock(fileno(f), LOCK_EX);
9998                 ParseArgsFromFile(f);
9999                 fseek(f, 0, SEEK_SET);
10000                 FREE(appData.participants); appData.participants = participants;
10001                 if(expunge) { // erase results of replaced engine
10002                     int len = strlen(appData.results), w, b, dummy;
10003                     for(i=0; i<len; i++) {
10004                         Pairing(i, nPlayers, &w, &b, &dummy);
10005                         if((w == changed || b == changed) && appData.results[i] == '*') {
10006                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10007                             fclose(f);
10008                             return;
10009                         }
10010                     }
10011                     for(i=0; i<len; i++) {
10012                         Pairing(i, nPlayers, &w, &b, &dummy);
10013                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10014                     }
10015                 }
10016                 WriteTourneyFile(appData.results, f);
10017                 fclose(f); // release lock
10018                 return;
10019             }
10020         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10021     }
10022     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10023     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10024     free(participants);
10025     return;
10026 }
10027
10028 int
10029 CreateTourney (char *name)
10030 {
10031         FILE *f;
10032         if(matchMode && strcmp(name, appData.tourneyFile)) {
10033              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10034         }
10035         if(name[0] == NULLCHAR) {
10036             if(appData.participants[0])
10037                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10038             return 0;
10039         }
10040         f = fopen(name, "r");
10041         if(f) { // file exists
10042             ASSIGN(appData.tourneyFile, name);
10043             ParseArgsFromFile(f); // parse it
10044         } else {
10045             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10046             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10047                 DisplayError(_("Not enough participants"), 0);
10048                 return 0;
10049             }
10050             ASSIGN(appData.tourneyFile, name);
10051             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10052             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10053         }
10054         fclose(f);
10055         appData.noChessProgram = FALSE;
10056         appData.clockMode = TRUE;
10057         SetGNUMode();
10058         return 1;
10059 }
10060
10061 int
10062 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10063 {
10064     char buf[MSG_SIZ], *p, *q;
10065     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10066     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10067     skip = !all && group[0]; // if group requested, we start in skip mode
10068     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10069         p = names; q = buf; header = 0;
10070         while(*p && *p != '\n') *q++ = *p++;
10071         *q = 0;
10072         if(*p == '\n') p++;
10073         if(buf[0] == '#') {
10074             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10075             depth++; // we must be entering a new group
10076             if(all) continue; // suppress printing group headers when complete list requested
10077             header = 1;
10078             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10079         }
10080         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10081         if(engineList[i]) free(engineList[i]);
10082         engineList[i] = strdup(buf);
10083         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10084         if(engineMnemonic[i]) free(engineMnemonic[i]);
10085         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10086             strcat(buf, " (");
10087             sscanf(q + 8, "%s", buf + strlen(buf));
10088             strcat(buf, ")");
10089         }
10090         engineMnemonic[i] = strdup(buf);
10091         i++;
10092     }
10093     engineList[i] = engineMnemonic[i] = NULL;
10094     return i;
10095 }
10096
10097 // following implemented as macro to avoid type limitations
10098 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10099
10100 void
10101 SwapEngines (int n)
10102 {   // swap settings for first engine and other engine (so far only some selected options)
10103     int h;
10104     char *p;
10105     if(n == 0) return;
10106     SWAP(directory, p)
10107     SWAP(chessProgram, p)
10108     SWAP(isUCI, h)
10109     SWAP(hasOwnBookUCI, h)
10110     SWAP(protocolVersion, h)
10111     SWAP(reuse, h)
10112     SWAP(scoreIsAbsolute, h)
10113     SWAP(timeOdds, h)
10114     SWAP(logo, p)
10115     SWAP(pgnName, p)
10116     SWAP(pvSAN, h)
10117     SWAP(engOptions, p)
10118     SWAP(engInitString, p)
10119     SWAP(computerString, p)
10120     SWAP(features, p)
10121     SWAP(fenOverride, p)
10122     SWAP(NPS, h)
10123     SWAP(accumulateTC, h)
10124     SWAP(host, p)
10125 }
10126
10127 int
10128 SetPlayer (int player, char *p)
10129 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10130     int i;
10131     char buf[MSG_SIZ], *engineName;
10132     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10133     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10134     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10135     if(mnemonic[i]) {
10136         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10137         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10138         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10139         ParseArgsFromString(buf);
10140     }
10141     free(engineName);
10142     return i;
10143 }
10144
10145 char *recentEngines;
10146
10147 void
10148 RecentEngineEvent (int nr)
10149 {
10150     int n;
10151 //    SwapEngines(1); // bump first to second
10152 //    ReplaceEngine(&second, 1); // and load it there
10153     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10154     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10155     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10156         ReplaceEngine(&first, 0);
10157         FloatToFront(&appData.recentEngineList, command[n]);
10158     }
10159 }
10160
10161 int
10162 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10163 {   // determine players from game number
10164     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10165
10166     if(appData.tourneyType == 0) {
10167         roundsPerCycle = (nPlayers - 1) | 1;
10168         pairingsPerRound = nPlayers / 2;
10169     } else if(appData.tourneyType > 0) {
10170         roundsPerCycle = nPlayers - appData.tourneyType;
10171         pairingsPerRound = appData.tourneyType;
10172     }
10173     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10174     gamesPerCycle = gamesPerRound * roundsPerCycle;
10175     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10176     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10177     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10178     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10179     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10180     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10181
10182     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10183     if(appData.roundSync) *syncInterval = gamesPerRound;
10184
10185     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10186
10187     if(appData.tourneyType == 0) {
10188         if(curPairing == (nPlayers-1)/2 ) {
10189             *whitePlayer = curRound;
10190             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10191         } else {
10192             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10193             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10194             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10195             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10196         }
10197     } else if(appData.tourneyType > 1) {
10198         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10199         *whitePlayer = curRound + appData.tourneyType;
10200     } else if(appData.tourneyType > 0) {
10201         *whitePlayer = curPairing;
10202         *blackPlayer = curRound + appData.tourneyType;
10203     }
10204
10205     // take care of white/black alternation per round. 
10206     // For cycles and games this is already taken care of by default, derived from matchGame!
10207     return curRound & 1;
10208 }
10209
10210 int
10211 NextTourneyGame (int nr, int *swapColors)
10212 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10213     char *p, *q;
10214     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10215     FILE *tf;
10216     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10217     tf = fopen(appData.tourneyFile, "r");
10218     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10219     ParseArgsFromFile(tf); fclose(tf);
10220     InitTimeControls(); // TC might be altered from tourney file
10221
10222     nPlayers = CountPlayers(appData.participants); // count participants
10223     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10224     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10225
10226     if(syncInterval) {
10227         p = q = appData.results;
10228         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10229         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10230             DisplayMessage(_("Waiting for other game(s)"),"");
10231             waitingForGame = TRUE;
10232             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10233             return 0;
10234         }
10235         waitingForGame = FALSE;
10236     }
10237
10238     if(appData.tourneyType < 0) {
10239         if(nr>=0 && !pairingReceived) {
10240             char buf[1<<16];
10241             if(pairing.pr == NoProc) {
10242                 if(!appData.pairingEngine[0]) {
10243                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10244                     return 0;
10245                 }
10246                 StartChessProgram(&pairing); // starts the pairing engine
10247             }
10248             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10249             SendToProgram(buf, &pairing);
10250             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10251             SendToProgram(buf, &pairing);
10252             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10253         }
10254         pairingReceived = 0;                              // ... so we continue here 
10255         *swapColors = 0;
10256         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10257         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10258         matchGame = 1; roundNr = nr / syncInterval + 1;
10259     }
10260
10261     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10262
10263     // redefine engines, engine dir, etc.
10264     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10265     if(first.pr == NoProc) {
10266       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10267       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10268     }
10269     if(second.pr == NoProc) {
10270       SwapEngines(1);
10271       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10272       SwapEngines(1);         // and make that valid for second engine by swapping
10273       InitEngine(&second, 1);
10274     }
10275     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10276     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10277     return 1;
10278 }
10279
10280 void
10281 NextMatchGame ()
10282 {   // performs game initialization that does not invoke engines, and then tries to start the game
10283     int res, firstWhite, swapColors = 0;
10284     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10285     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
10286         char buf[MSG_SIZ];
10287         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10288         if(strcmp(buf, currentDebugFile)) { // name has changed
10289             FILE *f = fopen(buf, "w");
10290             if(f) { // if opening the new file failed, just keep using the old one
10291                 ASSIGN(currentDebugFile, buf);
10292                 fclose(debugFP);
10293                 debugFP = f;
10294             }
10295             if(appData.serverFileName) {
10296                 if(serverFP) fclose(serverFP);
10297                 serverFP = fopen(appData.serverFileName, "w");
10298                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10299                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10300             }
10301         }
10302     }
10303     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10304     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10305     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10306     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10307     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10308     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10309     Reset(FALSE, first.pr != NoProc);
10310     res = LoadGameOrPosition(matchGame); // setup game
10311     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10312     if(!res) return; // abort when bad game/pos file
10313     TwoMachinesEvent();
10314 }
10315
10316 void
10317 UserAdjudicationEvent (int result)
10318 {
10319     ChessMove gameResult = GameIsDrawn;
10320
10321     if( result > 0 ) {
10322         gameResult = WhiteWins;
10323     }
10324     else if( result < 0 ) {
10325         gameResult = BlackWins;
10326     }
10327
10328     if( gameMode == TwoMachinesPlay ) {
10329         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10330     }
10331 }
10332
10333
10334 // [HGM] save: calculate checksum of game to make games easily identifiable
10335 int
10336 StringCheckSum (char *s)
10337 {
10338         int i = 0;
10339         if(s==NULL) return 0;
10340         while(*s) i = i*259 + *s++;
10341         return i;
10342 }
10343
10344 int
10345 GameCheckSum ()
10346 {
10347         int i, sum=0;
10348         for(i=backwardMostMove; i<forwardMostMove; i++) {
10349                 sum += pvInfoList[i].depth;
10350                 sum += StringCheckSum(parseList[i]);
10351                 sum += StringCheckSum(commentList[i]);
10352                 sum *= 261;
10353         }
10354         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10355         return sum + StringCheckSum(commentList[i]);
10356 } // end of save patch
10357
10358 void
10359 GameEnds (ChessMove result, char *resultDetails, int whosays)
10360 {
10361     GameMode nextGameMode;
10362     int isIcsGame;
10363     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10364
10365     if(endingGame) return; /* [HGM] crash: forbid recursion */
10366     endingGame = 1;
10367     if(twoBoards) { // [HGM] dual: switch back to one board
10368         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10369         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10370     }
10371     if (appData.debugMode) {
10372       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10373               result, resultDetails ? resultDetails : "(null)", whosays);
10374     }
10375
10376     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10377
10378     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10379         /* If we are playing on ICS, the server decides when the
10380            game is over, but the engine can offer to draw, claim
10381            a draw, or resign.
10382          */
10383 #if ZIPPY
10384         if (appData.zippyPlay && first.initDone) {
10385             if (result == GameIsDrawn) {
10386                 /* In case draw still needs to be claimed */
10387                 SendToICS(ics_prefix);
10388                 SendToICS("draw\n");
10389             } else if (StrCaseStr(resultDetails, "resign")) {
10390                 SendToICS(ics_prefix);
10391                 SendToICS("resign\n");
10392             }
10393         }
10394 #endif
10395         endingGame = 0; /* [HGM] crash */
10396         return;
10397     }
10398
10399     /* If we're loading the game from a file, stop */
10400     if (whosays == GE_FILE) {
10401       (void) StopLoadGameTimer();
10402       gameFileFP = NULL;
10403     }
10404
10405     /* Cancel draw offers */
10406     first.offeredDraw = second.offeredDraw = 0;
10407
10408     /* If this is an ICS game, only ICS can really say it's done;
10409        if not, anyone can. */
10410     isIcsGame = (gameMode == IcsPlayingWhite ||
10411                  gameMode == IcsPlayingBlack ||
10412                  gameMode == IcsObserving    ||
10413                  gameMode == IcsExamining);
10414
10415     if (!isIcsGame || whosays == GE_ICS) {
10416         /* OK -- not an ICS game, or ICS said it was done */
10417         StopClocks();
10418         if (!isIcsGame && !appData.noChessProgram)
10419           SetUserThinkingEnables();
10420
10421         /* [HGM] if a machine claims the game end we verify this claim */
10422         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10423             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10424                 char claimer;
10425                 ChessMove trueResult = (ChessMove) -1;
10426
10427                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10428                                             first.twoMachinesColor[0] :
10429                                             second.twoMachinesColor[0] ;
10430
10431                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10432                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10433                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10434                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10435                 } else
10436                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10437                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10438                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10439                 } else
10440                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10441                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10442                 }
10443
10444                 // now verify win claims, but not in drop games, as we don't understand those yet
10445                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10446                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10447                     (result == WhiteWins && claimer == 'w' ||
10448                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10449                       if (appData.debugMode) {
10450                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10451                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10452                       }
10453                       if(result != trueResult) {
10454                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10455                               result = claimer == 'w' ? BlackWins : WhiteWins;
10456                               resultDetails = buf;
10457                       }
10458                 } else
10459                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10460                     && (forwardMostMove <= backwardMostMove ||
10461                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10462                         (claimer=='b')==(forwardMostMove&1))
10463                                                                                   ) {
10464                       /* [HGM] verify: draws that were not flagged are false claims */
10465                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10466                       result = claimer == 'w' ? BlackWins : WhiteWins;
10467                       resultDetails = buf;
10468                 }
10469                 /* (Claiming a loss is accepted no questions asked!) */
10470             }
10471             /* [HGM] bare: don't allow bare King to win */
10472             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10473                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10474                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10475                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10476                && result != GameIsDrawn)
10477             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10478                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10479                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10480                         if(p >= 0 && p <= (int)WhiteKing) k++;
10481                 }
10482                 if (appData.debugMode) {
10483                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10484                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10485                 }
10486                 if(k <= 1) {
10487                         result = GameIsDrawn;
10488                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10489                         resultDetails = buf;
10490                 }
10491             }
10492         }
10493
10494
10495         if(serverMoves != NULL && !loadFlag) { char c = '=';
10496             if(result==WhiteWins) c = '+';
10497             if(result==BlackWins) c = '-';
10498             if(resultDetails != NULL)
10499                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10500         }
10501         if (resultDetails != NULL) {
10502             gameInfo.result = result;
10503             gameInfo.resultDetails = StrSave(resultDetails);
10504
10505             /* display last move only if game was not loaded from file */
10506             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10507                 DisplayMove(currentMove - 1);
10508
10509             if (forwardMostMove != 0) {
10510                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10511                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10512                                                                 ) {
10513                     if (*appData.saveGameFile != NULLCHAR) {
10514                         SaveGameToFile(appData.saveGameFile, TRUE);
10515                     } else if (appData.autoSaveGames) {
10516                         AutoSaveGame();
10517                     }
10518                     if (*appData.savePositionFile != NULLCHAR) {
10519                         SavePositionToFile(appData.savePositionFile);
10520                     }
10521                 }
10522             }
10523
10524             /* Tell program how game ended in case it is learning */
10525             /* [HGM] Moved this to after saving the PGN, just in case */
10526             /* engine died and we got here through time loss. In that */
10527             /* case we will get a fatal error writing the pipe, which */
10528             /* would otherwise lose us the PGN.                       */
10529             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10530             /* output during GameEnds should never be fatal anymore   */
10531             if (gameMode == MachinePlaysWhite ||
10532                 gameMode == MachinePlaysBlack ||
10533                 gameMode == TwoMachinesPlay ||
10534                 gameMode == IcsPlayingWhite ||
10535                 gameMode == IcsPlayingBlack ||
10536                 gameMode == BeginningOfGame) {
10537                 char buf[MSG_SIZ];
10538                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10539                         resultDetails);
10540                 if (first.pr != NoProc) {
10541                     SendToProgram(buf, &first);
10542                 }
10543                 if (second.pr != NoProc &&
10544                     gameMode == TwoMachinesPlay) {
10545                     SendToProgram(buf, &second);
10546                 }
10547             }
10548         }
10549
10550         if (appData.icsActive) {
10551             if (appData.quietPlay &&
10552                 (gameMode == IcsPlayingWhite ||
10553                  gameMode == IcsPlayingBlack)) {
10554                 SendToICS(ics_prefix);
10555                 SendToICS("set shout 1\n");
10556             }
10557             nextGameMode = IcsIdle;
10558             ics_user_moved = FALSE;
10559             /* clean up premove.  It's ugly when the game has ended and the
10560              * premove highlights are still on the board.
10561              */
10562             if (gotPremove) {
10563               gotPremove = FALSE;
10564               ClearPremoveHighlights();
10565               DrawPosition(FALSE, boards[currentMove]);
10566             }
10567             if (whosays == GE_ICS) {
10568                 switch (result) {
10569                 case WhiteWins:
10570                     if (gameMode == IcsPlayingWhite)
10571                         PlayIcsWinSound();
10572                     else if(gameMode == IcsPlayingBlack)
10573                         PlayIcsLossSound();
10574                     break;
10575                 case BlackWins:
10576                     if (gameMode == IcsPlayingBlack)
10577                         PlayIcsWinSound();
10578                     else if(gameMode == IcsPlayingWhite)
10579                         PlayIcsLossSound();
10580                     break;
10581                 case GameIsDrawn:
10582                     PlayIcsDrawSound();
10583                     break;
10584                 default:
10585                     PlayIcsUnfinishedSound();
10586                 }
10587             }
10588         } else if (gameMode == EditGame ||
10589                    gameMode == PlayFromGameFile ||
10590                    gameMode == AnalyzeMode ||
10591                    gameMode == AnalyzeFile) {
10592             nextGameMode = gameMode;
10593         } else {
10594             nextGameMode = EndOfGame;
10595         }
10596         pausing = FALSE;
10597         ModeHighlight();
10598     } else {
10599         nextGameMode = gameMode;
10600     }
10601
10602     if (appData.noChessProgram) {
10603         gameMode = nextGameMode;
10604         ModeHighlight();
10605         endingGame = 0; /* [HGM] crash */
10606         return;
10607     }
10608
10609     if (first.reuse) {
10610         /* Put first chess program into idle state */
10611         if (first.pr != NoProc &&
10612             (gameMode == MachinePlaysWhite ||
10613              gameMode == MachinePlaysBlack ||
10614              gameMode == TwoMachinesPlay ||
10615              gameMode == IcsPlayingWhite ||
10616              gameMode == IcsPlayingBlack ||
10617              gameMode == BeginningOfGame)) {
10618             SendToProgram("force\n", &first);
10619             if (first.usePing) {
10620               char buf[MSG_SIZ];
10621               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10622               SendToProgram(buf, &first);
10623             }
10624         }
10625     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10626         /* Kill off first chess program */
10627         if (first.isr != NULL)
10628           RemoveInputSource(first.isr);
10629         first.isr = NULL;
10630
10631         if (first.pr != NoProc) {
10632             ExitAnalyzeMode();
10633             DoSleep( appData.delayBeforeQuit );
10634             SendToProgram("quit\n", &first);
10635             DoSleep( appData.delayAfterQuit );
10636             DestroyChildProcess(first.pr, first.useSigterm);
10637         }
10638         first.pr = NoProc;
10639     }
10640     if (second.reuse) {
10641         /* Put second chess program into idle state */
10642         if (second.pr != NoProc &&
10643             gameMode == TwoMachinesPlay) {
10644             SendToProgram("force\n", &second);
10645             if (second.usePing) {
10646               char buf[MSG_SIZ];
10647               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10648               SendToProgram(buf, &second);
10649             }
10650         }
10651     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10652         /* Kill off second chess program */
10653         if (second.isr != NULL)
10654           RemoveInputSource(second.isr);
10655         second.isr = NULL;
10656
10657         if (second.pr != NoProc) {
10658             DoSleep( appData.delayBeforeQuit );
10659             SendToProgram("quit\n", &second);
10660             DoSleep( appData.delayAfterQuit );
10661             DestroyChildProcess(second.pr, second.useSigterm);
10662         }
10663         second.pr = NoProc;
10664     }
10665
10666     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10667         char resChar = '=';
10668         switch (result) {
10669         case WhiteWins:
10670           resChar = '+';
10671           if (first.twoMachinesColor[0] == 'w') {
10672             first.matchWins++;
10673           } else {
10674             second.matchWins++;
10675           }
10676           break;
10677         case BlackWins:
10678           resChar = '-';
10679           if (first.twoMachinesColor[0] == 'b') {
10680             first.matchWins++;
10681           } else {
10682             second.matchWins++;
10683           }
10684           break;
10685         case GameUnfinished:
10686           resChar = ' ';
10687         default:
10688           break;
10689         }
10690
10691         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10692         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10693             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10694             ReserveGame(nextGame, resChar); // sets nextGame
10695             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10696             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10697         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10698
10699         if (nextGame <= appData.matchGames && !abortMatch) {
10700             gameMode = nextGameMode;
10701             matchGame = nextGame; // this will be overruled in tourney mode!
10702             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10703             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10704             endingGame = 0; /* [HGM] crash */
10705             return;
10706         } else {
10707             gameMode = nextGameMode;
10708             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10709                      first.tidy, second.tidy,
10710                      first.matchWins, second.matchWins,
10711                      appData.matchGames - (first.matchWins + second.matchWins));
10712             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10713             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10714             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10715             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10716                 first.twoMachinesColor = "black\n";
10717                 second.twoMachinesColor = "white\n";
10718             } else {
10719                 first.twoMachinesColor = "white\n";
10720                 second.twoMachinesColor = "black\n";
10721             }
10722         }
10723     }
10724     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10725         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10726       ExitAnalyzeMode();
10727     gameMode = nextGameMode;
10728     ModeHighlight();
10729     endingGame = 0;  /* [HGM] crash */
10730     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10731         if(matchMode == TRUE) { // match through command line: exit with or without popup
10732             if(ranking) {
10733                 ToNrEvent(forwardMostMove);
10734                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10735                 else ExitEvent(0);
10736             } else DisplayFatalError(buf, 0, 0);
10737         } else { // match through menu; just stop, with or without popup
10738             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10739             ModeHighlight();
10740             if(ranking){
10741                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10742             } else DisplayNote(buf);
10743       }
10744       if(ranking) free(ranking);
10745     }
10746 }
10747
10748 /* Assumes program was just initialized (initString sent).
10749    Leaves program in force mode. */
10750 void
10751 FeedMovesToProgram (ChessProgramState *cps, int upto)
10752 {
10753     int i;
10754
10755     if (appData.debugMode)
10756       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10757               startedFromSetupPosition ? "position and " : "",
10758               backwardMostMove, upto, cps->which);
10759     if(currentlyInitializedVariant != gameInfo.variant) {
10760       char buf[MSG_SIZ];
10761         // [HGM] variantswitch: make engine aware of new variant
10762         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10763                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10764         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10765         SendToProgram(buf, cps);
10766         currentlyInitializedVariant = gameInfo.variant;
10767     }
10768     SendToProgram("force\n", cps);
10769     if (startedFromSetupPosition) {
10770         SendBoard(cps, backwardMostMove);
10771     if (appData.debugMode) {
10772         fprintf(debugFP, "feedMoves\n");
10773     }
10774     }
10775     for (i = backwardMostMove; i < upto; i++) {
10776         SendMoveToProgram(i, cps);
10777     }
10778 }
10779
10780
10781 int
10782 ResurrectChessProgram ()
10783 {
10784      /* The chess program may have exited.
10785         If so, restart it and feed it all the moves made so far. */
10786     static int doInit = 0;
10787
10788     if (appData.noChessProgram) return 1;
10789
10790     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10791         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10792         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10793         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10794     } else {
10795         if (first.pr != NoProc) return 1;
10796         StartChessProgram(&first);
10797     }
10798     InitChessProgram(&first, FALSE);
10799     FeedMovesToProgram(&first, currentMove);
10800
10801     if (!first.sendTime) {
10802         /* can't tell gnuchess what its clock should read,
10803            so we bow to its notion. */
10804         ResetClocks();
10805         timeRemaining[0][currentMove] = whiteTimeRemaining;
10806         timeRemaining[1][currentMove] = blackTimeRemaining;
10807     }
10808
10809     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10810                 appData.icsEngineAnalyze) && first.analysisSupport) {
10811       SendToProgram("analyze\n", &first);
10812       first.analyzing = TRUE;
10813     }
10814     return 1;
10815 }
10816
10817 /*
10818  * Button procedures
10819  */
10820 void
10821 Reset (int redraw, int init)
10822 {
10823     int i;
10824
10825     if (appData.debugMode) {
10826         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10827                 redraw, init, gameMode);
10828     }
10829     CleanupTail(); // [HGM] vari: delete any stored variations
10830     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10831     pausing = pauseExamInvalid = FALSE;
10832     startedFromSetupPosition = blackPlaysFirst = FALSE;
10833     firstMove = TRUE;
10834     whiteFlag = blackFlag = FALSE;
10835     userOfferedDraw = FALSE;
10836     hintRequested = bookRequested = FALSE;
10837     first.maybeThinking = FALSE;
10838     second.maybeThinking = FALSE;
10839     first.bookSuspend = FALSE; // [HGM] book
10840     second.bookSuspend = FALSE;
10841     thinkOutput[0] = NULLCHAR;
10842     lastHint[0] = NULLCHAR;
10843     ClearGameInfo(&gameInfo);
10844     gameInfo.variant = StringToVariant(appData.variant);
10845     ics_user_moved = ics_clock_paused = FALSE;
10846     ics_getting_history = H_FALSE;
10847     ics_gamenum = -1;
10848     white_holding[0] = black_holding[0] = NULLCHAR;
10849     ClearProgramStats();
10850     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10851
10852     ResetFrontEnd();
10853     ClearHighlights();
10854     flipView = appData.flipView;
10855     ClearPremoveHighlights();
10856     gotPremove = FALSE;
10857     alarmSounded = FALSE;
10858
10859     GameEnds(EndOfFile, NULL, GE_PLAYER);
10860     if(appData.serverMovesName != NULL) {
10861         /* [HGM] prepare to make moves file for broadcasting */
10862         clock_t t = clock();
10863         if(serverMoves != NULL) fclose(serverMoves);
10864         serverMoves = fopen(appData.serverMovesName, "r");
10865         if(serverMoves != NULL) {
10866             fclose(serverMoves);
10867             /* delay 15 sec before overwriting, so all clients can see end */
10868             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10869         }
10870         serverMoves = fopen(appData.serverMovesName, "w");
10871     }
10872
10873     ExitAnalyzeMode();
10874     gameMode = BeginningOfGame;
10875     ModeHighlight();
10876     if(appData.icsActive) gameInfo.variant = VariantNormal;
10877     currentMove = forwardMostMove = backwardMostMove = 0;
10878     MarkTargetSquares(1);
10879     InitPosition(redraw);
10880     for (i = 0; i < MAX_MOVES; i++) {
10881         if (commentList[i] != NULL) {
10882             free(commentList[i]);
10883             commentList[i] = NULL;
10884         }
10885     }
10886     ResetClocks();
10887     timeRemaining[0][0] = whiteTimeRemaining;
10888     timeRemaining[1][0] = blackTimeRemaining;
10889
10890     if (first.pr == NoProc) {
10891         StartChessProgram(&first);
10892     }
10893     if (init) {
10894             InitChessProgram(&first, startedFromSetupPosition);
10895     }
10896     DisplayTitle("");
10897     DisplayMessage("", "");
10898     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10899     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10900     ClearMap();        // [HGM] exclude: invalidate map
10901 }
10902
10903 void
10904 AutoPlayGameLoop ()
10905 {
10906     for (;;) {
10907         if (!AutoPlayOneMove())
10908           return;
10909         if (matchMode || appData.timeDelay == 0)
10910           continue;
10911         if (appData.timeDelay < 0)
10912           return;
10913         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10914         break;
10915     }
10916 }
10917
10918
10919 int
10920 AutoPlayOneMove ()
10921 {
10922     int fromX, fromY, toX, toY;
10923
10924     if (appData.debugMode) {
10925       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10926     }
10927
10928     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10929       return FALSE;
10930
10931     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10932       pvInfoList[currentMove].depth = programStats.depth;
10933       pvInfoList[currentMove].score = programStats.score;
10934       pvInfoList[currentMove].time  = 0;
10935       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10936     }
10937
10938     if (currentMove >= forwardMostMove) {
10939       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10940 //      gameMode = EndOfGame;
10941 //      ModeHighlight();
10942
10943       /* [AS] Clear current move marker at the end of a game */
10944       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10945
10946       return FALSE;
10947     }
10948
10949     toX = moveList[currentMove][2] - AAA;
10950     toY = moveList[currentMove][3] - ONE;
10951
10952     if (moveList[currentMove][1] == '@') {
10953         if (appData.highlightLastMove) {
10954             SetHighlights(-1, -1, toX, toY);
10955         }
10956     } else {
10957         fromX = moveList[currentMove][0] - AAA;
10958         fromY = moveList[currentMove][1] - ONE;
10959
10960         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10961
10962         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10963
10964         if (appData.highlightLastMove) {
10965             SetHighlights(fromX, fromY, toX, toY);
10966         }
10967     }
10968     DisplayMove(currentMove);
10969     SendMoveToProgram(currentMove++, &first);
10970     DisplayBothClocks();
10971     DrawPosition(FALSE, boards[currentMove]);
10972     // [HGM] PV info: always display, routine tests if empty
10973     DisplayComment(currentMove - 1, commentList[currentMove]);
10974     return TRUE;
10975 }
10976
10977
10978 int
10979 LoadGameOneMove (ChessMove readAhead)
10980 {
10981     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10982     char promoChar = NULLCHAR;
10983     ChessMove moveType;
10984     char move[MSG_SIZ];
10985     char *p, *q;
10986
10987     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10988         gameMode != AnalyzeMode && gameMode != Training) {
10989         gameFileFP = NULL;
10990         return FALSE;
10991     }
10992
10993     yyboardindex = forwardMostMove;
10994     if (readAhead != EndOfFile) {
10995       moveType = readAhead;
10996     } else {
10997       if (gameFileFP == NULL)
10998           return FALSE;
10999       moveType = (ChessMove) Myylex();
11000     }
11001
11002     done = FALSE;
11003     switch (moveType) {
11004       case Comment:
11005         if (appData.debugMode)
11006           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11007         p = yy_text;
11008
11009         /* append the comment but don't display it */
11010         AppendComment(currentMove, p, FALSE);
11011         return TRUE;
11012
11013       case WhiteCapturesEnPassant:
11014       case BlackCapturesEnPassant:
11015       case WhitePromotion:
11016       case BlackPromotion:
11017       case WhiteNonPromotion:
11018       case BlackNonPromotion:
11019       case NormalMove:
11020       case WhiteKingSideCastle:
11021       case WhiteQueenSideCastle:
11022       case BlackKingSideCastle:
11023       case BlackQueenSideCastle:
11024       case WhiteKingSideCastleWild:
11025       case WhiteQueenSideCastleWild:
11026       case BlackKingSideCastleWild:
11027       case BlackQueenSideCastleWild:
11028       /* PUSH Fabien */
11029       case WhiteHSideCastleFR:
11030       case WhiteASideCastleFR:
11031       case BlackHSideCastleFR:
11032       case BlackASideCastleFR:
11033       /* POP Fabien */
11034         if (appData.debugMode)
11035           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11036         fromX = currentMoveString[0] - AAA;
11037         fromY = currentMoveString[1] - ONE;
11038         toX = currentMoveString[2] - AAA;
11039         toY = currentMoveString[3] - ONE;
11040         promoChar = currentMoveString[4];
11041         break;
11042
11043       case WhiteDrop:
11044       case BlackDrop:
11045         if (appData.debugMode)
11046           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11047         fromX = moveType == WhiteDrop ?
11048           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11049         (int) CharToPiece(ToLower(currentMoveString[0]));
11050         fromY = DROP_RANK;
11051         toX = currentMoveString[2] - AAA;
11052         toY = currentMoveString[3] - ONE;
11053         break;
11054
11055       case WhiteWins:
11056       case BlackWins:
11057       case GameIsDrawn:
11058       case GameUnfinished:
11059         if (appData.debugMode)
11060           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11061         p = strchr(yy_text, '{');
11062         if (p == NULL) p = strchr(yy_text, '(');
11063         if (p == NULL) {
11064             p = yy_text;
11065             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11066         } else {
11067             q = strchr(p, *p == '{' ? '}' : ')');
11068             if (q != NULL) *q = NULLCHAR;
11069             p++;
11070         }
11071         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11072         GameEnds(moveType, p, GE_FILE);
11073         done = TRUE;
11074         if (cmailMsgLoaded) {
11075             ClearHighlights();
11076             flipView = WhiteOnMove(currentMove);
11077             if (moveType == GameUnfinished) flipView = !flipView;
11078             if (appData.debugMode)
11079               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11080         }
11081         break;
11082
11083       case EndOfFile:
11084         if (appData.debugMode)
11085           fprintf(debugFP, "Parser hit end of file\n");
11086         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11087           case MT_NONE:
11088           case MT_CHECK:
11089             break;
11090           case MT_CHECKMATE:
11091           case MT_STAINMATE:
11092             if (WhiteOnMove(currentMove)) {
11093                 GameEnds(BlackWins, "Black mates", GE_FILE);
11094             } else {
11095                 GameEnds(WhiteWins, "White mates", GE_FILE);
11096             }
11097             break;
11098           case MT_STALEMATE:
11099             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11100             break;
11101         }
11102         done = TRUE;
11103         break;
11104
11105       case MoveNumberOne:
11106         if (lastLoadGameStart == GNUChessGame) {
11107             /* GNUChessGames have numbers, but they aren't move numbers */
11108             if (appData.debugMode)
11109               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11110                       yy_text, (int) moveType);
11111             return LoadGameOneMove(EndOfFile); /* tail recursion */
11112         }
11113         /* else fall thru */
11114
11115       case XBoardGame:
11116       case GNUChessGame:
11117       case PGNTag:
11118         /* Reached start of next game in file */
11119         if (appData.debugMode)
11120           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11121         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11122           case MT_NONE:
11123           case MT_CHECK:
11124             break;
11125           case MT_CHECKMATE:
11126           case MT_STAINMATE:
11127             if (WhiteOnMove(currentMove)) {
11128                 GameEnds(BlackWins, "Black mates", GE_FILE);
11129             } else {
11130                 GameEnds(WhiteWins, "White mates", GE_FILE);
11131             }
11132             break;
11133           case MT_STALEMATE:
11134             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11135             break;
11136         }
11137         done = TRUE;
11138         break;
11139
11140       case PositionDiagram:     /* should not happen; ignore */
11141       case ElapsedTime:         /* ignore */
11142       case NAG:                 /* ignore */
11143         if (appData.debugMode)
11144           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11145                   yy_text, (int) moveType);
11146         return LoadGameOneMove(EndOfFile); /* tail recursion */
11147
11148       case IllegalMove:
11149         if (appData.testLegality) {
11150             if (appData.debugMode)
11151               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11152             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11153                     (forwardMostMove / 2) + 1,
11154                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11155             DisplayError(move, 0);
11156             done = TRUE;
11157         } else {
11158             if (appData.debugMode)
11159               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11160                       yy_text, currentMoveString);
11161             fromX = currentMoveString[0] - AAA;
11162             fromY = currentMoveString[1] - ONE;
11163             toX = currentMoveString[2] - AAA;
11164             toY = currentMoveString[3] - ONE;
11165             promoChar = currentMoveString[4];
11166         }
11167         break;
11168
11169       case AmbiguousMove:
11170         if (appData.debugMode)
11171           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11172         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11173                 (forwardMostMove / 2) + 1,
11174                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11175         DisplayError(move, 0);
11176         done = TRUE;
11177         break;
11178
11179       default:
11180       case ImpossibleMove:
11181         if (appData.debugMode)
11182           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11183         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11184                 (forwardMostMove / 2) + 1,
11185                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11186         DisplayError(move, 0);
11187         done = TRUE;
11188         break;
11189     }
11190
11191     if (done) {
11192         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11193             DrawPosition(FALSE, boards[currentMove]);
11194             DisplayBothClocks();
11195             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11196               DisplayComment(currentMove - 1, commentList[currentMove]);
11197         }
11198         (void) StopLoadGameTimer();
11199         gameFileFP = NULL;
11200         cmailOldMove = forwardMostMove;
11201         return FALSE;
11202     } else {
11203         /* currentMoveString is set as a side-effect of yylex */
11204
11205         thinkOutput[0] = NULLCHAR;
11206         MakeMove(fromX, fromY, toX, toY, promoChar);
11207         currentMove = forwardMostMove;
11208         return TRUE;
11209     }
11210 }
11211
11212 /* Load the nth game from the given file */
11213 int
11214 LoadGameFromFile (char *filename, int n, char *title, int useList)
11215 {
11216     FILE *f;
11217     char buf[MSG_SIZ];
11218
11219     if (strcmp(filename, "-") == 0) {
11220         f = stdin;
11221         title = "stdin";
11222     } else {
11223         f = fopen(filename, "rb");
11224         if (f == NULL) {
11225           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11226             DisplayError(buf, errno);
11227             return FALSE;
11228         }
11229     }
11230     if (fseek(f, 0, 0) == -1) {
11231         /* f is not seekable; probably a pipe */
11232         useList = FALSE;
11233     }
11234     if (useList && n == 0) {
11235         int error = GameListBuild(f);
11236         if (error) {
11237             DisplayError(_("Cannot build game list"), error);
11238         } else if (!ListEmpty(&gameList) &&
11239                    ((ListGame *) gameList.tailPred)->number > 1) {
11240             GameListPopUp(f, title);
11241             return TRUE;
11242         }
11243         GameListDestroy();
11244         n = 1;
11245     }
11246     if (n == 0) n = 1;
11247     return LoadGame(f, n, title, FALSE);
11248 }
11249
11250
11251 void
11252 MakeRegisteredMove ()
11253 {
11254     int fromX, fromY, toX, toY;
11255     char promoChar;
11256     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11257         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11258           case CMAIL_MOVE:
11259           case CMAIL_DRAW:
11260             if (appData.debugMode)
11261               fprintf(debugFP, "Restoring %s for game %d\n",
11262                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11263
11264             thinkOutput[0] = NULLCHAR;
11265             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11266             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11267             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11268             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11269             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11270             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11271             MakeMove(fromX, fromY, toX, toY, promoChar);
11272             ShowMove(fromX, fromY, toX, toY);
11273
11274             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11275               case MT_NONE:
11276               case MT_CHECK:
11277                 break;
11278
11279               case MT_CHECKMATE:
11280               case MT_STAINMATE:
11281                 if (WhiteOnMove(currentMove)) {
11282                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11283                 } else {
11284                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11285                 }
11286                 break;
11287
11288               case MT_STALEMATE:
11289                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11290                 break;
11291             }
11292
11293             break;
11294
11295           case CMAIL_RESIGN:
11296             if (WhiteOnMove(currentMove)) {
11297                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11298             } else {
11299                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11300             }
11301             break;
11302
11303           case CMAIL_ACCEPT:
11304             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11305             break;
11306
11307           default:
11308             break;
11309         }
11310     }
11311
11312     return;
11313 }
11314
11315 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11316 int
11317 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11318 {
11319     int retVal;
11320
11321     if (gameNumber > nCmailGames) {
11322         DisplayError(_("No more games in this message"), 0);
11323         return FALSE;
11324     }
11325     if (f == lastLoadGameFP) {
11326         int offset = gameNumber - lastLoadGameNumber;
11327         if (offset == 0) {
11328             cmailMsg[0] = NULLCHAR;
11329             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11330                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11331                 nCmailMovesRegistered--;
11332             }
11333             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11334             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11335                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11336             }
11337         } else {
11338             if (! RegisterMove()) return FALSE;
11339         }
11340     }
11341
11342     retVal = LoadGame(f, gameNumber, title, useList);
11343
11344     /* Make move registered during previous look at this game, if any */
11345     MakeRegisteredMove();
11346
11347     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11348         commentList[currentMove]
11349           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11350         DisplayComment(currentMove - 1, commentList[currentMove]);
11351     }
11352
11353     return retVal;
11354 }
11355
11356 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11357 int
11358 ReloadGame (int offset)
11359 {
11360     int gameNumber = lastLoadGameNumber + offset;
11361     if (lastLoadGameFP == NULL) {
11362         DisplayError(_("No game has been loaded yet"), 0);
11363         return FALSE;
11364     }
11365     if (gameNumber <= 0) {
11366         DisplayError(_("Can't back up any further"), 0);
11367         return FALSE;
11368     }
11369     if (cmailMsgLoaded) {
11370         return CmailLoadGame(lastLoadGameFP, gameNumber,
11371                              lastLoadGameTitle, lastLoadGameUseList);
11372     } else {
11373         return LoadGame(lastLoadGameFP, gameNumber,
11374                         lastLoadGameTitle, lastLoadGameUseList);
11375     }
11376 }
11377
11378 int keys[EmptySquare+1];
11379
11380 int
11381 PositionMatches (Board b1, Board b2)
11382 {
11383     int r, f, sum=0;
11384     switch(appData.searchMode) {
11385         case 1: return CompareWithRights(b1, b2);
11386         case 2:
11387             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11388                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11389             }
11390             return TRUE;
11391         case 3:
11392             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11393               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11394                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11395             }
11396             return sum==0;
11397         case 4:
11398             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11399                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11400             }
11401             return sum==0;
11402     }
11403     return TRUE;
11404 }
11405
11406 #define Q_PROMO  4
11407 #define Q_EP     3
11408 #define Q_BCASTL 2
11409 #define Q_WCASTL 1
11410
11411 int pieceList[256], quickBoard[256];
11412 ChessSquare pieceType[256] = { EmptySquare };
11413 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11414 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11415 int soughtTotal, turn;
11416 Boolean epOK, flipSearch;
11417
11418 typedef struct {
11419     unsigned char piece, to;
11420 } Move;
11421
11422 #define DSIZE (250000)
11423
11424 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11425 Move *moveDatabase = initialSpace;
11426 unsigned int movePtr, dataSize = DSIZE;
11427
11428 int
11429 MakePieceList (Board board, int *counts)
11430 {
11431     int r, f, n=Q_PROMO, total=0;
11432     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11433     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11434         int sq = f + (r<<4);
11435         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11436             quickBoard[sq] = ++n;
11437             pieceList[n] = sq;
11438             pieceType[n] = board[r][f];
11439             counts[board[r][f]]++;
11440             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11441             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11442             total++;
11443         }
11444     }
11445     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11446     return total;
11447 }
11448
11449 void
11450 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11451 {
11452     int sq = fromX + (fromY<<4);
11453     int piece = quickBoard[sq];
11454     quickBoard[sq] = 0;
11455     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11456     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11457         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11458         moveDatabase[movePtr++].piece = Q_WCASTL;
11459         quickBoard[sq] = piece;
11460         piece = quickBoard[from]; quickBoard[from] = 0;
11461         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11462     } else
11463     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11464         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11465         moveDatabase[movePtr++].piece = Q_BCASTL;
11466         quickBoard[sq] = piece;
11467         piece = quickBoard[from]; quickBoard[from] = 0;
11468         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11469     } else
11470     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11471         quickBoard[(fromY<<4)+toX] = 0;
11472         moveDatabase[movePtr].piece = Q_EP;
11473         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11474         moveDatabase[movePtr].to = sq;
11475     } else
11476     if(promoPiece != pieceType[piece]) {
11477         moveDatabase[movePtr++].piece = Q_PROMO;
11478         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11479     }
11480     moveDatabase[movePtr].piece = piece;
11481     quickBoard[sq] = piece;
11482     movePtr++;
11483 }
11484
11485 int
11486 PackGame (Board board)
11487 {
11488     Move *newSpace = NULL;
11489     moveDatabase[movePtr].piece = 0; // terminate previous game
11490     if(movePtr > dataSize) {
11491         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11492         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11493         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11494         if(newSpace) {
11495             int i;
11496             Move *p = moveDatabase, *q = newSpace;
11497             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11498             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11499             moveDatabase = newSpace;
11500         } else { // calloc failed, we must be out of memory. Too bad...
11501             dataSize = 0; // prevent calloc events for all subsequent games
11502             return 0;     // and signal this one isn't cached
11503         }
11504     }
11505     movePtr++;
11506     MakePieceList(board, counts);
11507     return movePtr;
11508 }
11509
11510 int
11511 QuickCompare (Board board, int *minCounts, int *maxCounts)
11512 {   // compare according to search mode
11513     int r, f;
11514     switch(appData.searchMode)
11515     {
11516       case 1: // exact position match
11517         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11518         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11519             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11520         }
11521         break;
11522       case 2: // can have extra material on empty squares
11523         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11524             if(board[r][f] == EmptySquare) continue;
11525             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11526         }
11527         break;
11528       case 3: // material with exact Pawn structure
11529         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11530             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11531             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11532         } // fall through to material comparison
11533       case 4: // exact material
11534         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11535         break;
11536       case 6: // material range with given imbalance
11537         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11538         // fall through to range comparison
11539       case 5: // material range
11540         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11541     }
11542     return TRUE;
11543 }
11544
11545 int
11546 QuickScan (Board board, Move *move)
11547 {   // reconstruct game,and compare all positions in it
11548     int cnt=0, stretch=0, total = MakePieceList(board, counts), delayedKing = -1;
11549     do {
11550         int piece = move->piece;
11551         int to = move->to, from = pieceList[piece];
11552         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11553           if(!piece) return -1;
11554           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11555             piece = (++move)->piece;
11556             from = pieceList[piece];
11557             counts[pieceType[piece]]--;
11558             pieceType[piece] = (ChessSquare) move->to;
11559             counts[move->to]++;
11560           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11561             counts[pieceType[quickBoard[to]]]--;
11562             quickBoard[to] = 0; total--;
11563             move++;
11564             continue;
11565           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11566             int rook;
11567             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11568             from  = pieceList[piece]; // so this must be King
11569             quickBoard[from] = 0;
11570             pieceList[piece] = to;
11571             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11572             quickBoard[from] = 0; // rook
11573             quickBoard[to] = piece;
11574             to = move->to; piece = move->piece;
11575             goto aftercastle;
11576           }
11577         }
11578         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11579         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11580         quickBoard[from] = 0;
11581       aftercastle:
11582         quickBoard[to] = piece;
11583         pieceList[piece] = to;
11584         cnt++; turn ^= 3;
11585         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11586            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11587            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11588                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11589           ) {
11590             static int lastCounts[EmptySquare+1];
11591             int i;
11592             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11593             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11594         } else stretch = 0;
11595         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11596         move++; delayedKing = -1;
11597     } while(1);
11598 }
11599
11600 void
11601 InitSearch ()
11602 {
11603     int r, f;
11604     flipSearch = FALSE;
11605     CopyBoard(soughtBoard, boards[currentMove]);
11606     soughtTotal = MakePieceList(soughtBoard, maxSought);
11607     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11608     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11609     CopyBoard(reverseBoard, boards[currentMove]);
11610     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11611         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11612         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11613         reverseBoard[r][f] = piece;
11614     }
11615     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11616     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11617     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11618                  || (boards[currentMove][CASTLING][2] == NoRights || 
11619                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11620                  && (boards[currentMove][CASTLING][5] == NoRights || 
11621                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11622       ) {
11623         flipSearch = TRUE;
11624         CopyBoard(flipBoard, soughtBoard);
11625         CopyBoard(rotateBoard, reverseBoard);
11626         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11627             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11628             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11629         }
11630     }
11631     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11632     if(appData.searchMode >= 5) {
11633         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11634         MakePieceList(soughtBoard, minSought);
11635         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11636     }
11637     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11638         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11639 }
11640
11641 GameInfo dummyInfo;
11642
11643 int
11644 GameContainsPosition (FILE *f, ListGame *lg)
11645 {
11646     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11647     int fromX, fromY, toX, toY;
11648     char promoChar;
11649     static int initDone=FALSE;
11650
11651     // weed out games based on numerical tag comparison
11652     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11653     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11654     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11655     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11656     if(!initDone) {
11657         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11658         initDone = TRUE;
11659     }
11660     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11661     else CopyBoard(boards[scratch], initialPosition); // default start position
11662     if(lg->moves) {
11663         turn = btm + 1;
11664         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11665         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11666     }
11667     if(btm) plyNr++;
11668     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11669     fseek(f, lg->offset, 0);
11670     yynewfile(f);
11671     while(1) {
11672         yyboardindex = scratch;
11673         quickFlag = plyNr+1;
11674         next = Myylex();
11675         quickFlag = 0;
11676         switch(next) {
11677             case PGNTag:
11678                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11679             default:
11680                 continue;
11681
11682             case XBoardGame:
11683             case GNUChessGame:
11684                 if(plyNr) return -1; // after we have seen moves, this is for new game
11685               continue;
11686
11687             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11688             case ImpossibleMove:
11689             case WhiteWins: // game ends here with these four
11690             case BlackWins:
11691             case GameIsDrawn:
11692             case GameUnfinished:
11693                 return -1;
11694
11695             case IllegalMove:
11696                 if(appData.testLegality) return -1;
11697             case WhiteCapturesEnPassant:
11698             case BlackCapturesEnPassant:
11699             case WhitePromotion:
11700             case BlackPromotion:
11701             case WhiteNonPromotion:
11702             case BlackNonPromotion:
11703             case NormalMove:
11704             case WhiteKingSideCastle:
11705             case WhiteQueenSideCastle:
11706             case BlackKingSideCastle:
11707             case BlackQueenSideCastle:
11708             case WhiteKingSideCastleWild:
11709             case WhiteQueenSideCastleWild:
11710             case BlackKingSideCastleWild:
11711             case BlackQueenSideCastleWild:
11712             case WhiteHSideCastleFR:
11713             case WhiteASideCastleFR:
11714             case BlackHSideCastleFR:
11715             case BlackASideCastleFR:
11716                 fromX = currentMoveString[0] - AAA;
11717                 fromY = currentMoveString[1] - ONE;
11718                 toX = currentMoveString[2] - AAA;
11719                 toY = currentMoveString[3] - ONE;
11720                 promoChar = currentMoveString[4];
11721                 break;
11722             case WhiteDrop:
11723             case BlackDrop:
11724                 fromX = next == WhiteDrop ?
11725                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11726                   (int) CharToPiece(ToLower(currentMoveString[0]));
11727                 fromY = DROP_RANK;
11728                 toX = currentMoveString[2] - AAA;
11729                 toY = currentMoveString[3] - ONE;
11730                 promoChar = 0;
11731                 break;
11732         }
11733         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11734         plyNr++;
11735         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11736         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11737         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11738         if(appData.findMirror) {
11739             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11740             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11741         }
11742     }
11743 }
11744
11745 /* Load the nth game from open file f */
11746 int
11747 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11748 {
11749     ChessMove cm;
11750     char buf[MSG_SIZ];
11751     int gn = gameNumber;
11752     ListGame *lg = NULL;
11753     int numPGNTags = 0;
11754     int err, pos = -1;
11755     GameMode oldGameMode;
11756     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11757
11758     if (appData.debugMode)
11759         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11760
11761     if (gameMode == Training )
11762         SetTrainingModeOff();
11763
11764     oldGameMode = gameMode;
11765     if (gameMode != BeginningOfGame) {
11766       Reset(FALSE, TRUE);
11767     }
11768
11769     gameFileFP = f;
11770     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11771         fclose(lastLoadGameFP);
11772     }
11773
11774     if (useList) {
11775         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11776
11777         if (lg) {
11778             fseek(f, lg->offset, 0);
11779             GameListHighlight(gameNumber);
11780             pos = lg->position;
11781             gn = 1;
11782         }
11783         else {
11784             DisplayError(_("Game number out of range"), 0);
11785             return FALSE;
11786         }
11787     } else {
11788         GameListDestroy();
11789         if (fseek(f, 0, 0) == -1) {
11790             if (f == lastLoadGameFP ?
11791                 gameNumber == lastLoadGameNumber + 1 :
11792                 gameNumber == 1) {
11793                 gn = 1;
11794             } else {
11795                 DisplayError(_("Can't seek on game file"), 0);
11796                 return FALSE;
11797             }
11798         }
11799     }
11800     lastLoadGameFP = f;
11801     lastLoadGameNumber = gameNumber;
11802     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11803     lastLoadGameUseList = useList;
11804
11805     yynewfile(f);
11806
11807     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11808       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11809                 lg->gameInfo.black);
11810             DisplayTitle(buf);
11811     } else if (*title != NULLCHAR) {
11812         if (gameNumber > 1) {
11813           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11814             DisplayTitle(buf);
11815         } else {
11816             DisplayTitle(title);
11817         }
11818     }
11819
11820     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11821         gameMode = PlayFromGameFile;
11822         ModeHighlight();
11823     }
11824
11825     currentMove = forwardMostMove = backwardMostMove = 0;
11826     CopyBoard(boards[0], initialPosition);
11827     StopClocks();
11828
11829     /*
11830      * Skip the first gn-1 games in the file.
11831      * Also skip over anything that precedes an identifiable
11832      * start of game marker, to avoid being confused by
11833      * garbage at the start of the file.  Currently
11834      * recognized start of game markers are the move number "1",
11835      * the pattern "gnuchess .* game", the pattern
11836      * "^[#;%] [^ ]* game file", and a PGN tag block.
11837      * A game that starts with one of the latter two patterns
11838      * will also have a move number 1, possibly
11839      * following a position diagram.
11840      * 5-4-02: Let's try being more lenient and allowing a game to
11841      * start with an unnumbered move.  Does that break anything?
11842      */
11843     cm = lastLoadGameStart = EndOfFile;
11844     while (gn > 0) {
11845         yyboardindex = forwardMostMove;
11846         cm = (ChessMove) Myylex();
11847         switch (cm) {
11848           case EndOfFile:
11849             if (cmailMsgLoaded) {
11850                 nCmailGames = CMAIL_MAX_GAMES - gn;
11851             } else {
11852                 Reset(TRUE, TRUE);
11853                 DisplayError(_("Game not found in file"), 0);
11854             }
11855             return FALSE;
11856
11857           case GNUChessGame:
11858           case XBoardGame:
11859             gn--;
11860             lastLoadGameStart = cm;
11861             break;
11862
11863           case MoveNumberOne:
11864             switch (lastLoadGameStart) {
11865               case GNUChessGame:
11866               case XBoardGame:
11867               case PGNTag:
11868                 break;
11869               case MoveNumberOne:
11870               case EndOfFile:
11871                 gn--;           /* count this game */
11872                 lastLoadGameStart = cm;
11873                 break;
11874               default:
11875                 /* impossible */
11876                 break;
11877             }
11878             break;
11879
11880           case PGNTag:
11881             switch (lastLoadGameStart) {
11882               case GNUChessGame:
11883               case PGNTag:
11884               case MoveNumberOne:
11885               case EndOfFile:
11886                 gn--;           /* count this game */
11887                 lastLoadGameStart = cm;
11888                 break;
11889               case XBoardGame:
11890                 lastLoadGameStart = cm; /* game counted already */
11891                 break;
11892               default:
11893                 /* impossible */
11894                 break;
11895             }
11896             if (gn > 0) {
11897                 do {
11898                     yyboardindex = forwardMostMove;
11899                     cm = (ChessMove) Myylex();
11900                 } while (cm == PGNTag || cm == Comment);
11901             }
11902             break;
11903
11904           case WhiteWins:
11905           case BlackWins:
11906           case GameIsDrawn:
11907             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11908                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11909                     != CMAIL_OLD_RESULT) {
11910                     nCmailResults ++ ;
11911                     cmailResult[  CMAIL_MAX_GAMES
11912                                 - gn - 1] = CMAIL_OLD_RESULT;
11913                 }
11914             }
11915             break;
11916
11917           case NormalMove:
11918             /* Only a NormalMove can be at the start of a game
11919              * without a position diagram. */
11920             if (lastLoadGameStart == EndOfFile ) {
11921               gn--;
11922               lastLoadGameStart = MoveNumberOne;
11923             }
11924             break;
11925
11926           default:
11927             break;
11928         }
11929     }
11930
11931     if (appData.debugMode)
11932       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11933
11934     if (cm == XBoardGame) {
11935         /* Skip any header junk before position diagram and/or move 1 */
11936         for (;;) {
11937             yyboardindex = forwardMostMove;
11938             cm = (ChessMove) Myylex();
11939
11940             if (cm == EndOfFile ||
11941                 cm == GNUChessGame || cm == XBoardGame) {
11942                 /* Empty game; pretend end-of-file and handle later */
11943                 cm = EndOfFile;
11944                 break;
11945             }
11946
11947             if (cm == MoveNumberOne || cm == PositionDiagram ||
11948                 cm == PGNTag || cm == Comment)
11949               break;
11950         }
11951     } else if (cm == GNUChessGame) {
11952         if (gameInfo.event != NULL) {
11953             free(gameInfo.event);
11954         }
11955         gameInfo.event = StrSave(yy_text);
11956     }
11957
11958     startedFromSetupPosition = FALSE;
11959     while (cm == PGNTag) {
11960         if (appData.debugMode)
11961           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11962         err = ParsePGNTag(yy_text, &gameInfo);
11963         if (!err) numPGNTags++;
11964
11965         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11966         if(gameInfo.variant != oldVariant) {
11967             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11968             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11969             InitPosition(TRUE);
11970             oldVariant = gameInfo.variant;
11971             if (appData.debugMode)
11972               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11973         }
11974
11975
11976         if (gameInfo.fen != NULL) {
11977           Board initial_position;
11978           startedFromSetupPosition = TRUE;
11979           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11980             Reset(TRUE, TRUE);
11981             DisplayError(_("Bad FEN position in file"), 0);
11982             return FALSE;
11983           }
11984           CopyBoard(boards[0], initial_position);
11985           if (blackPlaysFirst) {
11986             currentMove = forwardMostMove = backwardMostMove = 1;
11987             CopyBoard(boards[1], initial_position);
11988             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11989             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11990             timeRemaining[0][1] = whiteTimeRemaining;
11991             timeRemaining[1][1] = blackTimeRemaining;
11992             if (commentList[0] != NULL) {
11993               commentList[1] = commentList[0];
11994               commentList[0] = NULL;
11995             }
11996           } else {
11997             currentMove = forwardMostMove = backwardMostMove = 0;
11998           }
11999           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12000           {   int i;
12001               initialRulePlies = FENrulePlies;
12002               for( i=0; i< nrCastlingRights; i++ )
12003                   initialRights[i] = initial_position[CASTLING][i];
12004           }
12005           yyboardindex = forwardMostMove;
12006           free(gameInfo.fen);
12007           gameInfo.fen = NULL;
12008         }
12009
12010         yyboardindex = forwardMostMove;
12011         cm = (ChessMove) Myylex();
12012
12013         /* Handle comments interspersed among the tags */
12014         while (cm == Comment) {
12015             char *p;
12016             if (appData.debugMode)
12017               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12018             p = yy_text;
12019             AppendComment(currentMove, p, FALSE);
12020             yyboardindex = forwardMostMove;
12021             cm = (ChessMove) Myylex();
12022         }
12023     }
12024
12025     /* don't rely on existence of Event tag since if game was
12026      * pasted from clipboard the Event tag may not exist
12027      */
12028     if (numPGNTags > 0){
12029         char *tags;
12030         if (gameInfo.variant == VariantNormal) {
12031           VariantClass v = StringToVariant(gameInfo.event);
12032           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12033           if(v < VariantShogi) gameInfo.variant = v;
12034         }
12035         if (!matchMode) {
12036           if( appData.autoDisplayTags ) {
12037             tags = PGNTags(&gameInfo);
12038             TagsPopUp(tags, CmailMsg());
12039             free(tags);
12040           }
12041         }
12042     } else {
12043         /* Make something up, but don't display it now */
12044         SetGameInfo();
12045         TagsPopDown();
12046     }
12047
12048     if (cm == PositionDiagram) {
12049         int i, j;
12050         char *p;
12051         Board initial_position;
12052
12053         if (appData.debugMode)
12054           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12055
12056         if (!startedFromSetupPosition) {
12057             p = yy_text;
12058             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12059               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12060                 switch (*p) {
12061                   case '{':
12062                   case '[':
12063                   case '-':
12064                   case ' ':
12065                   case '\t':
12066                   case '\n':
12067                   case '\r':
12068                     break;
12069                   default:
12070                     initial_position[i][j++] = CharToPiece(*p);
12071                     break;
12072                 }
12073             while (*p == ' ' || *p == '\t' ||
12074                    *p == '\n' || *p == '\r') p++;
12075
12076             if (strncmp(p, "black", strlen("black"))==0)
12077               blackPlaysFirst = TRUE;
12078             else
12079               blackPlaysFirst = FALSE;
12080             startedFromSetupPosition = TRUE;
12081
12082             CopyBoard(boards[0], initial_position);
12083             if (blackPlaysFirst) {
12084                 currentMove = forwardMostMove = backwardMostMove = 1;
12085                 CopyBoard(boards[1], initial_position);
12086                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12087                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12088                 timeRemaining[0][1] = whiteTimeRemaining;
12089                 timeRemaining[1][1] = blackTimeRemaining;
12090                 if (commentList[0] != NULL) {
12091                     commentList[1] = commentList[0];
12092                     commentList[0] = NULL;
12093                 }
12094             } else {
12095                 currentMove = forwardMostMove = backwardMostMove = 0;
12096             }
12097         }
12098         yyboardindex = forwardMostMove;
12099         cm = (ChessMove) Myylex();
12100     }
12101
12102     if (first.pr == NoProc) {
12103         StartChessProgram(&first);
12104     }
12105     InitChessProgram(&first, FALSE);
12106     SendToProgram("force\n", &first);
12107     if (startedFromSetupPosition) {
12108         SendBoard(&first, forwardMostMove);
12109     if (appData.debugMode) {
12110         fprintf(debugFP, "Load Game\n");
12111     }
12112         DisplayBothClocks();
12113     }
12114
12115     /* [HGM] server: flag to write setup moves in broadcast file as one */
12116     loadFlag = appData.suppressLoadMoves;
12117
12118     while (cm == Comment) {
12119         char *p;
12120         if (appData.debugMode)
12121           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12122         p = yy_text;
12123         AppendComment(currentMove, p, FALSE);
12124         yyboardindex = forwardMostMove;
12125         cm = (ChessMove) Myylex();
12126     }
12127
12128     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12129         cm == WhiteWins || cm == BlackWins ||
12130         cm == GameIsDrawn || cm == GameUnfinished) {
12131         DisplayMessage("", _("No moves in game"));
12132         if (cmailMsgLoaded) {
12133             if (appData.debugMode)
12134               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12135             ClearHighlights();
12136             flipView = FALSE;
12137         }
12138         DrawPosition(FALSE, boards[currentMove]);
12139         DisplayBothClocks();
12140         gameMode = EditGame;
12141         ModeHighlight();
12142         gameFileFP = NULL;
12143         cmailOldMove = 0;
12144         return TRUE;
12145     }
12146
12147     // [HGM] PV info: routine tests if comment empty
12148     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12149         DisplayComment(currentMove - 1, commentList[currentMove]);
12150     }
12151     if (!matchMode && appData.timeDelay != 0)
12152       DrawPosition(FALSE, boards[currentMove]);
12153
12154     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12155       programStats.ok_to_send = 1;
12156     }
12157
12158     /* if the first token after the PGN tags is a move
12159      * and not move number 1, retrieve it from the parser
12160      */
12161     if (cm != MoveNumberOne)
12162         LoadGameOneMove(cm);
12163
12164     /* load the remaining moves from the file */
12165     while (LoadGameOneMove(EndOfFile)) {
12166       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12167       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12168     }
12169
12170     /* rewind to the start of the game */
12171     currentMove = backwardMostMove;
12172
12173     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12174
12175     if (oldGameMode == AnalyzeFile ||
12176         oldGameMode == AnalyzeMode) {
12177       AnalyzeFileEvent();
12178     }
12179
12180     if (!matchMode && pos > 0) {
12181         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12182     } else
12183     if (matchMode || appData.timeDelay == 0) {
12184       ToEndEvent();
12185     } else if (appData.timeDelay > 0) {
12186       AutoPlayGameLoop();
12187     }
12188
12189     if (appData.debugMode)
12190         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12191
12192     loadFlag = 0; /* [HGM] true game starts */
12193     return TRUE;
12194 }
12195
12196 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12197 int
12198 ReloadPosition (int offset)
12199 {
12200     int positionNumber = lastLoadPositionNumber + offset;
12201     if (lastLoadPositionFP == NULL) {
12202         DisplayError(_("No position has been loaded yet"), 0);
12203         return FALSE;
12204     }
12205     if (positionNumber <= 0) {
12206         DisplayError(_("Can't back up any further"), 0);
12207         return FALSE;
12208     }
12209     return LoadPosition(lastLoadPositionFP, positionNumber,
12210                         lastLoadPositionTitle);
12211 }
12212
12213 /* Load the nth position from the given file */
12214 int
12215 LoadPositionFromFile (char *filename, int n, char *title)
12216 {
12217     FILE *f;
12218     char buf[MSG_SIZ];
12219
12220     if (strcmp(filename, "-") == 0) {
12221         return LoadPosition(stdin, n, "stdin");
12222     } else {
12223         f = fopen(filename, "rb");
12224         if (f == NULL) {
12225             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12226             DisplayError(buf, errno);
12227             return FALSE;
12228         } else {
12229             return LoadPosition(f, n, title);
12230         }
12231     }
12232 }
12233
12234 /* Load the nth position from the given open file, and close it */
12235 int
12236 LoadPosition (FILE *f, int positionNumber, char *title)
12237 {
12238     char *p, line[MSG_SIZ];
12239     Board initial_position;
12240     int i, j, fenMode, pn;
12241
12242     if (gameMode == Training )
12243         SetTrainingModeOff();
12244
12245     if (gameMode != BeginningOfGame) {
12246         Reset(FALSE, TRUE);
12247     }
12248     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12249         fclose(lastLoadPositionFP);
12250     }
12251     if (positionNumber == 0) positionNumber = 1;
12252     lastLoadPositionFP = f;
12253     lastLoadPositionNumber = positionNumber;
12254     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12255     if (first.pr == NoProc && !appData.noChessProgram) {
12256       StartChessProgram(&first);
12257       InitChessProgram(&first, FALSE);
12258     }
12259     pn = positionNumber;
12260     if (positionNumber < 0) {
12261         /* Negative position number means to seek to that byte offset */
12262         if (fseek(f, -positionNumber, 0) == -1) {
12263             DisplayError(_("Can't seek on position file"), 0);
12264             return FALSE;
12265         };
12266         pn = 1;
12267     } else {
12268         if (fseek(f, 0, 0) == -1) {
12269             if (f == lastLoadPositionFP ?
12270                 positionNumber == lastLoadPositionNumber + 1 :
12271                 positionNumber == 1) {
12272                 pn = 1;
12273             } else {
12274                 DisplayError(_("Can't seek on position file"), 0);
12275                 return FALSE;
12276             }
12277         }
12278     }
12279     /* See if this file is FEN or old-style xboard */
12280     if (fgets(line, MSG_SIZ, f) == NULL) {
12281         DisplayError(_("Position not found in file"), 0);
12282         return FALSE;
12283     }
12284     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12285     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12286
12287     if (pn >= 2) {
12288         if (fenMode || line[0] == '#') pn--;
12289         while (pn > 0) {
12290             /* skip positions before number pn */
12291             if (fgets(line, MSG_SIZ, f) == NULL) {
12292                 Reset(TRUE, TRUE);
12293                 DisplayError(_("Position not found in file"), 0);
12294                 return FALSE;
12295             }
12296             if (fenMode || line[0] == '#') pn--;
12297         }
12298     }
12299
12300     if (fenMode) {
12301         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12302             DisplayError(_("Bad FEN position in file"), 0);
12303             return FALSE;
12304         }
12305     } else {
12306         (void) fgets(line, MSG_SIZ, f);
12307         (void) fgets(line, MSG_SIZ, f);
12308
12309         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12310             (void) fgets(line, MSG_SIZ, f);
12311             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12312                 if (*p == ' ')
12313                   continue;
12314                 initial_position[i][j++] = CharToPiece(*p);
12315             }
12316         }
12317
12318         blackPlaysFirst = FALSE;
12319         if (!feof(f)) {
12320             (void) fgets(line, MSG_SIZ, f);
12321             if (strncmp(line, "black", strlen("black"))==0)
12322               blackPlaysFirst = TRUE;
12323         }
12324     }
12325     startedFromSetupPosition = TRUE;
12326
12327     CopyBoard(boards[0], initial_position);
12328     if (blackPlaysFirst) {
12329         currentMove = forwardMostMove = backwardMostMove = 1;
12330         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12331         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12332         CopyBoard(boards[1], initial_position);
12333         DisplayMessage("", _("Black to play"));
12334     } else {
12335         currentMove = forwardMostMove = backwardMostMove = 0;
12336         DisplayMessage("", _("White to play"));
12337     }
12338     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12339     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12340         SendToProgram("force\n", &first);
12341         SendBoard(&first, forwardMostMove);
12342     }
12343     if (appData.debugMode) {
12344 int i, j;
12345   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12346   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12347         fprintf(debugFP, "Load Position\n");
12348     }
12349
12350     if (positionNumber > 1) {
12351       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12352         DisplayTitle(line);
12353     } else {
12354         DisplayTitle(title);
12355     }
12356     gameMode = EditGame;
12357     ModeHighlight();
12358     ResetClocks();
12359     timeRemaining[0][1] = whiteTimeRemaining;
12360     timeRemaining[1][1] = blackTimeRemaining;
12361     DrawPosition(FALSE, boards[currentMove]);
12362
12363     return TRUE;
12364 }
12365
12366
12367 void
12368 CopyPlayerNameIntoFileName (char **dest, char *src)
12369 {
12370     while (*src != NULLCHAR && *src != ',') {
12371         if (*src == ' ') {
12372             *(*dest)++ = '_';
12373             src++;
12374         } else {
12375             *(*dest)++ = *src++;
12376         }
12377     }
12378 }
12379
12380 char *
12381 DefaultFileName (char *ext)
12382 {
12383     static char def[MSG_SIZ];
12384     char *p;
12385
12386     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12387         p = def;
12388         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12389         *p++ = '-';
12390         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12391         *p++ = '.';
12392         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12393     } else {
12394         def[0] = NULLCHAR;
12395     }
12396     return def;
12397 }
12398
12399 /* Save the current game to the given file */
12400 int
12401 SaveGameToFile (char *filename, int append)
12402 {
12403     FILE *f;
12404     char buf[MSG_SIZ];
12405     int result, i, t,tot=0;
12406
12407     if (strcmp(filename, "-") == 0) {
12408         return SaveGame(stdout, 0, NULL);
12409     } else {
12410         for(i=0; i<10; i++) { // upto 10 tries
12411              f = fopen(filename, append ? "a" : "w");
12412              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12413              if(f || errno != 13) break;
12414              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12415              tot += t;
12416         }
12417         if (f == NULL) {
12418             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12419             DisplayError(buf, errno);
12420             return FALSE;
12421         } else {
12422             safeStrCpy(buf, lastMsg, MSG_SIZ);
12423             DisplayMessage(_("Waiting for access to save file"), "");
12424             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12425             DisplayMessage(_("Saving game"), "");
12426             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12427             result = SaveGame(f, 0, NULL);
12428             DisplayMessage(buf, "");
12429             return result;
12430         }
12431     }
12432 }
12433
12434 char *
12435 SavePart (char *str)
12436 {
12437     static char buf[MSG_SIZ];
12438     char *p;
12439
12440     p = strchr(str, ' ');
12441     if (p == NULL) return str;
12442     strncpy(buf, str, p - str);
12443     buf[p - str] = NULLCHAR;
12444     return buf;
12445 }
12446
12447 #define PGN_MAX_LINE 75
12448
12449 #define PGN_SIDE_WHITE  0
12450 #define PGN_SIDE_BLACK  1
12451
12452 static int
12453 FindFirstMoveOutOfBook (int side)
12454 {
12455     int result = -1;
12456
12457     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12458         int index = backwardMostMove;
12459         int has_book_hit = 0;
12460
12461         if( (index % 2) != side ) {
12462             index++;
12463         }
12464
12465         while( index < forwardMostMove ) {
12466             /* Check to see if engine is in book */
12467             int depth = pvInfoList[index].depth;
12468             int score = pvInfoList[index].score;
12469             int in_book = 0;
12470
12471             if( depth <= 2 ) {
12472                 in_book = 1;
12473             }
12474             else if( score == 0 && depth == 63 ) {
12475                 in_book = 1; /* Zappa */
12476             }
12477             else if( score == 2 && depth == 99 ) {
12478                 in_book = 1; /* Abrok */
12479             }
12480
12481             has_book_hit += in_book;
12482
12483             if( ! in_book ) {
12484                 result = index;
12485
12486                 break;
12487             }
12488
12489             index += 2;
12490         }
12491     }
12492
12493     return result;
12494 }
12495
12496 void
12497 GetOutOfBookInfo (char * buf)
12498 {
12499     int oob[2];
12500     int i;
12501     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12502
12503     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12504     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12505
12506     *buf = '\0';
12507
12508     if( oob[0] >= 0 || oob[1] >= 0 ) {
12509         for( i=0; i<2; i++ ) {
12510             int idx = oob[i];
12511
12512             if( idx >= 0 ) {
12513                 if( i > 0 && oob[0] >= 0 ) {
12514                     strcat( buf, "   " );
12515                 }
12516
12517                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12518                 sprintf( buf+strlen(buf), "%s%.2f",
12519                     pvInfoList[idx].score >= 0 ? "+" : "",
12520                     pvInfoList[idx].score / 100.0 );
12521             }
12522         }
12523     }
12524 }
12525
12526 /* Save game in PGN style and close the file */
12527 int
12528 SaveGamePGN (FILE *f)
12529 {
12530     int i, offset, linelen, newblock;
12531     time_t tm;
12532 //    char *movetext;
12533     char numtext[32];
12534     int movelen, numlen, blank;
12535     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12536
12537     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12538
12539     tm = time((time_t *) NULL);
12540
12541     PrintPGNTags(f, &gameInfo);
12542
12543     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12544
12545     if (backwardMostMove > 0 || startedFromSetupPosition) {
12546         char *fen = PositionToFEN(backwardMostMove, NULL);
12547         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12548         fprintf(f, "\n{--------------\n");
12549         PrintPosition(f, backwardMostMove);
12550         fprintf(f, "--------------}\n");
12551         free(fen);
12552     }
12553     else {
12554         /* [AS] Out of book annotation */
12555         if( appData.saveOutOfBookInfo ) {
12556             char buf[64];
12557
12558             GetOutOfBookInfo( buf );
12559
12560             if( buf[0] != '\0' ) {
12561                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12562             }
12563         }
12564
12565         fprintf(f, "\n");
12566     }
12567
12568     i = backwardMostMove;
12569     linelen = 0;
12570     newblock = TRUE;
12571
12572     while (i < forwardMostMove) {
12573         /* Print comments preceding this move */
12574         if (commentList[i] != NULL) {
12575             if (linelen > 0) fprintf(f, "\n");
12576             fprintf(f, "%s", commentList[i]);
12577             linelen = 0;
12578             newblock = TRUE;
12579         }
12580
12581         /* Format move number */
12582         if ((i % 2) == 0)
12583           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12584         else
12585           if (newblock)
12586             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12587           else
12588             numtext[0] = NULLCHAR;
12589
12590         numlen = strlen(numtext);
12591         newblock = FALSE;
12592
12593         /* Print move number */
12594         blank = linelen > 0 && numlen > 0;
12595         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12596             fprintf(f, "\n");
12597             linelen = 0;
12598             blank = 0;
12599         }
12600         if (blank) {
12601             fprintf(f, " ");
12602             linelen++;
12603         }
12604         fprintf(f, "%s", numtext);
12605         linelen += numlen;
12606
12607         /* Get move */
12608         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12609         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12610
12611         /* Print move */
12612         blank = linelen > 0 && movelen > 0;
12613         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12614             fprintf(f, "\n");
12615             linelen = 0;
12616             blank = 0;
12617         }
12618         if (blank) {
12619             fprintf(f, " ");
12620             linelen++;
12621         }
12622         fprintf(f, "%s", move_buffer);
12623         linelen += movelen;
12624
12625         /* [AS] Add PV info if present */
12626         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12627             /* [HGM] add time */
12628             char buf[MSG_SIZ]; int seconds;
12629
12630             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12631
12632             if( seconds <= 0)
12633               buf[0] = 0;
12634             else
12635               if( seconds < 30 )
12636                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12637               else
12638                 {
12639                   seconds = (seconds + 4)/10; // round to full seconds
12640                   if( seconds < 60 )
12641                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12642                   else
12643                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12644                 }
12645
12646             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12647                       pvInfoList[i].score >= 0 ? "+" : "",
12648                       pvInfoList[i].score / 100.0,
12649                       pvInfoList[i].depth,
12650                       buf );
12651
12652             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12653
12654             /* Print score/depth */
12655             blank = linelen > 0 && movelen > 0;
12656             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12657                 fprintf(f, "\n");
12658                 linelen = 0;
12659                 blank = 0;
12660             }
12661             if (blank) {
12662                 fprintf(f, " ");
12663                 linelen++;
12664             }
12665             fprintf(f, "%s", move_buffer);
12666             linelen += movelen;
12667         }
12668
12669         i++;
12670     }
12671
12672     /* Start a new line */
12673     if (linelen > 0) fprintf(f, "\n");
12674
12675     /* Print comments after last move */
12676     if (commentList[i] != NULL) {
12677         fprintf(f, "%s\n", commentList[i]);
12678     }
12679
12680     /* Print result */
12681     if (gameInfo.resultDetails != NULL &&
12682         gameInfo.resultDetails[0] != NULLCHAR) {
12683         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12684                 PGNResult(gameInfo.result));
12685     } else {
12686         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12687     }
12688
12689     fclose(f);
12690     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12691     return TRUE;
12692 }
12693
12694 /* Save game in old style and close the file */
12695 int
12696 SaveGameOldStyle (FILE *f)
12697 {
12698     int i, offset;
12699     time_t tm;
12700
12701     tm = time((time_t *) NULL);
12702
12703     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12704     PrintOpponents(f);
12705
12706     if (backwardMostMove > 0 || startedFromSetupPosition) {
12707         fprintf(f, "\n[--------------\n");
12708         PrintPosition(f, backwardMostMove);
12709         fprintf(f, "--------------]\n");
12710     } else {
12711         fprintf(f, "\n");
12712     }
12713
12714     i = backwardMostMove;
12715     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12716
12717     while (i < forwardMostMove) {
12718         if (commentList[i] != NULL) {
12719             fprintf(f, "[%s]\n", commentList[i]);
12720         }
12721
12722         if ((i % 2) == 1) {
12723             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12724             i++;
12725         } else {
12726             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12727             i++;
12728             if (commentList[i] != NULL) {
12729                 fprintf(f, "\n");
12730                 continue;
12731             }
12732             if (i >= forwardMostMove) {
12733                 fprintf(f, "\n");
12734                 break;
12735             }
12736             fprintf(f, "%s\n", parseList[i]);
12737             i++;
12738         }
12739     }
12740
12741     if (commentList[i] != NULL) {
12742         fprintf(f, "[%s]\n", commentList[i]);
12743     }
12744
12745     /* This isn't really the old style, but it's close enough */
12746     if (gameInfo.resultDetails != NULL &&
12747         gameInfo.resultDetails[0] != NULLCHAR) {
12748         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12749                 gameInfo.resultDetails);
12750     } else {
12751         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12752     }
12753
12754     fclose(f);
12755     return TRUE;
12756 }
12757
12758 /* Save the current game to open file f and close the file */
12759 int
12760 SaveGame (FILE *f, int dummy, char *dummy2)
12761 {
12762     if (gameMode == EditPosition) EditPositionDone(TRUE);
12763     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12764     if (appData.oldSaveStyle)
12765       return SaveGameOldStyle(f);
12766     else
12767       return SaveGamePGN(f);
12768 }
12769
12770 /* Save the current position to the given file */
12771 int
12772 SavePositionToFile (char *filename)
12773 {
12774     FILE *f;
12775     char buf[MSG_SIZ];
12776
12777     if (strcmp(filename, "-") == 0) {
12778         return SavePosition(stdout, 0, NULL);
12779     } else {
12780         f = fopen(filename, "a");
12781         if (f == NULL) {
12782             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12783             DisplayError(buf, errno);
12784             return FALSE;
12785         } else {
12786             safeStrCpy(buf, lastMsg, MSG_SIZ);
12787             DisplayMessage(_("Waiting for access to save file"), "");
12788             flock(fileno(f), LOCK_EX); // [HGM] lock
12789             DisplayMessage(_("Saving position"), "");
12790             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12791             SavePosition(f, 0, NULL);
12792             DisplayMessage(buf, "");
12793             return TRUE;
12794         }
12795     }
12796 }
12797
12798 /* Save the current position to the given open file and close the file */
12799 int
12800 SavePosition (FILE *f, int dummy, char *dummy2)
12801 {
12802     time_t tm;
12803     char *fen;
12804
12805     if (gameMode == EditPosition) EditPositionDone(TRUE);
12806     if (appData.oldSaveStyle) {
12807         tm = time((time_t *) NULL);
12808
12809         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12810         PrintOpponents(f);
12811         fprintf(f, "[--------------\n");
12812         PrintPosition(f, currentMove);
12813         fprintf(f, "--------------]\n");
12814     } else {
12815         fen = PositionToFEN(currentMove, NULL);
12816         fprintf(f, "%s\n", fen);
12817         free(fen);
12818     }
12819     fclose(f);
12820     return TRUE;
12821 }
12822
12823 void
12824 ReloadCmailMsgEvent (int unregister)
12825 {
12826 #if !WIN32
12827     static char *inFilename = NULL;
12828     static char *outFilename;
12829     int i;
12830     struct stat inbuf, outbuf;
12831     int status;
12832
12833     /* Any registered moves are unregistered if unregister is set, */
12834     /* i.e. invoked by the signal handler */
12835     if (unregister) {
12836         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12837             cmailMoveRegistered[i] = FALSE;
12838             if (cmailCommentList[i] != NULL) {
12839                 free(cmailCommentList[i]);
12840                 cmailCommentList[i] = NULL;
12841             }
12842         }
12843         nCmailMovesRegistered = 0;
12844     }
12845
12846     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12847         cmailResult[i] = CMAIL_NOT_RESULT;
12848     }
12849     nCmailResults = 0;
12850
12851     if (inFilename == NULL) {
12852         /* Because the filenames are static they only get malloced once  */
12853         /* and they never get freed                                      */
12854         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12855         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12856
12857         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12858         sprintf(outFilename, "%s.out", appData.cmailGameName);
12859     }
12860
12861     status = stat(outFilename, &outbuf);
12862     if (status < 0) {
12863         cmailMailedMove = FALSE;
12864     } else {
12865         status = stat(inFilename, &inbuf);
12866         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12867     }
12868
12869     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12870        counts the games, notes how each one terminated, etc.
12871
12872        It would be nice to remove this kludge and instead gather all
12873        the information while building the game list.  (And to keep it
12874        in the game list nodes instead of having a bunch of fixed-size
12875        parallel arrays.)  Note this will require getting each game's
12876        termination from the PGN tags, as the game list builder does
12877        not process the game moves.  --mann
12878        */
12879     cmailMsgLoaded = TRUE;
12880     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12881
12882     /* Load first game in the file or popup game menu */
12883     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12884
12885 #endif /* !WIN32 */
12886     return;
12887 }
12888
12889 int
12890 RegisterMove ()
12891 {
12892     FILE *f;
12893     char string[MSG_SIZ];
12894
12895     if (   cmailMailedMove
12896         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12897         return TRUE;            /* Allow free viewing  */
12898     }
12899
12900     /* Unregister move to ensure that we don't leave RegisterMove        */
12901     /* with the move registered when the conditions for registering no   */
12902     /* longer hold                                                       */
12903     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12904         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12905         nCmailMovesRegistered --;
12906
12907         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12908           {
12909               free(cmailCommentList[lastLoadGameNumber - 1]);
12910               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12911           }
12912     }
12913
12914     if (cmailOldMove == -1) {
12915         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12916         return FALSE;
12917     }
12918
12919     if (currentMove > cmailOldMove + 1) {
12920         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12921         return FALSE;
12922     }
12923
12924     if (currentMove < cmailOldMove) {
12925         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12926         return FALSE;
12927     }
12928
12929     if (forwardMostMove > currentMove) {
12930         /* Silently truncate extra moves */
12931         TruncateGame();
12932     }
12933
12934     if (   (currentMove == cmailOldMove + 1)
12935         || (   (currentMove == cmailOldMove)
12936             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12937                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12938         if (gameInfo.result != GameUnfinished) {
12939             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12940         }
12941
12942         if (commentList[currentMove] != NULL) {
12943             cmailCommentList[lastLoadGameNumber - 1]
12944               = StrSave(commentList[currentMove]);
12945         }
12946         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12947
12948         if (appData.debugMode)
12949           fprintf(debugFP, "Saving %s for game %d\n",
12950                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12951
12952         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12953
12954         f = fopen(string, "w");
12955         if (appData.oldSaveStyle) {
12956             SaveGameOldStyle(f); /* also closes the file */
12957
12958             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12959             f = fopen(string, "w");
12960             SavePosition(f, 0, NULL); /* also closes the file */
12961         } else {
12962             fprintf(f, "{--------------\n");
12963             PrintPosition(f, currentMove);
12964             fprintf(f, "--------------}\n\n");
12965
12966             SaveGame(f, 0, NULL); /* also closes the file*/
12967         }
12968
12969         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12970         nCmailMovesRegistered ++;
12971     } else if (nCmailGames == 1) {
12972         DisplayError(_("You have not made a move yet"), 0);
12973         return FALSE;
12974     }
12975
12976     return TRUE;
12977 }
12978
12979 void
12980 MailMoveEvent ()
12981 {
12982 #if !WIN32
12983     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12984     FILE *commandOutput;
12985     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12986     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12987     int nBuffers;
12988     int i;
12989     int archived;
12990     char *arcDir;
12991
12992     if (! cmailMsgLoaded) {
12993         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12994         return;
12995     }
12996
12997     if (nCmailGames == nCmailResults) {
12998         DisplayError(_("No unfinished games"), 0);
12999         return;
13000     }
13001
13002 #if CMAIL_PROHIBIT_REMAIL
13003     if (cmailMailedMove) {
13004       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);
13005         DisplayError(msg, 0);
13006         return;
13007     }
13008 #endif
13009
13010     if (! (cmailMailedMove || RegisterMove())) return;
13011
13012     if (   cmailMailedMove
13013         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13014       snprintf(string, MSG_SIZ, partCommandString,
13015                appData.debugMode ? " -v" : "", appData.cmailGameName);
13016         commandOutput = popen(string, "r");
13017
13018         if (commandOutput == NULL) {
13019             DisplayError(_("Failed to invoke cmail"), 0);
13020         } else {
13021             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13022                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13023             }
13024             if (nBuffers > 1) {
13025                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13026                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13027                 nBytes = MSG_SIZ - 1;
13028             } else {
13029                 (void) memcpy(msg, buffer, nBytes);
13030             }
13031             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13032
13033             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13034                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13035
13036                 archived = TRUE;
13037                 for (i = 0; i < nCmailGames; i ++) {
13038                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13039                         archived = FALSE;
13040                     }
13041                 }
13042                 if (   archived
13043                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13044                         != NULL)) {
13045                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13046                            arcDir,
13047                            appData.cmailGameName,
13048                            gameInfo.date);
13049                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13050                     cmailMsgLoaded = FALSE;
13051                 }
13052             }
13053
13054             DisplayInformation(msg);
13055             pclose(commandOutput);
13056         }
13057     } else {
13058         if ((*cmailMsg) != '\0') {
13059             DisplayInformation(cmailMsg);
13060         }
13061     }
13062
13063     return;
13064 #endif /* !WIN32 */
13065 }
13066
13067 char *
13068 CmailMsg ()
13069 {
13070 #if WIN32
13071     return NULL;
13072 #else
13073     int  prependComma = 0;
13074     char number[5];
13075     char string[MSG_SIZ];       /* Space for game-list */
13076     int  i;
13077
13078     if (!cmailMsgLoaded) return "";
13079
13080     if (cmailMailedMove) {
13081       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13082     } else {
13083         /* Create a list of games left */
13084       snprintf(string, MSG_SIZ, "[");
13085         for (i = 0; i < nCmailGames; i ++) {
13086             if (! (   cmailMoveRegistered[i]
13087                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13088                 if (prependComma) {
13089                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13090                 } else {
13091                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13092                     prependComma = 1;
13093                 }
13094
13095                 strcat(string, number);
13096             }
13097         }
13098         strcat(string, "]");
13099
13100         if (nCmailMovesRegistered + nCmailResults == 0) {
13101             switch (nCmailGames) {
13102               case 1:
13103                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13104                 break;
13105
13106               case 2:
13107                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13108                 break;
13109
13110               default:
13111                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13112                          nCmailGames);
13113                 break;
13114             }
13115         } else {
13116             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13117               case 1:
13118                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13119                          string);
13120                 break;
13121
13122               case 0:
13123                 if (nCmailResults == nCmailGames) {
13124                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13125                 } else {
13126                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13127                 }
13128                 break;
13129
13130               default:
13131                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13132                          string);
13133             }
13134         }
13135     }
13136     return cmailMsg;
13137 #endif /* WIN32 */
13138 }
13139
13140 void
13141 ResetGameEvent ()
13142 {
13143     if (gameMode == Training)
13144       SetTrainingModeOff();
13145
13146     Reset(TRUE, TRUE);
13147     cmailMsgLoaded = FALSE;
13148     if (appData.icsActive) {
13149       SendToICS(ics_prefix);
13150       SendToICS("refresh\n");
13151     }
13152 }
13153
13154 void
13155 ExitEvent (int status)
13156 {
13157     exiting++;
13158     if (exiting > 2) {
13159       /* Give up on clean exit */
13160       exit(status);
13161     }
13162     if (exiting > 1) {
13163       /* Keep trying for clean exit */
13164       return;
13165     }
13166
13167     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13168
13169     if (telnetISR != NULL) {
13170       RemoveInputSource(telnetISR);
13171     }
13172     if (icsPR != NoProc) {
13173       DestroyChildProcess(icsPR, TRUE);
13174     }
13175
13176     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13177     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13178
13179     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13180     /* make sure this other one finishes before killing it!                  */
13181     if(endingGame) { int count = 0;
13182         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13183         while(endingGame && count++ < 10) DoSleep(1);
13184         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13185     }
13186
13187     /* Kill off chess programs */
13188     if (first.pr != NoProc) {
13189         ExitAnalyzeMode();
13190
13191         DoSleep( appData.delayBeforeQuit );
13192         SendToProgram("quit\n", &first);
13193         DoSleep( appData.delayAfterQuit );
13194         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13195     }
13196     if (second.pr != NoProc) {
13197         DoSleep( appData.delayBeforeQuit );
13198         SendToProgram("quit\n", &second);
13199         DoSleep( appData.delayAfterQuit );
13200         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13201     }
13202     if (first.isr != NULL) {
13203         RemoveInputSource(first.isr);
13204     }
13205     if (second.isr != NULL) {
13206         RemoveInputSource(second.isr);
13207     }
13208
13209     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13210     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13211
13212     ShutDownFrontEnd();
13213     exit(status);
13214 }
13215
13216 void
13217 PauseEvent ()
13218 {
13219     if (appData.debugMode)
13220         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13221     if (pausing) {
13222         pausing = FALSE;
13223         ModeHighlight();
13224         if (gameMode == MachinePlaysWhite ||
13225             gameMode == MachinePlaysBlack) {
13226             StartClocks();
13227         } else {
13228             DisplayBothClocks();
13229         }
13230         if (gameMode == PlayFromGameFile) {
13231             if (appData.timeDelay >= 0)
13232                 AutoPlayGameLoop();
13233         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13234             Reset(FALSE, TRUE);
13235             SendToICS(ics_prefix);
13236             SendToICS("refresh\n");
13237         } else if (currentMove < forwardMostMove) {
13238             ForwardInner(forwardMostMove);
13239         }
13240         pauseExamInvalid = FALSE;
13241     } else {
13242         switch (gameMode) {
13243           default:
13244             return;
13245           case IcsExamining:
13246             pauseExamForwardMostMove = forwardMostMove;
13247             pauseExamInvalid = FALSE;
13248             /* fall through */
13249           case IcsObserving:
13250           case IcsPlayingWhite:
13251           case IcsPlayingBlack:
13252             pausing = TRUE;
13253             ModeHighlight();
13254             return;
13255           case PlayFromGameFile:
13256             (void) StopLoadGameTimer();
13257             pausing = TRUE;
13258             ModeHighlight();
13259             break;
13260           case BeginningOfGame:
13261             if (appData.icsActive) return;
13262             /* else fall through */
13263           case MachinePlaysWhite:
13264           case MachinePlaysBlack:
13265           case TwoMachinesPlay:
13266             if (forwardMostMove == 0)
13267               return;           /* don't pause if no one has moved */
13268             if ((gameMode == MachinePlaysWhite &&
13269                  !WhiteOnMove(forwardMostMove)) ||
13270                 (gameMode == MachinePlaysBlack &&
13271                  WhiteOnMove(forwardMostMove))) {
13272                 StopClocks();
13273             }
13274             pausing = TRUE;
13275             ModeHighlight();
13276             break;
13277         }
13278     }
13279 }
13280
13281 void
13282 EditCommentEvent ()
13283 {
13284     char title[MSG_SIZ];
13285
13286     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13287       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13288     } else {
13289       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13290                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13291                parseList[currentMove - 1]);
13292     }
13293
13294     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13295 }
13296
13297
13298 void
13299 EditTagsEvent ()
13300 {
13301     char *tags = PGNTags(&gameInfo);
13302     bookUp = FALSE;
13303     EditTagsPopUp(tags, NULL);
13304     free(tags);
13305 }
13306
13307 void
13308 AnalyzeModeEvent ()
13309 {
13310     if (appData.noChessProgram || gameMode == AnalyzeMode)
13311       return;
13312
13313     if (gameMode != AnalyzeFile) {
13314         if (!appData.icsEngineAnalyze) {
13315                EditGameEvent();
13316                if (gameMode != EditGame) return;
13317         }
13318         ResurrectChessProgram();
13319         SendToProgram("analyze\n", &first);
13320         first.analyzing = TRUE;
13321         /*first.maybeThinking = TRUE;*/
13322         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13323         EngineOutputPopUp();
13324     }
13325     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13326     pausing = FALSE;
13327     ModeHighlight();
13328     SetGameInfo();
13329
13330     StartAnalysisClock();
13331     GetTimeMark(&lastNodeCountTime);
13332     lastNodeCount = 0;
13333 }
13334
13335 void
13336 AnalyzeFileEvent ()
13337 {
13338     if (appData.noChessProgram || gameMode == AnalyzeFile)
13339       return;
13340
13341     if (gameMode != AnalyzeMode) {
13342         EditGameEvent();
13343         if (gameMode != EditGame) return;
13344         ResurrectChessProgram();
13345         SendToProgram("analyze\n", &first);
13346         first.analyzing = TRUE;
13347         /*first.maybeThinking = TRUE;*/
13348         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13349         EngineOutputPopUp();
13350     }
13351     gameMode = AnalyzeFile;
13352     pausing = FALSE;
13353     ModeHighlight();
13354     SetGameInfo();
13355
13356     StartAnalysisClock();
13357     GetTimeMark(&lastNodeCountTime);
13358     lastNodeCount = 0;
13359     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13360 }
13361
13362 void
13363 MachineWhiteEvent ()
13364 {
13365     char buf[MSG_SIZ];
13366     char *bookHit = NULL;
13367
13368     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13369       return;
13370
13371
13372     if (gameMode == PlayFromGameFile ||
13373         gameMode == TwoMachinesPlay  ||
13374         gameMode == Training         ||
13375         gameMode == AnalyzeMode      ||
13376         gameMode == EndOfGame)
13377         EditGameEvent();
13378
13379     if (gameMode == EditPosition)
13380         EditPositionDone(TRUE);
13381
13382     if (!WhiteOnMove(currentMove)) {
13383         DisplayError(_("It is not White's turn"), 0);
13384         return;
13385     }
13386
13387     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13388       ExitAnalyzeMode();
13389
13390     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13391         gameMode == AnalyzeFile)
13392         TruncateGame();
13393
13394     ResurrectChessProgram();    /* in case it isn't running */
13395     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13396         gameMode = MachinePlaysWhite;
13397         ResetClocks();
13398     } else
13399     gameMode = MachinePlaysWhite;
13400     pausing = FALSE;
13401     ModeHighlight();
13402     SetGameInfo();
13403     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13404     DisplayTitle(buf);
13405     if (first.sendName) {
13406       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13407       SendToProgram(buf, &first);
13408     }
13409     if (first.sendTime) {
13410       if (first.useColors) {
13411         SendToProgram("black\n", &first); /*gnu kludge*/
13412       }
13413       SendTimeRemaining(&first, TRUE);
13414     }
13415     if (first.useColors) {
13416       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13417     }
13418     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13419     SetMachineThinkingEnables();
13420     first.maybeThinking = TRUE;
13421     StartClocks();
13422     firstMove = FALSE;
13423
13424     if (appData.autoFlipView && !flipView) {
13425       flipView = !flipView;
13426       DrawPosition(FALSE, NULL);
13427       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13428     }
13429
13430     if(bookHit) { // [HGM] book: simulate book reply
13431         static char bookMove[MSG_SIZ]; // a bit generous?
13432
13433         programStats.nodes = programStats.depth = programStats.time =
13434         programStats.score = programStats.got_only_move = 0;
13435         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13436
13437         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13438         strcat(bookMove, bookHit);
13439         HandleMachineMove(bookMove, &first);
13440     }
13441 }
13442
13443 void
13444 MachineBlackEvent ()
13445 {
13446   char buf[MSG_SIZ];
13447   char *bookHit = NULL;
13448
13449     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13450         return;
13451
13452
13453     if (gameMode == PlayFromGameFile ||
13454         gameMode == TwoMachinesPlay  ||
13455         gameMode == Training         ||
13456         gameMode == AnalyzeMode      ||
13457         gameMode == EndOfGame)
13458         EditGameEvent();
13459
13460     if (gameMode == EditPosition)
13461         EditPositionDone(TRUE);
13462
13463     if (WhiteOnMove(currentMove)) {
13464         DisplayError(_("It is not Black's turn"), 0);
13465         return;
13466     }
13467
13468     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13469       ExitAnalyzeMode();
13470
13471     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13472         gameMode == AnalyzeFile)
13473         TruncateGame();
13474
13475     ResurrectChessProgram();    /* in case it isn't running */
13476     gameMode = MachinePlaysBlack;
13477     pausing = FALSE;
13478     ModeHighlight();
13479     SetGameInfo();
13480     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13481     DisplayTitle(buf);
13482     if (first.sendName) {
13483       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13484       SendToProgram(buf, &first);
13485     }
13486     if (first.sendTime) {
13487       if (first.useColors) {
13488         SendToProgram("white\n", &first); /*gnu kludge*/
13489       }
13490       SendTimeRemaining(&first, FALSE);
13491     }
13492     if (first.useColors) {
13493       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13494     }
13495     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13496     SetMachineThinkingEnables();
13497     first.maybeThinking = TRUE;
13498     StartClocks();
13499
13500     if (appData.autoFlipView && flipView) {
13501       flipView = !flipView;
13502       DrawPosition(FALSE, NULL);
13503       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13504     }
13505     if(bookHit) { // [HGM] book: simulate book reply
13506         static char bookMove[MSG_SIZ]; // a bit generous?
13507
13508         programStats.nodes = programStats.depth = programStats.time =
13509         programStats.score = programStats.got_only_move = 0;
13510         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13511
13512         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13513         strcat(bookMove, bookHit);
13514         HandleMachineMove(bookMove, &first);
13515     }
13516 }
13517
13518
13519 void
13520 DisplayTwoMachinesTitle ()
13521 {
13522     char buf[MSG_SIZ];
13523     if (appData.matchGames > 0) {
13524         if(appData.tourneyFile[0]) {
13525           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13526                    gameInfo.white, _("vs."), gameInfo.black,
13527                    nextGame+1, appData.matchGames+1,
13528                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13529         } else 
13530         if (first.twoMachinesColor[0] == 'w') {
13531           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13532                    gameInfo.white, _("vs."),  gameInfo.black,
13533                    first.matchWins, second.matchWins,
13534                    matchGame - 1 - (first.matchWins + second.matchWins));
13535         } else {
13536           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13537                    gameInfo.white, _("vs."), gameInfo.black,
13538                    second.matchWins, first.matchWins,
13539                    matchGame - 1 - (first.matchWins + second.matchWins));
13540         }
13541     } else {
13542       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13543     }
13544     DisplayTitle(buf);
13545 }
13546
13547 void
13548 SettingsMenuIfReady ()
13549 {
13550   if (second.lastPing != second.lastPong) {
13551     DisplayMessage("", _("Waiting for second chess program"));
13552     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13553     return;
13554   }
13555   ThawUI();
13556   DisplayMessage("", "");
13557   SettingsPopUp(&second);
13558 }
13559
13560 int
13561 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13562 {
13563     char buf[MSG_SIZ];
13564     if (cps->pr == NoProc) {
13565         StartChessProgram(cps);
13566         if (cps->protocolVersion == 1) {
13567           retry();
13568         } else {
13569           /* kludge: allow timeout for initial "feature" command */
13570           FreezeUI();
13571           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13572           DisplayMessage("", buf);
13573           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13574         }
13575         return 1;
13576     }
13577     return 0;
13578 }
13579
13580 void
13581 TwoMachinesEvent P((void))
13582 {
13583     int i;
13584     char buf[MSG_SIZ];
13585     ChessProgramState *onmove;
13586     char *bookHit = NULL;
13587     static int stalling = 0;
13588     TimeMark now;
13589     long wait;
13590
13591     if (appData.noChessProgram) return;
13592
13593     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13594         DisplayError("second engine does not play this", 0);
13595         return;
13596     }
13597
13598     switch (gameMode) {
13599       case TwoMachinesPlay:
13600         return;
13601       case MachinePlaysWhite:
13602       case MachinePlaysBlack:
13603         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13604             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13605             return;
13606         }
13607         /* fall through */
13608       case BeginningOfGame:
13609       case PlayFromGameFile:
13610       case EndOfGame:
13611         EditGameEvent();
13612         if (gameMode != EditGame) return;
13613         break;
13614       case EditPosition:
13615         EditPositionDone(TRUE);
13616         break;
13617       case AnalyzeMode:
13618       case AnalyzeFile:
13619         ExitAnalyzeMode();
13620         break;
13621       case EditGame:
13622       default:
13623         break;
13624     }
13625
13626 //    forwardMostMove = currentMove;
13627     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13628
13629     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13630
13631     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13632     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13633       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13634       return;
13635     }
13636     if(!stalling) {
13637       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13638       SendToProgram("force\n", &second);
13639       stalling = 1;
13640       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13641       return;
13642     }
13643     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13644     if(appData.matchPause>10000 || appData.matchPause<10)
13645                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13646     wait = SubtractTimeMarks(&now, &pauseStart);
13647     if(wait < appData.matchPause) {
13648         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13649         return;
13650     }
13651     // we are now committed to starting the game
13652     stalling = 0;
13653     DisplayMessage("", "");
13654     if (startedFromSetupPosition) {
13655         SendBoard(&second, backwardMostMove);
13656     if (appData.debugMode) {
13657         fprintf(debugFP, "Two Machines\n");
13658     }
13659     }
13660     for (i = backwardMostMove; i < forwardMostMove; i++) {
13661         SendMoveToProgram(i, &second);
13662     }
13663
13664     gameMode = TwoMachinesPlay;
13665     pausing = FALSE;
13666     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13667     SetGameInfo();
13668     DisplayTwoMachinesTitle();
13669     firstMove = TRUE;
13670     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13671         onmove = &first;
13672     } else {
13673         onmove = &second;
13674     }
13675     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13676     SendToProgram(first.computerString, &first);
13677     if (first.sendName) {
13678       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13679       SendToProgram(buf, &first);
13680     }
13681     SendToProgram(second.computerString, &second);
13682     if (second.sendName) {
13683       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13684       SendToProgram(buf, &second);
13685     }
13686
13687     ResetClocks();
13688     if (!first.sendTime || !second.sendTime) {
13689         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13690         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13691     }
13692     if (onmove->sendTime) {
13693       if (onmove->useColors) {
13694         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13695       }
13696       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13697     }
13698     if (onmove->useColors) {
13699       SendToProgram(onmove->twoMachinesColor, onmove);
13700     }
13701     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13702 //    SendToProgram("go\n", onmove);
13703     onmove->maybeThinking = TRUE;
13704     SetMachineThinkingEnables();
13705
13706     StartClocks();
13707
13708     if(bookHit) { // [HGM] book: simulate book reply
13709         static char bookMove[MSG_SIZ]; // a bit generous?
13710
13711         programStats.nodes = programStats.depth = programStats.time =
13712         programStats.score = programStats.got_only_move = 0;
13713         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13714
13715         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13716         strcat(bookMove, bookHit);
13717         savedMessage = bookMove; // args for deferred call
13718         savedState = onmove;
13719         ScheduleDelayedEvent(DeferredBookMove, 1);
13720     }
13721 }
13722
13723 void
13724 TrainingEvent ()
13725 {
13726     if (gameMode == Training) {
13727       SetTrainingModeOff();
13728       gameMode = PlayFromGameFile;
13729       DisplayMessage("", _("Training mode off"));
13730     } else {
13731       gameMode = Training;
13732       animateTraining = appData.animate;
13733
13734       /* make sure we are not already at the end of the game */
13735       if (currentMove < forwardMostMove) {
13736         SetTrainingModeOn();
13737         DisplayMessage("", _("Training mode on"));
13738       } else {
13739         gameMode = PlayFromGameFile;
13740         DisplayError(_("Already at end of game"), 0);
13741       }
13742     }
13743     ModeHighlight();
13744 }
13745
13746 void
13747 IcsClientEvent ()
13748 {
13749     if (!appData.icsActive) return;
13750     switch (gameMode) {
13751       case IcsPlayingWhite:
13752       case IcsPlayingBlack:
13753       case IcsObserving:
13754       case IcsIdle:
13755       case BeginningOfGame:
13756       case IcsExamining:
13757         return;
13758
13759       case EditGame:
13760         break;
13761
13762       case EditPosition:
13763         EditPositionDone(TRUE);
13764         break;
13765
13766       case AnalyzeMode:
13767       case AnalyzeFile:
13768         ExitAnalyzeMode();
13769         break;
13770
13771       default:
13772         EditGameEvent();
13773         break;
13774     }
13775
13776     gameMode = IcsIdle;
13777     ModeHighlight();
13778     return;
13779 }
13780
13781 void
13782 EditGameEvent ()
13783 {
13784     int i;
13785
13786     switch (gameMode) {
13787       case Training:
13788         SetTrainingModeOff();
13789         break;
13790       case MachinePlaysWhite:
13791       case MachinePlaysBlack:
13792       case BeginningOfGame:
13793         SendToProgram("force\n", &first);
13794         SetUserThinkingEnables();
13795         break;
13796       case PlayFromGameFile:
13797         (void) StopLoadGameTimer();
13798         if (gameFileFP != NULL) {
13799             gameFileFP = NULL;
13800         }
13801         break;
13802       case EditPosition:
13803         EditPositionDone(TRUE);
13804         break;
13805       case AnalyzeMode:
13806       case AnalyzeFile:
13807         ExitAnalyzeMode();
13808         SendToProgram("force\n", &first);
13809         break;
13810       case TwoMachinesPlay:
13811         GameEnds(EndOfFile, NULL, GE_PLAYER);
13812         ResurrectChessProgram();
13813         SetUserThinkingEnables();
13814         break;
13815       case EndOfGame:
13816         ResurrectChessProgram();
13817         break;
13818       case IcsPlayingBlack:
13819       case IcsPlayingWhite:
13820         DisplayError(_("Warning: You are still playing a game"), 0);
13821         break;
13822       case IcsObserving:
13823         DisplayError(_("Warning: You are still observing a game"), 0);
13824         break;
13825       case IcsExamining:
13826         DisplayError(_("Warning: You are still examining a game"), 0);
13827         break;
13828       case IcsIdle:
13829         break;
13830       case EditGame:
13831       default:
13832         return;
13833     }
13834
13835     pausing = FALSE;
13836     StopClocks();
13837     first.offeredDraw = second.offeredDraw = 0;
13838
13839     if (gameMode == PlayFromGameFile) {
13840         whiteTimeRemaining = timeRemaining[0][currentMove];
13841         blackTimeRemaining = timeRemaining[1][currentMove];
13842         DisplayTitle("");
13843     }
13844
13845     if (gameMode == MachinePlaysWhite ||
13846         gameMode == MachinePlaysBlack ||
13847         gameMode == TwoMachinesPlay ||
13848         gameMode == EndOfGame) {
13849         i = forwardMostMove;
13850         while (i > currentMove) {
13851             SendToProgram("undo\n", &first);
13852             i--;
13853         }
13854         if(!adjustedClock) {
13855         whiteTimeRemaining = timeRemaining[0][currentMove];
13856         blackTimeRemaining = timeRemaining[1][currentMove];
13857         DisplayBothClocks();
13858         }
13859         if (whiteFlag || blackFlag) {
13860             whiteFlag = blackFlag = 0;
13861         }
13862         DisplayTitle("");
13863     }
13864
13865     gameMode = EditGame;
13866     ModeHighlight();
13867     SetGameInfo();
13868 }
13869
13870
13871 void
13872 EditPositionEvent ()
13873 {
13874     if (gameMode == EditPosition) {
13875         EditGameEvent();
13876         return;
13877     }
13878
13879     EditGameEvent();
13880     if (gameMode != EditGame) return;
13881
13882     gameMode = EditPosition;
13883     ModeHighlight();
13884     SetGameInfo();
13885     if (currentMove > 0)
13886       CopyBoard(boards[0], boards[currentMove]);
13887
13888     blackPlaysFirst = !WhiteOnMove(currentMove);
13889     ResetClocks();
13890     currentMove = forwardMostMove = backwardMostMove = 0;
13891     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13892     DisplayMove(-1);
13893     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13894 }
13895
13896 void
13897 ExitAnalyzeMode ()
13898 {
13899     /* [DM] icsEngineAnalyze - possible call from other functions */
13900     if (appData.icsEngineAnalyze) {
13901         appData.icsEngineAnalyze = FALSE;
13902
13903         DisplayMessage("",_("Close ICS engine analyze..."));
13904     }
13905     if (first.analysisSupport && first.analyzing) {
13906       SendToProgram("exit\n", &first);
13907       first.analyzing = FALSE;
13908     }
13909     thinkOutput[0] = NULLCHAR;
13910 }
13911
13912 void
13913 EditPositionDone (Boolean fakeRights)
13914 {
13915     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13916
13917     startedFromSetupPosition = TRUE;
13918     InitChessProgram(&first, FALSE);
13919     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13920       boards[0][EP_STATUS] = EP_NONE;
13921       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13922     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13923         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13924         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13925       } else boards[0][CASTLING][2] = NoRights;
13926     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13927         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13928         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13929       } else boards[0][CASTLING][5] = NoRights;
13930     }
13931     SendToProgram("force\n", &first);
13932     if (blackPlaysFirst) {
13933         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13934         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13935         currentMove = forwardMostMove = backwardMostMove = 1;
13936         CopyBoard(boards[1], boards[0]);
13937     } else {
13938         currentMove = forwardMostMove = backwardMostMove = 0;
13939     }
13940     SendBoard(&first, forwardMostMove);
13941     if (appData.debugMode) {
13942         fprintf(debugFP, "EditPosDone\n");
13943     }
13944     DisplayTitle("");
13945     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13946     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13947     gameMode = EditGame;
13948     ModeHighlight();
13949     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13950     ClearHighlights(); /* [AS] */
13951 }
13952
13953 /* Pause for `ms' milliseconds */
13954 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13955 void
13956 TimeDelay (long ms)
13957 {
13958     TimeMark m1, m2;
13959
13960     GetTimeMark(&m1);
13961     do {
13962         GetTimeMark(&m2);
13963     } while (SubtractTimeMarks(&m2, &m1) < ms);
13964 }
13965
13966 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13967 void
13968 SendMultiLineToICS (char *buf)
13969 {
13970     char temp[MSG_SIZ+1], *p;
13971     int len;
13972
13973     len = strlen(buf);
13974     if (len > MSG_SIZ)
13975       len = MSG_SIZ;
13976
13977     strncpy(temp, buf, len);
13978     temp[len] = 0;
13979
13980     p = temp;
13981     while (*p) {
13982         if (*p == '\n' || *p == '\r')
13983           *p = ' ';
13984         ++p;
13985     }
13986
13987     strcat(temp, "\n");
13988     SendToICS(temp);
13989     SendToPlayer(temp, strlen(temp));
13990 }
13991
13992 void
13993 SetWhiteToPlayEvent ()
13994 {
13995     if (gameMode == EditPosition) {
13996         blackPlaysFirst = FALSE;
13997         DisplayBothClocks();    /* works because currentMove is 0 */
13998     } else if (gameMode == IcsExamining) {
13999         SendToICS(ics_prefix);
14000         SendToICS("tomove white\n");
14001     }
14002 }
14003
14004 void
14005 SetBlackToPlayEvent ()
14006 {
14007     if (gameMode == EditPosition) {
14008         blackPlaysFirst = TRUE;
14009         currentMove = 1;        /* kludge */
14010         DisplayBothClocks();
14011         currentMove = 0;
14012     } else if (gameMode == IcsExamining) {
14013         SendToICS(ics_prefix);
14014         SendToICS("tomove black\n");
14015     }
14016 }
14017
14018 void
14019 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14020 {
14021     char buf[MSG_SIZ];
14022     ChessSquare piece = boards[0][y][x];
14023
14024     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14025
14026     switch (selection) {
14027       case ClearBoard:
14028         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14029             SendToICS(ics_prefix);
14030             SendToICS("bsetup clear\n");
14031         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14032             SendToICS(ics_prefix);
14033             SendToICS("clearboard\n");
14034         } else {
14035             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14036                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14037                 for (y = 0; y < BOARD_HEIGHT; y++) {
14038                     if (gameMode == IcsExamining) {
14039                         if (boards[currentMove][y][x] != EmptySquare) {
14040                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14041                                     AAA + x, ONE + y);
14042                             SendToICS(buf);
14043                         }
14044                     } else {
14045                         boards[0][y][x] = p;
14046                     }
14047                 }
14048             }
14049         }
14050         if (gameMode == EditPosition) {
14051             DrawPosition(FALSE, boards[0]);
14052         }
14053         break;
14054
14055       case WhitePlay:
14056         SetWhiteToPlayEvent();
14057         break;
14058
14059       case BlackPlay:
14060         SetBlackToPlayEvent();
14061         break;
14062
14063       case EmptySquare:
14064         if (gameMode == IcsExamining) {
14065             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14066             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14067             SendToICS(buf);
14068         } else {
14069             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14070                 if(x == BOARD_LEFT-2) {
14071                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14072                     boards[0][y][1] = 0;
14073                 } else
14074                 if(x == BOARD_RGHT+1) {
14075                     if(y >= gameInfo.holdingsSize) break;
14076                     boards[0][y][BOARD_WIDTH-2] = 0;
14077                 } else break;
14078             }
14079             boards[0][y][x] = EmptySquare;
14080             DrawPosition(FALSE, boards[0]);
14081         }
14082         break;
14083
14084       case PromotePiece:
14085         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14086            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14087             selection = (ChessSquare) (PROMOTED piece);
14088         } else if(piece == EmptySquare) selection = WhiteSilver;
14089         else selection = (ChessSquare)((int)piece - 1);
14090         goto defaultlabel;
14091
14092       case DemotePiece:
14093         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14094            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14095             selection = (ChessSquare) (DEMOTED piece);
14096         } else if(piece == EmptySquare) selection = BlackSilver;
14097         else selection = (ChessSquare)((int)piece + 1);
14098         goto defaultlabel;
14099
14100       case WhiteQueen:
14101       case BlackQueen:
14102         if(gameInfo.variant == VariantShatranj ||
14103            gameInfo.variant == VariantXiangqi  ||
14104            gameInfo.variant == VariantCourier  ||
14105            gameInfo.variant == VariantMakruk     )
14106             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14107         goto defaultlabel;
14108
14109       case WhiteKing:
14110       case BlackKing:
14111         if(gameInfo.variant == VariantXiangqi)
14112             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14113         if(gameInfo.variant == VariantKnightmate)
14114             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14115       default:
14116         defaultlabel:
14117         if (gameMode == IcsExamining) {
14118             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14119             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14120                      PieceToChar(selection), AAA + x, ONE + y);
14121             SendToICS(buf);
14122         } else {
14123             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14124                 int n;
14125                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14126                     n = PieceToNumber(selection - BlackPawn);
14127                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14128                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14129                     boards[0][BOARD_HEIGHT-1-n][1]++;
14130                 } else
14131                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14132                     n = PieceToNumber(selection);
14133                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14134                     boards[0][n][BOARD_WIDTH-1] = selection;
14135                     boards[0][n][BOARD_WIDTH-2]++;
14136                 }
14137             } else
14138             boards[0][y][x] = selection;
14139             DrawPosition(TRUE, boards[0]);
14140             ClearHighlights();
14141             fromX = fromY = -1;
14142         }
14143         break;
14144     }
14145 }
14146
14147
14148 void
14149 DropMenuEvent (ChessSquare selection, int x, int y)
14150 {
14151     ChessMove moveType;
14152
14153     switch (gameMode) {
14154       case IcsPlayingWhite:
14155       case MachinePlaysBlack:
14156         if (!WhiteOnMove(currentMove)) {
14157             DisplayMoveError(_("It is Black's turn"));
14158             return;
14159         }
14160         moveType = WhiteDrop;
14161         break;
14162       case IcsPlayingBlack:
14163       case MachinePlaysWhite:
14164         if (WhiteOnMove(currentMove)) {
14165             DisplayMoveError(_("It is White's turn"));
14166             return;
14167         }
14168         moveType = BlackDrop;
14169         break;
14170       case EditGame:
14171         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14172         break;
14173       default:
14174         return;
14175     }
14176
14177     if (moveType == BlackDrop && selection < BlackPawn) {
14178       selection = (ChessSquare) ((int) selection
14179                                  + (int) BlackPawn - (int) WhitePawn);
14180     }
14181     if (boards[currentMove][y][x] != EmptySquare) {
14182         DisplayMoveError(_("That square is occupied"));
14183         return;
14184     }
14185
14186     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14187 }
14188
14189 void
14190 AcceptEvent ()
14191 {
14192     /* Accept a pending offer of any kind from opponent */
14193
14194     if (appData.icsActive) {
14195         SendToICS(ics_prefix);
14196         SendToICS("accept\n");
14197     } else if (cmailMsgLoaded) {
14198         if (currentMove == cmailOldMove &&
14199             commentList[cmailOldMove] != NULL &&
14200             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14201                    "Black offers a draw" : "White offers a draw")) {
14202             TruncateGame();
14203             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14204             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14205         } else {
14206             DisplayError(_("There is no pending offer on this move"), 0);
14207             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14208         }
14209     } else {
14210         /* Not used for offers from chess program */
14211     }
14212 }
14213
14214 void
14215 DeclineEvent ()
14216 {
14217     /* Decline a pending offer of any kind from opponent */
14218
14219     if (appData.icsActive) {
14220         SendToICS(ics_prefix);
14221         SendToICS("decline\n");
14222     } else if (cmailMsgLoaded) {
14223         if (currentMove == cmailOldMove &&
14224             commentList[cmailOldMove] != NULL &&
14225             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14226                    "Black offers a draw" : "White offers a draw")) {
14227 #ifdef NOTDEF
14228             AppendComment(cmailOldMove, "Draw declined", TRUE);
14229             DisplayComment(cmailOldMove - 1, "Draw declined");
14230 #endif /*NOTDEF*/
14231         } else {
14232             DisplayError(_("There is no pending offer on this move"), 0);
14233         }
14234     } else {
14235         /* Not used for offers from chess program */
14236     }
14237 }
14238
14239 void
14240 RematchEvent ()
14241 {
14242     /* Issue ICS rematch command */
14243     if (appData.icsActive) {
14244         SendToICS(ics_prefix);
14245         SendToICS("rematch\n");
14246     }
14247 }
14248
14249 void
14250 CallFlagEvent ()
14251 {
14252     /* Call your opponent's flag (claim a win on time) */
14253     if (appData.icsActive) {
14254         SendToICS(ics_prefix);
14255         SendToICS("flag\n");
14256     } else {
14257         switch (gameMode) {
14258           default:
14259             return;
14260           case MachinePlaysWhite:
14261             if (whiteFlag) {
14262                 if (blackFlag)
14263                   GameEnds(GameIsDrawn, "Both players ran out of time",
14264                            GE_PLAYER);
14265                 else
14266                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14267             } else {
14268                 DisplayError(_("Your opponent is not out of time"), 0);
14269             }
14270             break;
14271           case MachinePlaysBlack:
14272             if (blackFlag) {
14273                 if (whiteFlag)
14274                   GameEnds(GameIsDrawn, "Both players ran out of time",
14275                            GE_PLAYER);
14276                 else
14277                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14278             } else {
14279                 DisplayError(_("Your opponent is not out of time"), 0);
14280             }
14281             break;
14282         }
14283     }
14284 }
14285
14286 void
14287 ClockClick (int which)
14288 {       // [HGM] code moved to back-end from winboard.c
14289         if(which) { // black clock
14290           if (gameMode == EditPosition || gameMode == IcsExamining) {
14291             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14292             SetBlackToPlayEvent();
14293           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14294           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14295           } else if (shiftKey) {
14296             AdjustClock(which, -1);
14297           } else if (gameMode == IcsPlayingWhite ||
14298                      gameMode == MachinePlaysBlack) {
14299             CallFlagEvent();
14300           }
14301         } else { // white clock
14302           if (gameMode == EditPosition || gameMode == IcsExamining) {
14303             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14304             SetWhiteToPlayEvent();
14305           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14306           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14307           } else if (shiftKey) {
14308             AdjustClock(which, -1);
14309           } else if (gameMode == IcsPlayingBlack ||
14310                    gameMode == MachinePlaysWhite) {
14311             CallFlagEvent();
14312           }
14313         }
14314 }
14315
14316 void
14317 DrawEvent ()
14318 {
14319     /* Offer draw or accept pending draw offer from opponent */
14320
14321     if (appData.icsActive) {
14322         /* Note: tournament rules require draw offers to be
14323            made after you make your move but before you punch
14324            your clock.  Currently ICS doesn't let you do that;
14325            instead, you immediately punch your clock after making
14326            a move, but you can offer a draw at any time. */
14327
14328         SendToICS(ics_prefix);
14329         SendToICS("draw\n");
14330         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14331     } else if (cmailMsgLoaded) {
14332         if (currentMove == cmailOldMove &&
14333             commentList[cmailOldMove] != NULL &&
14334             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14335                    "Black offers a draw" : "White offers a draw")) {
14336             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14337             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14338         } else if (currentMove == cmailOldMove + 1) {
14339             char *offer = WhiteOnMove(cmailOldMove) ?
14340               "White offers a draw" : "Black offers a draw";
14341             AppendComment(currentMove, offer, TRUE);
14342             DisplayComment(currentMove - 1, offer);
14343             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14344         } else {
14345             DisplayError(_("You must make your move before offering a draw"), 0);
14346             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14347         }
14348     } else if (first.offeredDraw) {
14349         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14350     } else {
14351         if (first.sendDrawOffers) {
14352             SendToProgram("draw\n", &first);
14353             userOfferedDraw = TRUE;
14354         }
14355     }
14356 }
14357
14358 void
14359 AdjournEvent ()
14360 {
14361     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14362
14363     if (appData.icsActive) {
14364         SendToICS(ics_prefix);
14365         SendToICS("adjourn\n");
14366     } else {
14367         /* Currently GNU Chess doesn't offer or accept Adjourns */
14368     }
14369 }
14370
14371
14372 void
14373 AbortEvent ()
14374 {
14375     /* Offer Abort or accept pending Abort offer from opponent */
14376
14377     if (appData.icsActive) {
14378         SendToICS(ics_prefix);
14379         SendToICS("abort\n");
14380     } else {
14381         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14382     }
14383 }
14384
14385 void
14386 ResignEvent ()
14387 {
14388     /* Resign.  You can do this even if it's not your turn. */
14389
14390     if (appData.icsActive) {
14391         SendToICS(ics_prefix);
14392         SendToICS("resign\n");
14393     } else {
14394         switch (gameMode) {
14395           case MachinePlaysWhite:
14396             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14397             break;
14398           case MachinePlaysBlack:
14399             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14400             break;
14401           case EditGame:
14402             if (cmailMsgLoaded) {
14403                 TruncateGame();
14404                 if (WhiteOnMove(cmailOldMove)) {
14405                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14406                 } else {
14407                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14408                 }
14409                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14410             }
14411             break;
14412           default:
14413             break;
14414         }
14415     }
14416 }
14417
14418
14419 void
14420 StopObservingEvent ()
14421 {
14422     /* Stop observing current games */
14423     SendToICS(ics_prefix);
14424     SendToICS("unobserve\n");
14425 }
14426
14427 void
14428 StopExaminingEvent ()
14429 {
14430     /* Stop observing current game */
14431     SendToICS(ics_prefix);
14432     SendToICS("unexamine\n");
14433 }
14434
14435 void
14436 ForwardInner (int target)
14437 {
14438     int limit; int oldSeekGraphUp = seekGraphUp;
14439
14440     if (appData.debugMode)
14441         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14442                 target, currentMove, forwardMostMove);
14443
14444     if (gameMode == EditPosition)
14445       return;
14446
14447     seekGraphUp = FALSE;
14448     MarkTargetSquares(1);
14449
14450     if (gameMode == PlayFromGameFile && !pausing)
14451       PauseEvent();
14452
14453     if (gameMode == IcsExamining && pausing)
14454       limit = pauseExamForwardMostMove;
14455     else
14456       limit = forwardMostMove;
14457
14458     if (target > limit) target = limit;
14459
14460     if (target > 0 && moveList[target - 1][0]) {
14461         int fromX, fromY, toX, toY;
14462         toX = moveList[target - 1][2] - AAA;
14463         toY = moveList[target - 1][3] - ONE;
14464         if (moveList[target - 1][1] == '@') {
14465             if (appData.highlightLastMove) {
14466                 SetHighlights(-1, -1, toX, toY);
14467             }
14468         } else {
14469             fromX = moveList[target - 1][0] - AAA;
14470             fromY = moveList[target - 1][1] - ONE;
14471             if (target == currentMove + 1) {
14472                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14473             }
14474             if (appData.highlightLastMove) {
14475                 SetHighlights(fromX, fromY, toX, toY);
14476             }
14477         }
14478     }
14479     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14480         gameMode == Training || gameMode == PlayFromGameFile ||
14481         gameMode == AnalyzeFile) {
14482         while (currentMove < target) {
14483             SendMoveToProgram(currentMove++, &first);
14484         }
14485     } else {
14486         currentMove = target;
14487     }
14488
14489     if (gameMode == EditGame || gameMode == EndOfGame) {
14490         whiteTimeRemaining = timeRemaining[0][currentMove];
14491         blackTimeRemaining = timeRemaining[1][currentMove];
14492     }
14493     DisplayBothClocks();
14494     DisplayMove(currentMove - 1);
14495     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14496     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14497     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14498         DisplayComment(currentMove - 1, commentList[currentMove]);
14499     }
14500     ClearMap(); // [HGM] exclude: invalidate map
14501 }
14502
14503
14504 void
14505 ForwardEvent ()
14506 {
14507     if (gameMode == IcsExamining && !pausing) {
14508         SendToICS(ics_prefix);
14509         SendToICS("forward\n");
14510     } else {
14511         ForwardInner(currentMove + 1);
14512     }
14513 }
14514
14515 void
14516 ToEndEvent ()
14517 {
14518     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14519         /* to optimze, we temporarily turn off analysis mode while we feed
14520          * the remaining moves to the engine. Otherwise we get analysis output
14521          * after each move.
14522          */
14523         if (first.analysisSupport) {
14524           SendToProgram("exit\nforce\n", &first);
14525           first.analyzing = FALSE;
14526         }
14527     }
14528
14529     if (gameMode == IcsExamining && !pausing) {
14530         SendToICS(ics_prefix);
14531         SendToICS("forward 999999\n");
14532     } else {
14533         ForwardInner(forwardMostMove);
14534     }
14535
14536     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14537         /* we have fed all the moves, so reactivate analysis mode */
14538         SendToProgram("analyze\n", &first);
14539         first.analyzing = TRUE;
14540         /*first.maybeThinking = TRUE;*/
14541         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14542     }
14543 }
14544
14545 void
14546 BackwardInner (int target)
14547 {
14548     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14549
14550     if (appData.debugMode)
14551         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14552                 target, currentMove, forwardMostMove);
14553
14554     if (gameMode == EditPosition) return;
14555     seekGraphUp = FALSE;
14556     MarkTargetSquares(1);
14557     if (currentMove <= backwardMostMove) {
14558         ClearHighlights();
14559         DrawPosition(full_redraw, boards[currentMove]);
14560         return;
14561     }
14562     if (gameMode == PlayFromGameFile && !pausing)
14563       PauseEvent();
14564
14565     if (moveList[target][0]) {
14566         int fromX, fromY, toX, toY;
14567         toX = moveList[target][2] - AAA;
14568         toY = moveList[target][3] - ONE;
14569         if (moveList[target][1] == '@') {
14570             if (appData.highlightLastMove) {
14571                 SetHighlights(-1, -1, toX, toY);
14572             }
14573         } else {
14574             fromX = moveList[target][0] - AAA;
14575             fromY = moveList[target][1] - ONE;
14576             if (target == currentMove - 1) {
14577                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14578             }
14579             if (appData.highlightLastMove) {
14580                 SetHighlights(fromX, fromY, toX, toY);
14581             }
14582         }
14583     }
14584     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14585         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14586         while (currentMove > target) {
14587             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14588                 // null move cannot be undone. Reload program with move history before it.
14589                 int i;
14590                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14591                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14592                 }
14593                 SendBoard(&first, i); 
14594                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14595                 break;
14596             }
14597             SendToProgram("undo\n", &first);
14598             currentMove--;
14599         }
14600     } else {
14601         currentMove = target;
14602     }
14603
14604     if (gameMode == EditGame || gameMode == EndOfGame) {
14605         whiteTimeRemaining = timeRemaining[0][currentMove];
14606         blackTimeRemaining = timeRemaining[1][currentMove];
14607     }
14608     DisplayBothClocks();
14609     DisplayMove(currentMove - 1);
14610     DrawPosition(full_redraw, boards[currentMove]);
14611     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14612     // [HGM] PV info: routine tests if comment empty
14613     DisplayComment(currentMove - 1, commentList[currentMove]);
14614     ClearMap(); // [HGM] exclude: invalidate map
14615 }
14616
14617 void
14618 BackwardEvent ()
14619 {
14620     if (gameMode == IcsExamining && !pausing) {
14621         SendToICS(ics_prefix);
14622         SendToICS("backward\n");
14623     } else {
14624         BackwardInner(currentMove - 1);
14625     }
14626 }
14627
14628 void
14629 ToStartEvent ()
14630 {
14631     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14632         /* to optimize, we temporarily turn off analysis mode while we undo
14633          * all the moves. Otherwise we get analysis output after each undo.
14634          */
14635         if (first.analysisSupport) {
14636           SendToProgram("exit\nforce\n", &first);
14637           first.analyzing = FALSE;
14638         }
14639     }
14640
14641     if (gameMode == IcsExamining && !pausing) {
14642         SendToICS(ics_prefix);
14643         SendToICS("backward 999999\n");
14644     } else {
14645         BackwardInner(backwardMostMove);
14646     }
14647
14648     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14649         /* we have fed all the moves, so reactivate analysis mode */
14650         SendToProgram("analyze\n", &first);
14651         first.analyzing = TRUE;
14652         /*first.maybeThinking = TRUE;*/
14653         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14654     }
14655 }
14656
14657 void
14658 ToNrEvent (int to)
14659 {
14660   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14661   if (to >= forwardMostMove) to = forwardMostMove;
14662   if (to <= backwardMostMove) to = backwardMostMove;
14663   if (to < currentMove) {
14664     BackwardInner(to);
14665   } else {
14666     ForwardInner(to);
14667   }
14668 }
14669
14670 void
14671 RevertEvent (Boolean annotate)
14672 {
14673     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14674         return;
14675     }
14676     if (gameMode != IcsExamining) {
14677         DisplayError(_("You are not examining a game"), 0);
14678         return;
14679     }
14680     if (pausing) {
14681         DisplayError(_("You can't revert while pausing"), 0);
14682         return;
14683     }
14684     SendToICS(ics_prefix);
14685     SendToICS("revert\n");
14686 }
14687
14688 void
14689 RetractMoveEvent ()
14690 {
14691     switch (gameMode) {
14692       case MachinePlaysWhite:
14693       case MachinePlaysBlack:
14694         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14695             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14696             return;
14697         }
14698         if (forwardMostMove < 2) return;
14699         currentMove = forwardMostMove = forwardMostMove - 2;
14700         whiteTimeRemaining = timeRemaining[0][currentMove];
14701         blackTimeRemaining = timeRemaining[1][currentMove];
14702         DisplayBothClocks();
14703         DisplayMove(currentMove - 1);
14704         ClearHighlights();/*!! could figure this out*/
14705         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14706         SendToProgram("remove\n", &first);
14707         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14708         break;
14709
14710       case BeginningOfGame:
14711       default:
14712         break;
14713
14714       case IcsPlayingWhite:
14715       case IcsPlayingBlack:
14716         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14717             SendToICS(ics_prefix);
14718             SendToICS("takeback 2\n");
14719         } else {
14720             SendToICS(ics_prefix);
14721             SendToICS("takeback 1\n");
14722         }
14723         break;
14724     }
14725 }
14726
14727 void
14728 MoveNowEvent ()
14729 {
14730     ChessProgramState *cps;
14731
14732     switch (gameMode) {
14733       case MachinePlaysWhite:
14734         if (!WhiteOnMove(forwardMostMove)) {
14735             DisplayError(_("It is your turn"), 0);
14736             return;
14737         }
14738         cps = &first;
14739         break;
14740       case MachinePlaysBlack:
14741         if (WhiteOnMove(forwardMostMove)) {
14742             DisplayError(_("It is your turn"), 0);
14743             return;
14744         }
14745         cps = &first;
14746         break;
14747       case TwoMachinesPlay:
14748         if (WhiteOnMove(forwardMostMove) ==
14749             (first.twoMachinesColor[0] == 'w')) {
14750             cps = &first;
14751         } else {
14752             cps = &second;
14753         }
14754         break;
14755       case BeginningOfGame:
14756       default:
14757         return;
14758     }
14759     SendToProgram("?\n", cps);
14760 }
14761
14762 void
14763 TruncateGameEvent ()
14764 {
14765     EditGameEvent();
14766     if (gameMode != EditGame) return;
14767     TruncateGame();
14768 }
14769
14770 void
14771 TruncateGame ()
14772 {
14773     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14774     if (forwardMostMove > currentMove) {
14775         if (gameInfo.resultDetails != NULL) {
14776             free(gameInfo.resultDetails);
14777             gameInfo.resultDetails = NULL;
14778             gameInfo.result = GameUnfinished;
14779         }
14780         forwardMostMove = currentMove;
14781         HistorySet(parseList, backwardMostMove, forwardMostMove,
14782                    currentMove-1);
14783     }
14784 }
14785
14786 void
14787 HintEvent ()
14788 {
14789     if (appData.noChessProgram) return;
14790     switch (gameMode) {
14791       case MachinePlaysWhite:
14792         if (WhiteOnMove(forwardMostMove)) {
14793             DisplayError(_("Wait until your turn"), 0);
14794             return;
14795         }
14796         break;
14797       case BeginningOfGame:
14798       case MachinePlaysBlack:
14799         if (!WhiteOnMove(forwardMostMove)) {
14800             DisplayError(_("Wait until your turn"), 0);
14801             return;
14802         }
14803         break;
14804       default:
14805         DisplayError(_("No hint available"), 0);
14806         return;
14807     }
14808     SendToProgram("hint\n", &first);
14809     hintRequested = TRUE;
14810 }
14811
14812 void
14813 BookEvent ()
14814 {
14815     if (appData.noChessProgram) return;
14816     switch (gameMode) {
14817       case MachinePlaysWhite:
14818         if (WhiteOnMove(forwardMostMove)) {
14819             DisplayError(_("Wait until your turn"), 0);
14820             return;
14821         }
14822         break;
14823       case BeginningOfGame:
14824       case MachinePlaysBlack:
14825         if (!WhiteOnMove(forwardMostMove)) {
14826             DisplayError(_("Wait until your turn"), 0);
14827             return;
14828         }
14829         break;
14830       case EditPosition:
14831         EditPositionDone(TRUE);
14832         break;
14833       case TwoMachinesPlay:
14834         return;
14835       default:
14836         break;
14837     }
14838     SendToProgram("bk\n", &first);
14839     bookOutput[0] = NULLCHAR;
14840     bookRequested = TRUE;
14841 }
14842
14843 void
14844 AboutGameEvent ()
14845 {
14846     char *tags = PGNTags(&gameInfo);
14847     TagsPopUp(tags, CmailMsg());
14848     free(tags);
14849 }
14850
14851 /* end button procedures */
14852
14853 void
14854 PrintPosition (FILE *fp, int move)
14855 {
14856     int i, j;
14857
14858     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14859         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14860             char c = PieceToChar(boards[move][i][j]);
14861             fputc(c == 'x' ? '.' : c, fp);
14862             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14863         }
14864     }
14865     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14866       fprintf(fp, "white to play\n");
14867     else
14868       fprintf(fp, "black to play\n");
14869 }
14870
14871 void
14872 PrintOpponents (FILE *fp)
14873 {
14874     if (gameInfo.white != NULL) {
14875         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14876     } else {
14877         fprintf(fp, "\n");
14878     }
14879 }
14880
14881 /* Find last component of program's own name, using some heuristics */
14882 void
14883 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14884 {
14885     char *p, *q, c;
14886     int local = (strcmp(host, "localhost") == 0);
14887     while (!local && (p = strchr(prog, ';')) != NULL) {
14888         p++;
14889         while (*p == ' ') p++;
14890         prog = p;
14891     }
14892     if (*prog == '"' || *prog == '\'') {
14893         q = strchr(prog + 1, *prog);
14894     } else {
14895         q = strchr(prog, ' ');
14896     }
14897     if (q == NULL) q = prog + strlen(prog);
14898     p = q;
14899     while (p >= prog && *p != '/' && *p != '\\') p--;
14900     p++;
14901     if(p == prog && *p == '"') p++;
14902     c = *q; *q = 0;
14903     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14904     memcpy(buf, p, q - p);
14905     buf[q - p] = NULLCHAR;
14906     if (!local) {
14907         strcat(buf, "@");
14908         strcat(buf, host);
14909     }
14910 }
14911
14912 char *
14913 TimeControlTagValue ()
14914 {
14915     char buf[MSG_SIZ];
14916     if (!appData.clockMode) {
14917       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14918     } else if (movesPerSession > 0) {
14919       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14920     } else if (timeIncrement == 0) {
14921       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14922     } else {
14923       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14924     }
14925     return StrSave(buf);
14926 }
14927
14928 void
14929 SetGameInfo ()
14930 {
14931     /* This routine is used only for certain modes */
14932     VariantClass v = gameInfo.variant;
14933     ChessMove r = GameUnfinished;
14934     char *p = NULL;
14935
14936     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14937         r = gameInfo.result;
14938         p = gameInfo.resultDetails;
14939         gameInfo.resultDetails = NULL;
14940     }
14941     ClearGameInfo(&gameInfo);
14942     gameInfo.variant = v;
14943
14944     switch (gameMode) {
14945       case MachinePlaysWhite:
14946         gameInfo.event = StrSave( appData.pgnEventHeader );
14947         gameInfo.site = StrSave(HostName());
14948         gameInfo.date = PGNDate();
14949         gameInfo.round = StrSave("-");
14950         gameInfo.white = StrSave(first.tidy);
14951         gameInfo.black = StrSave(UserName());
14952         gameInfo.timeControl = TimeControlTagValue();
14953         break;
14954
14955       case MachinePlaysBlack:
14956         gameInfo.event = StrSave( appData.pgnEventHeader );
14957         gameInfo.site = StrSave(HostName());
14958         gameInfo.date = PGNDate();
14959         gameInfo.round = StrSave("-");
14960         gameInfo.white = StrSave(UserName());
14961         gameInfo.black = StrSave(first.tidy);
14962         gameInfo.timeControl = TimeControlTagValue();
14963         break;
14964
14965       case TwoMachinesPlay:
14966         gameInfo.event = StrSave( appData.pgnEventHeader );
14967         gameInfo.site = StrSave(HostName());
14968         gameInfo.date = PGNDate();
14969         if (roundNr > 0) {
14970             char buf[MSG_SIZ];
14971             snprintf(buf, MSG_SIZ, "%d", roundNr);
14972             gameInfo.round = StrSave(buf);
14973         } else {
14974             gameInfo.round = StrSave("-");
14975         }
14976         if (first.twoMachinesColor[0] == 'w') {
14977             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14978             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14979         } else {
14980             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14981             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14982         }
14983         gameInfo.timeControl = TimeControlTagValue();
14984         break;
14985
14986       case EditGame:
14987         gameInfo.event = StrSave("Edited game");
14988         gameInfo.site = StrSave(HostName());
14989         gameInfo.date = PGNDate();
14990         gameInfo.round = StrSave("-");
14991         gameInfo.white = StrSave("-");
14992         gameInfo.black = StrSave("-");
14993         gameInfo.result = r;
14994         gameInfo.resultDetails = p;
14995         break;
14996
14997       case EditPosition:
14998         gameInfo.event = StrSave("Edited position");
14999         gameInfo.site = StrSave(HostName());
15000         gameInfo.date = PGNDate();
15001         gameInfo.round = StrSave("-");
15002         gameInfo.white = StrSave("-");
15003         gameInfo.black = StrSave("-");
15004         break;
15005
15006       case IcsPlayingWhite:
15007       case IcsPlayingBlack:
15008       case IcsObserving:
15009       case IcsExamining:
15010         break;
15011
15012       case PlayFromGameFile:
15013         gameInfo.event = StrSave("Game from non-PGN file");
15014         gameInfo.site = StrSave(HostName());
15015         gameInfo.date = PGNDate();
15016         gameInfo.round = StrSave("-");
15017         gameInfo.white = StrSave("?");
15018         gameInfo.black = StrSave("?");
15019         break;
15020
15021       default:
15022         break;
15023     }
15024 }
15025
15026 void
15027 ReplaceComment (int index, char *text)
15028 {
15029     int len;
15030     char *p;
15031     float score;
15032
15033     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15034        pvInfoList[index-1].depth == len &&
15035        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15036        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15037     while (*text == '\n') text++;
15038     len = strlen(text);
15039     while (len > 0 && text[len - 1] == '\n') len--;
15040
15041     if (commentList[index] != NULL)
15042       free(commentList[index]);
15043
15044     if (len == 0) {
15045         commentList[index] = NULL;
15046         return;
15047     }
15048   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15049       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15050       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15051     commentList[index] = (char *) malloc(len + 2);
15052     strncpy(commentList[index], text, len);
15053     commentList[index][len] = '\n';
15054     commentList[index][len + 1] = NULLCHAR;
15055   } else {
15056     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15057     char *p;
15058     commentList[index] = (char *) malloc(len + 7);
15059     safeStrCpy(commentList[index], "{\n", 3);
15060     safeStrCpy(commentList[index]+2, text, len+1);
15061     commentList[index][len+2] = NULLCHAR;
15062     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15063     strcat(commentList[index], "\n}\n");
15064   }
15065 }
15066
15067 void
15068 CrushCRs (char *text)
15069 {
15070   char *p = text;
15071   char *q = text;
15072   char ch;
15073
15074   do {
15075     ch = *p++;
15076     if (ch == '\r') continue;
15077     *q++ = ch;
15078   } while (ch != '\0');
15079 }
15080
15081 void
15082 AppendComment (int index, char *text, Boolean addBraces)
15083 /* addBraces  tells if we should add {} */
15084 {
15085     int oldlen, len;
15086     char *old;
15087
15088 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15089     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15090
15091     CrushCRs(text);
15092     while (*text == '\n') text++;
15093     len = strlen(text);
15094     while (len > 0 && text[len - 1] == '\n') len--;
15095     text[len] = NULLCHAR;
15096
15097     if (len == 0) return;
15098
15099     if (commentList[index] != NULL) {
15100       Boolean addClosingBrace = addBraces;
15101         old = commentList[index];
15102         oldlen = strlen(old);
15103         while(commentList[index][oldlen-1] ==  '\n')
15104           commentList[index][--oldlen] = NULLCHAR;
15105         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15106         safeStrCpy(commentList[index], old, oldlen + len + 6);
15107         free(old);
15108         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15109         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15110           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15111           while (*text == '\n') { text++; len--; }
15112           commentList[index][--oldlen] = NULLCHAR;
15113       }
15114         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15115         else          strcat(commentList[index], "\n");
15116         strcat(commentList[index], text);
15117         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15118         else          strcat(commentList[index], "\n");
15119     } else {
15120         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15121         if(addBraces)
15122           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15123         else commentList[index][0] = NULLCHAR;
15124         strcat(commentList[index], text);
15125         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15126         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15127     }
15128 }
15129
15130 static char *
15131 FindStr (char * text, char * sub_text)
15132 {
15133     char * result = strstr( text, sub_text );
15134
15135     if( result != NULL ) {
15136         result += strlen( sub_text );
15137     }
15138
15139     return result;
15140 }
15141
15142 /* [AS] Try to extract PV info from PGN comment */
15143 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15144 char *
15145 GetInfoFromComment (int index, char * text)
15146 {
15147     char * sep = text, *p;
15148
15149     if( text != NULL && index > 0 ) {
15150         int score = 0;
15151         int depth = 0;
15152         int time = -1, sec = 0, deci;
15153         char * s_eval = FindStr( text, "[%eval " );
15154         char * s_emt = FindStr( text, "[%emt " );
15155
15156         if( s_eval != NULL || s_emt != NULL ) {
15157             /* New style */
15158             char delim;
15159
15160             if( s_eval != NULL ) {
15161                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15162                     return text;
15163                 }
15164
15165                 if( delim != ']' ) {
15166                     return text;
15167                 }
15168             }
15169
15170             if( s_emt != NULL ) {
15171             }
15172                 return text;
15173         }
15174         else {
15175             /* We expect something like: [+|-]nnn.nn/dd */
15176             int score_lo = 0;
15177
15178             if(*text != '{') return text; // [HGM] braces: must be normal comment
15179
15180             sep = strchr( text, '/' );
15181             if( sep == NULL || sep < (text+4) ) {
15182                 return text;
15183             }
15184
15185             p = text;
15186             if(p[1] == '(') { // comment starts with PV
15187                p = strchr(p, ')'); // locate end of PV
15188                if(p == NULL || sep < p+5) return text;
15189                // at this point we have something like "{(.*) +0.23/6 ..."
15190                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15191                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15192                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15193             }
15194             time = -1; sec = -1; deci = -1;
15195             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15196                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15197                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15198                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15199                 return text;
15200             }
15201
15202             if( score_lo < 0 || score_lo >= 100 ) {
15203                 return text;
15204             }
15205
15206             if(sec >= 0) time = 600*time + 10*sec; else
15207             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15208
15209             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15210
15211             /* [HGM] PV time: now locate end of PV info */
15212             while( *++sep >= '0' && *sep <= '9'); // strip depth
15213             if(time >= 0)
15214             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15215             if(sec >= 0)
15216             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15217             if(deci >= 0)
15218             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15219             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15220         }
15221
15222         if( depth <= 0 ) {
15223             return text;
15224         }
15225
15226         if( time < 0 ) {
15227             time = -1;
15228         }
15229
15230         pvInfoList[index-1].depth = depth;
15231         pvInfoList[index-1].score = score;
15232         pvInfoList[index-1].time  = 10*time; // centi-sec
15233         if(*sep == '}') *sep = 0; else *--sep = '{';
15234         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15235     }
15236     return sep;
15237 }
15238
15239 void
15240 SendToProgram (char *message, ChessProgramState *cps)
15241 {
15242     int count, outCount, error;
15243     char buf[MSG_SIZ];
15244
15245     if (cps->pr == NoProc) return;
15246     Attention(cps);
15247
15248     if (appData.debugMode) {
15249         TimeMark now;
15250         GetTimeMark(&now);
15251         fprintf(debugFP, "%ld >%-6s: %s",
15252                 SubtractTimeMarks(&now, &programStartTime),
15253                 cps->which, message);
15254         if(serverFP)
15255             fprintf(serverFP, "%ld >%-6s: %s",
15256                 SubtractTimeMarks(&now, &programStartTime),
15257                 cps->which, message), fflush(serverFP);
15258     }
15259
15260     count = strlen(message);
15261     outCount = OutputToProcess(cps->pr, message, count, &error);
15262     if (outCount < count && !exiting
15263                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15264       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15265       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15266         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15267             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15268                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15269                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15270                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15271             } else {
15272                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15273                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15274                 gameInfo.result = res;
15275             }
15276             gameInfo.resultDetails = StrSave(buf);
15277         }
15278         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15279         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15280     }
15281 }
15282
15283 void
15284 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15285 {
15286     char *end_str;
15287     char buf[MSG_SIZ];
15288     ChessProgramState *cps = (ChessProgramState *)closure;
15289
15290     if (isr != cps->isr) return; /* Killed intentionally */
15291     if (count <= 0) {
15292         if (count == 0) {
15293             RemoveInputSource(cps->isr);
15294             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15295                     _(cps->which), cps->program);
15296             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15297             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15298                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15299                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15300                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15301                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15302                 } else {
15303                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15304                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15305                     gameInfo.result = res;
15306                 }
15307                 gameInfo.resultDetails = StrSave(buf);
15308             }
15309             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15310             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15311         } else {
15312             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15313                     _(cps->which), cps->program);
15314             RemoveInputSource(cps->isr);
15315
15316             /* [AS] Program is misbehaving badly... kill it */
15317             if( count == -2 ) {
15318                 DestroyChildProcess( cps->pr, 9 );
15319                 cps->pr = NoProc;
15320             }
15321
15322             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15323         }
15324         return;
15325     }
15326
15327     if ((end_str = strchr(message, '\r')) != NULL)
15328       *end_str = NULLCHAR;
15329     if ((end_str = strchr(message, '\n')) != NULL)
15330       *end_str = NULLCHAR;
15331
15332     if (appData.debugMode) {
15333         TimeMark now; int print = 1;
15334         char *quote = ""; char c; int i;
15335
15336         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15337                 char start = message[0];
15338                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15339                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15340                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15341                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15342                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15343                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15344                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15345                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15346                    sscanf(message, "hint: %c", &c)!=1 && 
15347                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15348                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15349                     print = (appData.engineComments >= 2);
15350                 }
15351                 message[0] = start; // restore original message
15352         }
15353         if(print) {
15354                 GetTimeMark(&now);
15355                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15356                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15357                         quote,
15358                         message);
15359                 if(serverFP)
15360                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15361                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15362                         quote,
15363                         message), fflush(serverFP);
15364         }
15365     }
15366
15367     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15368     if (appData.icsEngineAnalyze) {
15369         if (strstr(message, "whisper") != NULL ||
15370              strstr(message, "kibitz") != NULL ||
15371             strstr(message, "tellics") != NULL) return;
15372     }
15373
15374     HandleMachineMove(message, cps);
15375 }
15376
15377
15378 void
15379 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15380 {
15381     char buf[MSG_SIZ];
15382     int seconds;
15383
15384     if( timeControl_2 > 0 ) {
15385         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15386             tc = timeControl_2;
15387         }
15388     }
15389     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15390     inc /= cps->timeOdds;
15391     st  /= cps->timeOdds;
15392
15393     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15394
15395     if (st > 0) {
15396       /* Set exact time per move, normally using st command */
15397       if (cps->stKludge) {
15398         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15399         seconds = st % 60;
15400         if (seconds == 0) {
15401           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15402         } else {
15403           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15404         }
15405       } else {
15406         snprintf(buf, MSG_SIZ, "st %d\n", st);
15407       }
15408     } else {
15409       /* Set conventional or incremental time control, using level command */
15410       if (seconds == 0) {
15411         /* Note old gnuchess bug -- minutes:seconds used to not work.
15412            Fixed in later versions, but still avoid :seconds
15413            when seconds is 0. */
15414         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15415       } else {
15416         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15417                  seconds, inc/1000.);
15418       }
15419     }
15420     SendToProgram(buf, cps);
15421
15422     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15423     /* Orthogonally, limit search to given depth */
15424     if (sd > 0) {
15425       if (cps->sdKludge) {
15426         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15427       } else {
15428         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15429       }
15430       SendToProgram(buf, cps);
15431     }
15432
15433     if(cps->nps >= 0) { /* [HGM] nps */
15434         if(cps->supportsNPS == FALSE)
15435           cps->nps = -1; // don't use if engine explicitly says not supported!
15436         else {
15437           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15438           SendToProgram(buf, cps);
15439         }
15440     }
15441 }
15442
15443 ChessProgramState *
15444 WhitePlayer ()
15445 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15446 {
15447     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15448        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15449         return &second;
15450     return &first;
15451 }
15452
15453 void
15454 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15455 {
15456     char message[MSG_SIZ];
15457     long time, otime;
15458
15459     /* Note: this routine must be called when the clocks are stopped
15460        or when they have *just* been set or switched; otherwise
15461        it will be off by the time since the current tick started.
15462     */
15463     if (machineWhite) {
15464         time = whiteTimeRemaining / 10;
15465         otime = blackTimeRemaining / 10;
15466     } else {
15467         time = blackTimeRemaining / 10;
15468         otime = whiteTimeRemaining / 10;
15469     }
15470     /* [HGM] translate opponent's time by time-odds factor */
15471     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15472
15473     if (time <= 0) time = 1;
15474     if (otime <= 0) otime = 1;
15475
15476     snprintf(message, MSG_SIZ, "time %ld\n", time);
15477     SendToProgram(message, cps);
15478
15479     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15480     SendToProgram(message, cps);
15481 }
15482
15483 int
15484 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15485 {
15486   char buf[MSG_SIZ];
15487   int len = strlen(name);
15488   int val;
15489
15490   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15491     (*p) += len + 1;
15492     sscanf(*p, "%d", &val);
15493     *loc = (val != 0);
15494     while (**p && **p != ' ')
15495       (*p)++;
15496     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15497     SendToProgram(buf, cps);
15498     return TRUE;
15499   }
15500   return FALSE;
15501 }
15502
15503 int
15504 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15505 {
15506   char buf[MSG_SIZ];
15507   int len = strlen(name);
15508   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15509     (*p) += len + 1;
15510     sscanf(*p, "%d", loc);
15511     while (**p && **p != ' ') (*p)++;
15512     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15513     SendToProgram(buf, cps);
15514     return TRUE;
15515   }
15516   return FALSE;
15517 }
15518
15519 int
15520 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15521 {
15522   char buf[MSG_SIZ];
15523   int len = strlen(name);
15524   if (strncmp((*p), name, len) == 0
15525       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15526     (*p) += len + 2;
15527     sscanf(*p, "%[^\"]", loc);
15528     while (**p && **p != '\"') (*p)++;
15529     if (**p == '\"') (*p)++;
15530     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15531     SendToProgram(buf, cps);
15532     return TRUE;
15533   }
15534   return FALSE;
15535 }
15536
15537 int
15538 ParseOption (Option *opt, ChessProgramState *cps)
15539 // [HGM] options: process the string that defines an engine option, and determine
15540 // name, type, default value, and allowed value range
15541 {
15542         char *p, *q, buf[MSG_SIZ];
15543         int n, min = (-1)<<31, max = 1<<31, def;
15544
15545         if(p = strstr(opt->name, " -spin ")) {
15546             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15547             if(max < min) max = min; // enforce consistency
15548             if(def < min) def = min;
15549             if(def > max) def = max;
15550             opt->value = def;
15551             opt->min = min;
15552             opt->max = max;
15553             opt->type = Spin;
15554         } else if((p = strstr(opt->name, " -slider "))) {
15555             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15556             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15557             if(max < min) max = min; // enforce consistency
15558             if(def < min) def = min;
15559             if(def > max) def = max;
15560             opt->value = def;
15561             opt->min = min;
15562             opt->max = max;
15563             opt->type = Spin; // Slider;
15564         } else if((p = strstr(opt->name, " -string "))) {
15565             opt->textValue = p+9;
15566             opt->type = TextBox;
15567         } else if((p = strstr(opt->name, " -file "))) {
15568             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15569             opt->textValue = p+7;
15570             opt->type = FileName; // FileName;
15571         } else if((p = strstr(opt->name, " -path "))) {
15572             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15573             opt->textValue = p+7;
15574             opt->type = PathName; // PathName;
15575         } else if(p = strstr(opt->name, " -check ")) {
15576             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15577             opt->value = (def != 0);
15578             opt->type = CheckBox;
15579         } else if(p = strstr(opt->name, " -combo ")) {
15580             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15581             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15582             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15583             opt->value = n = 0;
15584             while(q = StrStr(q, " /// ")) {
15585                 n++; *q = 0;    // count choices, and null-terminate each of them
15586                 q += 5;
15587                 if(*q == '*') { // remember default, which is marked with * prefix
15588                     q++;
15589                     opt->value = n;
15590                 }
15591                 cps->comboList[cps->comboCnt++] = q;
15592             }
15593             cps->comboList[cps->comboCnt++] = NULL;
15594             opt->max = n + 1;
15595             opt->type = ComboBox;
15596         } else if(p = strstr(opt->name, " -button")) {
15597             opt->type = Button;
15598         } else if(p = strstr(opt->name, " -save")) {
15599             opt->type = SaveButton;
15600         } else return FALSE;
15601         *p = 0; // terminate option name
15602         // now look if the command-line options define a setting for this engine option.
15603         if(cps->optionSettings && cps->optionSettings[0])
15604             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15605         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15606           snprintf(buf, MSG_SIZ, "option %s", p);
15607                 if(p = strstr(buf, ",")) *p = 0;
15608                 if(q = strchr(buf, '=')) switch(opt->type) {
15609                     case ComboBox:
15610                         for(n=0; n<opt->max; n++)
15611                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15612                         break;
15613                     case TextBox:
15614                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15615                         break;
15616                     case Spin:
15617                     case CheckBox:
15618                         opt->value = atoi(q+1);
15619                     default:
15620                         break;
15621                 }
15622                 strcat(buf, "\n");
15623                 SendToProgram(buf, cps);
15624         }
15625         return TRUE;
15626 }
15627
15628 void
15629 FeatureDone (ChessProgramState *cps, int val)
15630 {
15631   DelayedEventCallback cb = GetDelayedEvent();
15632   if ((cb == InitBackEnd3 && cps == &first) ||
15633       (cb == SettingsMenuIfReady && cps == &second) ||
15634       (cb == LoadEngine) ||
15635       (cb == TwoMachinesEventIfReady)) {
15636     CancelDelayedEvent();
15637     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15638   }
15639   cps->initDone = val;
15640 }
15641
15642 /* Parse feature command from engine */
15643 void
15644 ParseFeatures (char *args, ChessProgramState *cps)
15645 {
15646   char *p = args;
15647   char *q;
15648   int val;
15649   char buf[MSG_SIZ];
15650
15651   for (;;) {
15652     while (*p == ' ') p++;
15653     if (*p == NULLCHAR) return;
15654
15655     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15656     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15657     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15658     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15659     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15660     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15661     if (BoolFeature(&p, "reuse", &val, cps)) {
15662       /* Engine can disable reuse, but can't enable it if user said no */
15663       if (!val) cps->reuse = FALSE;
15664       continue;
15665     }
15666     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15667     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15668       if (gameMode == TwoMachinesPlay) {
15669         DisplayTwoMachinesTitle();
15670       } else {
15671         DisplayTitle("");
15672       }
15673       continue;
15674     }
15675     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15676     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15677     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15678     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15679     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15680     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15681     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15682     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15683     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15684     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15685     if (IntFeature(&p, "done", &val, cps)) {
15686       FeatureDone(cps, val);
15687       continue;
15688     }
15689     /* Added by Tord: */
15690     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15691     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15692     /* End of additions by Tord */
15693
15694     /* [HGM] added features: */
15695     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15696     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15697     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15698     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15699     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15700     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15701     if (StringFeature(&p, "option", buf, cps)) {
15702         FREE(cps->option[cps->nrOptions].name);
15703         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15704         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15705         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15706           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15707             SendToProgram(buf, cps);
15708             continue;
15709         }
15710         if(cps->nrOptions >= MAX_OPTIONS) {
15711             cps->nrOptions--;
15712             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15713             DisplayError(buf, 0);
15714         }
15715         continue;
15716     }
15717     /* End of additions by HGM */
15718
15719     /* unknown feature: complain and skip */
15720     q = p;
15721     while (*q && *q != '=') q++;
15722     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15723     SendToProgram(buf, cps);
15724     p = q;
15725     if (*p == '=') {
15726       p++;
15727       if (*p == '\"') {
15728         p++;
15729         while (*p && *p != '\"') p++;
15730         if (*p == '\"') p++;
15731       } else {
15732         while (*p && *p != ' ') p++;
15733       }
15734     }
15735   }
15736
15737 }
15738
15739 void
15740 PeriodicUpdatesEvent (int newState)
15741 {
15742     if (newState == appData.periodicUpdates)
15743       return;
15744
15745     appData.periodicUpdates=newState;
15746
15747     /* Display type changes, so update it now */
15748 //    DisplayAnalysis();
15749
15750     /* Get the ball rolling again... */
15751     if (newState) {
15752         AnalysisPeriodicEvent(1);
15753         StartAnalysisClock();
15754     }
15755 }
15756
15757 void
15758 PonderNextMoveEvent (int newState)
15759 {
15760     if (newState == appData.ponderNextMove) return;
15761     if (gameMode == EditPosition) EditPositionDone(TRUE);
15762     if (newState) {
15763         SendToProgram("hard\n", &first);
15764         if (gameMode == TwoMachinesPlay) {
15765             SendToProgram("hard\n", &second);
15766         }
15767     } else {
15768         SendToProgram("easy\n", &first);
15769         thinkOutput[0] = NULLCHAR;
15770         if (gameMode == TwoMachinesPlay) {
15771             SendToProgram("easy\n", &second);
15772         }
15773     }
15774     appData.ponderNextMove = newState;
15775 }
15776
15777 void
15778 NewSettingEvent (int option, int *feature, char *command, int value)
15779 {
15780     char buf[MSG_SIZ];
15781
15782     if (gameMode == EditPosition) EditPositionDone(TRUE);
15783     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15784     if(feature == NULL || *feature) SendToProgram(buf, &first);
15785     if (gameMode == TwoMachinesPlay) {
15786         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15787     }
15788 }
15789
15790 void
15791 ShowThinkingEvent ()
15792 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15793 {
15794     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15795     int newState = appData.showThinking
15796         // [HGM] thinking: other features now need thinking output as well
15797         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15798
15799     if (oldState == newState) return;
15800     oldState = newState;
15801     if (gameMode == EditPosition) EditPositionDone(TRUE);
15802     if (oldState) {
15803         SendToProgram("post\n", &first);
15804         if (gameMode == TwoMachinesPlay) {
15805             SendToProgram("post\n", &second);
15806         }
15807     } else {
15808         SendToProgram("nopost\n", &first);
15809         thinkOutput[0] = NULLCHAR;
15810         if (gameMode == TwoMachinesPlay) {
15811             SendToProgram("nopost\n", &second);
15812         }
15813     }
15814 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15815 }
15816
15817 void
15818 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15819 {
15820   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15821   if (pr == NoProc) return;
15822   AskQuestion(title, question, replyPrefix, pr);
15823 }
15824
15825 void
15826 TypeInEvent (char firstChar)
15827 {
15828     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15829         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15830         gameMode == AnalyzeMode || gameMode == EditGame || 
15831         gameMode == EditPosition || gameMode == IcsExamining ||
15832         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15833         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15834                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15835                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15836         gameMode == Training) PopUpMoveDialog(firstChar);
15837 }
15838
15839 void
15840 TypeInDoneEvent (char *move)
15841 {
15842         Board board;
15843         int n, fromX, fromY, toX, toY;
15844         char promoChar;
15845         ChessMove moveType;
15846
15847         // [HGM] FENedit
15848         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15849                 EditPositionPasteFEN(move);
15850                 return;
15851         }
15852         // [HGM] movenum: allow move number to be typed in any mode
15853         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15854           ToNrEvent(2*n-1);
15855           return;
15856         }
15857         // undocumented kludge: allow command-line option to be typed in!
15858         // (potentially fatal, and does not implement the effect of the option.)
15859         // should only be used for options that are values on which future decisions will be made,
15860         // and definitely not on options that would be used during initialization.
15861         if(strstr(move, "!!! -") == move) {
15862             ParseArgsFromString(move+4);
15863             return;
15864         }
15865
15866       if (gameMode != EditGame && currentMove != forwardMostMove && 
15867         gameMode != Training) {
15868         DisplayMoveError(_("Displayed move is not current"));
15869       } else {
15870         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15871           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15872         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15873         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15874           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15875           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15876         } else {
15877           DisplayMoveError(_("Could not parse move"));
15878         }
15879       }
15880 }
15881
15882 void
15883 DisplayMove (int moveNumber)
15884 {
15885     char message[MSG_SIZ];
15886     char res[MSG_SIZ];
15887     char cpThinkOutput[MSG_SIZ];
15888
15889     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15890
15891     if (moveNumber == forwardMostMove - 1 ||
15892         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15893
15894         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15895
15896         if (strchr(cpThinkOutput, '\n')) {
15897             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15898         }
15899     } else {
15900         *cpThinkOutput = NULLCHAR;
15901     }
15902
15903     /* [AS] Hide thinking from human user */
15904     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15905         *cpThinkOutput = NULLCHAR;
15906         if( thinkOutput[0] != NULLCHAR ) {
15907             int i;
15908
15909             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15910                 cpThinkOutput[i] = '.';
15911             }
15912             cpThinkOutput[i] = NULLCHAR;
15913             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15914         }
15915     }
15916
15917     if (moveNumber == forwardMostMove - 1 &&
15918         gameInfo.resultDetails != NULL) {
15919         if (gameInfo.resultDetails[0] == NULLCHAR) {
15920           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15921         } else {
15922           snprintf(res, MSG_SIZ, " {%s} %s",
15923                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15924         }
15925     } else {
15926         res[0] = NULLCHAR;
15927     }
15928
15929     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15930         DisplayMessage(res, cpThinkOutput);
15931     } else {
15932       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15933                 WhiteOnMove(moveNumber) ? " " : ".. ",
15934                 parseList[moveNumber], res);
15935         DisplayMessage(message, cpThinkOutput);
15936     }
15937 }
15938
15939 void
15940 DisplayComment (int moveNumber, char *text)
15941 {
15942     char title[MSG_SIZ];
15943
15944     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15945       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15946     } else {
15947       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15948               WhiteOnMove(moveNumber) ? " " : ".. ",
15949               parseList[moveNumber]);
15950     }
15951     if (text != NULL && (appData.autoDisplayComment || commentUp))
15952         CommentPopUp(title, text);
15953 }
15954
15955 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15956  * might be busy thinking or pondering.  It can be omitted if your
15957  * gnuchess is configured to stop thinking immediately on any user
15958  * input.  However, that gnuchess feature depends on the FIONREAD
15959  * ioctl, which does not work properly on some flavors of Unix.
15960  */
15961 void
15962 Attention (ChessProgramState *cps)
15963 {
15964 #if ATTENTION
15965     if (!cps->useSigint) return;
15966     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15967     switch (gameMode) {
15968       case MachinePlaysWhite:
15969       case MachinePlaysBlack:
15970       case TwoMachinesPlay:
15971       case IcsPlayingWhite:
15972       case IcsPlayingBlack:
15973       case AnalyzeMode:
15974       case AnalyzeFile:
15975         /* Skip if we know it isn't thinking */
15976         if (!cps->maybeThinking) return;
15977         if (appData.debugMode)
15978           fprintf(debugFP, "Interrupting %s\n", cps->which);
15979         InterruptChildProcess(cps->pr);
15980         cps->maybeThinking = FALSE;
15981         break;
15982       default:
15983         break;
15984     }
15985 #endif /*ATTENTION*/
15986 }
15987
15988 int
15989 CheckFlags ()
15990 {
15991     if (whiteTimeRemaining <= 0) {
15992         if (!whiteFlag) {
15993             whiteFlag = TRUE;
15994             if (appData.icsActive) {
15995                 if (appData.autoCallFlag &&
15996                     gameMode == IcsPlayingBlack && !blackFlag) {
15997                   SendToICS(ics_prefix);
15998                   SendToICS("flag\n");
15999                 }
16000             } else {
16001                 if (blackFlag) {
16002                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16003                 } else {
16004                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16005                     if (appData.autoCallFlag) {
16006                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16007                         return TRUE;
16008                     }
16009                 }
16010             }
16011         }
16012     }
16013     if (blackTimeRemaining <= 0) {
16014         if (!blackFlag) {
16015             blackFlag = TRUE;
16016             if (appData.icsActive) {
16017                 if (appData.autoCallFlag &&
16018                     gameMode == IcsPlayingWhite && !whiteFlag) {
16019                   SendToICS(ics_prefix);
16020                   SendToICS("flag\n");
16021                 }
16022             } else {
16023                 if (whiteFlag) {
16024                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16025                 } else {
16026                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16027                     if (appData.autoCallFlag) {
16028                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16029                         return TRUE;
16030                     }
16031                 }
16032             }
16033         }
16034     }
16035     return FALSE;
16036 }
16037
16038 void
16039 CheckTimeControl ()
16040 {
16041     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16042         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16043
16044     /*
16045      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16046      */
16047     if ( !WhiteOnMove(forwardMostMove) ) {
16048         /* White made time control */
16049         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16050         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16051         /* [HGM] time odds: correct new time quota for time odds! */
16052                                             / WhitePlayer()->timeOdds;
16053         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16054     } else {
16055         lastBlack -= blackTimeRemaining;
16056         /* Black made time control */
16057         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16058                                             / WhitePlayer()->other->timeOdds;
16059         lastWhite = whiteTimeRemaining;
16060     }
16061 }
16062
16063 void
16064 DisplayBothClocks ()
16065 {
16066     int wom = gameMode == EditPosition ?
16067       !blackPlaysFirst : WhiteOnMove(currentMove);
16068     DisplayWhiteClock(whiteTimeRemaining, wom);
16069     DisplayBlackClock(blackTimeRemaining, !wom);
16070 }
16071
16072
16073 /* Timekeeping seems to be a portability nightmare.  I think everyone
16074    has ftime(), but I'm really not sure, so I'm including some ifdefs
16075    to use other calls if you don't.  Clocks will be less accurate if
16076    you have neither ftime nor gettimeofday.
16077 */
16078
16079 /* VS 2008 requires the #include outside of the function */
16080 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16081 #include <sys/timeb.h>
16082 #endif
16083
16084 /* Get the current time as a TimeMark */
16085 void
16086 GetTimeMark (TimeMark *tm)
16087 {
16088 #if HAVE_GETTIMEOFDAY
16089
16090     struct timeval timeVal;
16091     struct timezone timeZone;
16092
16093     gettimeofday(&timeVal, &timeZone);
16094     tm->sec = (long) timeVal.tv_sec;
16095     tm->ms = (int) (timeVal.tv_usec / 1000L);
16096
16097 #else /*!HAVE_GETTIMEOFDAY*/
16098 #if HAVE_FTIME
16099
16100 // include <sys/timeb.h> / moved to just above start of function
16101     struct timeb timeB;
16102
16103     ftime(&timeB);
16104     tm->sec = (long) timeB.time;
16105     tm->ms = (int) timeB.millitm;
16106
16107 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16108     tm->sec = (long) time(NULL);
16109     tm->ms = 0;
16110 #endif
16111 #endif
16112 }
16113
16114 /* Return the difference in milliseconds between two
16115    time marks.  We assume the difference will fit in a long!
16116 */
16117 long
16118 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16119 {
16120     return 1000L*(tm2->sec - tm1->sec) +
16121            (long) (tm2->ms - tm1->ms);
16122 }
16123
16124
16125 /*
16126  * Code to manage the game clocks.
16127  *
16128  * In tournament play, black starts the clock and then white makes a move.
16129  * We give the human user a slight advantage if he is playing white---the
16130  * clocks don't run until he makes his first move, so it takes zero time.
16131  * Also, we don't account for network lag, so we could get out of sync
16132  * with GNU Chess's clock -- but then, referees are always right.
16133  */
16134
16135 static TimeMark tickStartTM;
16136 static long intendedTickLength;
16137
16138 long
16139 NextTickLength (long timeRemaining)
16140 {
16141     long nominalTickLength, nextTickLength;
16142
16143     if (timeRemaining > 0L && timeRemaining <= 10000L)
16144       nominalTickLength = 100L;
16145     else
16146       nominalTickLength = 1000L;
16147     nextTickLength = timeRemaining % nominalTickLength;
16148     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16149
16150     return nextTickLength;
16151 }
16152
16153 /* Adjust clock one minute up or down */
16154 void
16155 AdjustClock (Boolean which, int dir)
16156 {
16157     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16158     if(which) blackTimeRemaining += 60000*dir;
16159     else      whiteTimeRemaining += 60000*dir;
16160     DisplayBothClocks();
16161     adjustedClock = TRUE;
16162 }
16163
16164 /* Stop clocks and reset to a fresh time control */
16165 void
16166 ResetClocks ()
16167 {
16168     (void) StopClockTimer();
16169     if (appData.icsActive) {
16170         whiteTimeRemaining = blackTimeRemaining = 0;
16171     } else if (searchTime) {
16172         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16173         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16174     } else { /* [HGM] correct new time quote for time odds */
16175         whiteTC = blackTC = fullTimeControlString;
16176         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16177         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16178     }
16179     if (whiteFlag || blackFlag) {
16180         DisplayTitle("");
16181         whiteFlag = blackFlag = FALSE;
16182     }
16183     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16184     DisplayBothClocks();
16185     adjustedClock = FALSE;
16186 }
16187
16188 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16189
16190 /* Decrement running clock by amount of time that has passed */
16191 void
16192 DecrementClocks ()
16193 {
16194     long timeRemaining;
16195     long lastTickLength, fudge;
16196     TimeMark now;
16197
16198     if (!appData.clockMode) return;
16199     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16200
16201     GetTimeMark(&now);
16202
16203     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16204
16205     /* Fudge if we woke up a little too soon */
16206     fudge = intendedTickLength - lastTickLength;
16207     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16208
16209     if (WhiteOnMove(forwardMostMove)) {
16210         if(whiteNPS >= 0) lastTickLength = 0;
16211         timeRemaining = whiteTimeRemaining -= lastTickLength;
16212         if(timeRemaining < 0 && !appData.icsActive) {
16213             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16214             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16215                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16216                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16217             }
16218         }
16219         DisplayWhiteClock(whiteTimeRemaining - fudge,
16220                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16221     } else {
16222         if(blackNPS >= 0) lastTickLength = 0;
16223         timeRemaining = blackTimeRemaining -= lastTickLength;
16224         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16225             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16226             if(suddenDeath) {
16227                 blackStartMove = forwardMostMove;
16228                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16229             }
16230         }
16231         DisplayBlackClock(blackTimeRemaining - fudge,
16232                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16233     }
16234     if (CheckFlags()) return;
16235
16236     if(twoBoards) { // count down secondary board's clocks as well
16237         activePartnerTime -= lastTickLength;
16238         partnerUp = 1;
16239         if(activePartner == 'W')
16240             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16241         else
16242             DisplayBlackClock(activePartnerTime, TRUE);
16243         partnerUp = 0;
16244     }
16245
16246     tickStartTM = now;
16247     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16248     StartClockTimer(intendedTickLength);
16249
16250     /* if the time remaining has fallen below the alarm threshold, sound the
16251      * alarm. if the alarm has sounded and (due to a takeback or time control
16252      * with increment) the time remaining has increased to a level above the
16253      * threshold, reset the alarm so it can sound again.
16254      */
16255
16256     if (appData.icsActive && appData.icsAlarm) {
16257
16258         /* make sure we are dealing with the user's clock */
16259         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16260                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16261            )) return;
16262
16263         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16264             alarmSounded = FALSE;
16265         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16266             PlayAlarmSound();
16267             alarmSounded = TRUE;
16268         }
16269     }
16270 }
16271
16272
16273 /* A player has just moved, so stop the previously running
16274    clock and (if in clock mode) start the other one.
16275    We redisplay both clocks in case we're in ICS mode, because
16276    ICS gives us an update to both clocks after every move.
16277    Note that this routine is called *after* forwardMostMove
16278    is updated, so the last fractional tick must be subtracted
16279    from the color that is *not* on move now.
16280 */
16281 void
16282 SwitchClocks (int newMoveNr)
16283 {
16284     long lastTickLength;
16285     TimeMark now;
16286     int flagged = FALSE;
16287
16288     GetTimeMark(&now);
16289
16290     if (StopClockTimer() && appData.clockMode) {
16291         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16292         if (!WhiteOnMove(forwardMostMove)) {
16293             if(blackNPS >= 0) lastTickLength = 0;
16294             blackTimeRemaining -= lastTickLength;
16295            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16296 //         if(pvInfoList[forwardMostMove].time == -1)
16297                  pvInfoList[forwardMostMove].time =               // use GUI time
16298                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16299         } else {
16300            if(whiteNPS >= 0) lastTickLength = 0;
16301            whiteTimeRemaining -= lastTickLength;
16302            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16303 //         if(pvInfoList[forwardMostMove].time == -1)
16304                  pvInfoList[forwardMostMove].time =
16305                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16306         }
16307         flagged = CheckFlags();
16308     }
16309     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16310     CheckTimeControl();
16311
16312     if (flagged || !appData.clockMode) return;
16313
16314     switch (gameMode) {
16315       case MachinePlaysBlack:
16316       case MachinePlaysWhite:
16317       case BeginningOfGame:
16318         if (pausing) return;
16319         break;
16320
16321       case EditGame:
16322       case PlayFromGameFile:
16323       case IcsExamining:
16324         return;
16325
16326       default:
16327         break;
16328     }
16329
16330     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16331         if(WhiteOnMove(forwardMostMove))
16332              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16333         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16334     }
16335
16336     tickStartTM = now;
16337     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16338       whiteTimeRemaining : blackTimeRemaining);
16339     StartClockTimer(intendedTickLength);
16340 }
16341
16342
16343 /* Stop both clocks */
16344 void
16345 StopClocks ()
16346 {
16347     long lastTickLength;
16348     TimeMark now;
16349
16350     if (!StopClockTimer()) return;
16351     if (!appData.clockMode) return;
16352
16353     GetTimeMark(&now);
16354
16355     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16356     if (WhiteOnMove(forwardMostMove)) {
16357         if(whiteNPS >= 0) lastTickLength = 0;
16358         whiteTimeRemaining -= lastTickLength;
16359         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16360     } else {
16361         if(blackNPS >= 0) lastTickLength = 0;
16362         blackTimeRemaining -= lastTickLength;
16363         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16364     }
16365     CheckFlags();
16366 }
16367
16368 /* Start clock of player on move.  Time may have been reset, so
16369    if clock is already running, stop and restart it. */
16370 void
16371 StartClocks ()
16372 {
16373     (void) StopClockTimer(); /* in case it was running already */
16374     DisplayBothClocks();
16375     if (CheckFlags()) return;
16376
16377     if (!appData.clockMode) return;
16378     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16379
16380     GetTimeMark(&tickStartTM);
16381     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16382       whiteTimeRemaining : blackTimeRemaining);
16383
16384    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16385     whiteNPS = blackNPS = -1;
16386     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16387        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16388         whiteNPS = first.nps;
16389     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16390        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16391         blackNPS = first.nps;
16392     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16393         whiteNPS = second.nps;
16394     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16395         blackNPS = second.nps;
16396     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16397
16398     StartClockTimer(intendedTickLength);
16399 }
16400
16401 char *
16402 TimeString (long ms)
16403 {
16404     long second, minute, hour, day;
16405     char *sign = "";
16406     static char buf[32];
16407
16408     if (ms > 0 && ms <= 9900) {
16409       /* convert milliseconds to tenths, rounding up */
16410       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16411
16412       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16413       return buf;
16414     }
16415
16416     /* convert milliseconds to seconds, rounding up */
16417     /* use floating point to avoid strangeness of integer division
16418        with negative dividends on many machines */
16419     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16420
16421     if (second < 0) {
16422         sign = "-";
16423         second = -second;
16424     }
16425
16426     day = second / (60 * 60 * 24);
16427     second = second % (60 * 60 * 24);
16428     hour = second / (60 * 60);
16429     second = second % (60 * 60);
16430     minute = second / 60;
16431     second = second % 60;
16432
16433     if (day > 0)
16434       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16435               sign, day, hour, minute, second);
16436     else if (hour > 0)
16437       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16438     else
16439       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16440
16441     return buf;
16442 }
16443
16444
16445 /*
16446  * This is necessary because some C libraries aren't ANSI C compliant yet.
16447  */
16448 char *
16449 StrStr (char *string, char *match)
16450 {
16451     int i, length;
16452
16453     length = strlen(match);
16454
16455     for (i = strlen(string) - length; i >= 0; i--, string++)
16456       if (!strncmp(match, string, length))
16457         return string;
16458
16459     return NULL;
16460 }
16461
16462 char *
16463 StrCaseStr (char *string, char *match)
16464 {
16465     int i, j, length;
16466
16467     length = strlen(match);
16468
16469     for (i = strlen(string) - length; i >= 0; i--, string++) {
16470         for (j = 0; j < length; j++) {
16471             if (ToLower(match[j]) != ToLower(string[j]))
16472               break;
16473         }
16474         if (j == length) return string;
16475     }
16476
16477     return NULL;
16478 }
16479
16480 #ifndef _amigados
16481 int
16482 StrCaseCmp (char *s1, char *s2)
16483 {
16484     char c1, c2;
16485
16486     for (;;) {
16487         c1 = ToLower(*s1++);
16488         c2 = ToLower(*s2++);
16489         if (c1 > c2) return 1;
16490         if (c1 < c2) return -1;
16491         if (c1 == NULLCHAR) return 0;
16492     }
16493 }
16494
16495
16496 int
16497 ToLower (int c)
16498 {
16499     return isupper(c) ? tolower(c) : c;
16500 }
16501
16502
16503 int
16504 ToUpper (int c)
16505 {
16506     return islower(c) ? toupper(c) : c;
16507 }
16508 #endif /* !_amigados    */
16509
16510 char *
16511 StrSave (char *s)
16512 {
16513   char *ret;
16514
16515   if ((ret = (char *) malloc(strlen(s) + 1)))
16516     {
16517       safeStrCpy(ret, s, strlen(s)+1);
16518     }
16519   return ret;
16520 }
16521
16522 char *
16523 StrSavePtr (char *s, char **savePtr)
16524 {
16525     if (*savePtr) {
16526         free(*savePtr);
16527     }
16528     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16529       safeStrCpy(*savePtr, s, strlen(s)+1);
16530     }
16531     return(*savePtr);
16532 }
16533
16534 char *
16535 PGNDate ()
16536 {
16537     time_t clock;
16538     struct tm *tm;
16539     char buf[MSG_SIZ];
16540
16541     clock = time((time_t *)NULL);
16542     tm = localtime(&clock);
16543     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16544             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16545     return StrSave(buf);
16546 }
16547
16548
16549 char *
16550 PositionToFEN (int move, char *overrideCastling)
16551 {
16552     int i, j, fromX, fromY, toX, toY;
16553     int whiteToPlay;
16554     char buf[MSG_SIZ];
16555     char *p, *q;
16556     int emptycount;
16557     ChessSquare piece;
16558
16559     whiteToPlay = (gameMode == EditPosition) ?
16560       !blackPlaysFirst : (move % 2 == 0);
16561     p = buf;
16562
16563     /* Piece placement data */
16564     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16565         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16566         emptycount = 0;
16567         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16568             if (boards[move][i][j] == EmptySquare) {
16569                 emptycount++;
16570             } else { ChessSquare piece = boards[move][i][j];
16571                 if (emptycount > 0) {
16572                     if(emptycount<10) /* [HGM] can be >= 10 */
16573                         *p++ = '0' + emptycount;
16574                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16575                     emptycount = 0;
16576                 }
16577                 if(PieceToChar(piece) == '+') {
16578                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16579                     *p++ = '+';
16580                     piece = (ChessSquare)(DEMOTED piece);
16581                 }
16582                 *p++ = PieceToChar(piece);
16583                 if(p[-1] == '~') {
16584                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16585                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16586                     *p++ = '~';
16587                 }
16588             }
16589         }
16590         if (emptycount > 0) {
16591             if(emptycount<10) /* [HGM] can be >= 10 */
16592                 *p++ = '0' + emptycount;
16593             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16594             emptycount = 0;
16595         }
16596         *p++ = '/';
16597     }
16598     *(p - 1) = ' ';
16599
16600     /* [HGM] print Crazyhouse or Shogi holdings */
16601     if( gameInfo.holdingsWidth ) {
16602         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16603         q = p;
16604         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16605             piece = boards[move][i][BOARD_WIDTH-1];
16606             if( piece != EmptySquare )
16607               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16608                   *p++ = PieceToChar(piece);
16609         }
16610         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16611             piece = boards[move][BOARD_HEIGHT-i-1][0];
16612             if( piece != EmptySquare )
16613               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16614                   *p++ = PieceToChar(piece);
16615         }
16616
16617         if( q == p ) *p++ = '-';
16618         *p++ = ']';
16619         *p++ = ' ';
16620     }
16621
16622     /* Active color */
16623     *p++ = whiteToPlay ? 'w' : 'b';
16624     *p++ = ' ';
16625
16626   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16627     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16628   } else {
16629   if(nrCastlingRights) {
16630      q = p;
16631      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16632        /* [HGM] write directly from rights */
16633            if(boards[move][CASTLING][2] != NoRights &&
16634               boards[move][CASTLING][0] != NoRights   )
16635                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16636            if(boards[move][CASTLING][2] != NoRights &&
16637               boards[move][CASTLING][1] != NoRights   )
16638                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16639            if(boards[move][CASTLING][5] != NoRights &&
16640               boards[move][CASTLING][3] != NoRights   )
16641                 *p++ = boards[move][CASTLING][3] + AAA;
16642            if(boards[move][CASTLING][5] != NoRights &&
16643               boards[move][CASTLING][4] != NoRights   )
16644                 *p++ = boards[move][CASTLING][4] + AAA;
16645      } else {
16646
16647         /* [HGM] write true castling rights */
16648         if( nrCastlingRights == 6 ) {
16649             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16650                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16651             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16652                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16653             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16654                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16655             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16656                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16657         }
16658      }
16659      if (q == p) *p++ = '-'; /* No castling rights */
16660      *p++ = ' ';
16661   }
16662
16663   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16664      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16665     /* En passant target square */
16666     if (move > backwardMostMove) {
16667         fromX = moveList[move - 1][0] - AAA;
16668         fromY = moveList[move - 1][1] - ONE;
16669         toX = moveList[move - 1][2] - AAA;
16670         toY = moveList[move - 1][3] - ONE;
16671         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16672             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16673             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16674             fromX == toX) {
16675             /* 2-square pawn move just happened */
16676             *p++ = toX + AAA;
16677             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16678         } else {
16679             *p++ = '-';
16680         }
16681     } else if(move == backwardMostMove) {
16682         // [HGM] perhaps we should always do it like this, and forget the above?
16683         if((signed char)boards[move][EP_STATUS] >= 0) {
16684             *p++ = boards[move][EP_STATUS] + AAA;
16685             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16686         } else {
16687             *p++ = '-';
16688         }
16689     } else {
16690         *p++ = '-';
16691     }
16692     *p++ = ' ';
16693   }
16694   }
16695
16696     /* [HGM] find reversible plies */
16697     {   int i = 0, j=move;
16698
16699         if (appData.debugMode) { int k;
16700             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16701             for(k=backwardMostMove; k<=forwardMostMove; k++)
16702                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16703
16704         }
16705
16706         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16707         if( j == backwardMostMove ) i += initialRulePlies;
16708         sprintf(p, "%d ", i);
16709         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16710     }
16711     /* Fullmove number */
16712     sprintf(p, "%d", (move / 2) + 1);
16713
16714     return StrSave(buf);
16715 }
16716
16717 Boolean
16718 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16719 {
16720     int i, j;
16721     char *p, c;
16722     int emptycount;
16723     ChessSquare piece;
16724
16725     p = fen;
16726
16727     /* [HGM] by default clear Crazyhouse holdings, if present */
16728     if(gameInfo.holdingsWidth) {
16729        for(i=0; i<BOARD_HEIGHT; i++) {
16730            board[i][0]             = EmptySquare; /* black holdings */
16731            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16732            board[i][1]             = (ChessSquare) 0; /* black counts */
16733            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16734        }
16735     }
16736
16737     /* Piece placement data */
16738     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16739         j = 0;
16740         for (;;) {
16741             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16742                 if (*p == '/') p++;
16743                 emptycount = gameInfo.boardWidth - j;
16744                 while (emptycount--)
16745                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16746                 break;
16747 #if(BOARD_FILES >= 10)
16748             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16749                 p++; emptycount=10;
16750                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16751                 while (emptycount--)
16752                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16753 #endif
16754             } else if (isdigit(*p)) {
16755                 emptycount = *p++ - '0';
16756                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16757                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16758                 while (emptycount--)
16759                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16760             } else if (*p == '+' || isalpha(*p)) {
16761                 if (j >= gameInfo.boardWidth) return FALSE;
16762                 if(*p=='+') {
16763                     piece = CharToPiece(*++p);
16764                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16765                     piece = (ChessSquare) (PROMOTED piece ); p++;
16766                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16767                 } else piece = CharToPiece(*p++);
16768
16769                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16770                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16771                     piece = (ChessSquare) (PROMOTED piece);
16772                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16773                     p++;
16774                 }
16775                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16776             } else {
16777                 return FALSE;
16778             }
16779         }
16780     }
16781     while (*p == '/' || *p == ' ') p++;
16782
16783     /* [HGM] look for Crazyhouse holdings here */
16784     while(*p==' ') p++;
16785     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16786         if(*p == '[') p++;
16787         if(*p == '-' ) p++; /* empty holdings */ else {
16788             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16789             /* if we would allow FEN reading to set board size, we would   */
16790             /* have to add holdings and shift the board read so far here   */
16791             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16792                 p++;
16793                 if((int) piece >= (int) BlackPawn ) {
16794                     i = (int)piece - (int)BlackPawn;
16795                     i = PieceToNumber((ChessSquare)i);
16796                     if( i >= gameInfo.holdingsSize ) return FALSE;
16797                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16798                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16799                 } else {
16800                     i = (int)piece - (int)WhitePawn;
16801                     i = PieceToNumber((ChessSquare)i);
16802                     if( i >= gameInfo.holdingsSize ) return FALSE;
16803                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16804                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16805                 }
16806             }
16807         }
16808         if(*p == ']') p++;
16809     }
16810
16811     while(*p == ' ') p++;
16812
16813     /* Active color */
16814     c = *p++;
16815     if(appData.colorNickNames) {
16816       if( c == appData.colorNickNames[0] ) c = 'w'; else
16817       if( c == appData.colorNickNames[1] ) c = 'b';
16818     }
16819     switch (c) {
16820       case 'w':
16821         *blackPlaysFirst = FALSE;
16822         break;
16823       case 'b':
16824         *blackPlaysFirst = TRUE;
16825         break;
16826       default:
16827         return FALSE;
16828     }
16829
16830     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16831     /* return the extra info in global variiables             */
16832
16833     /* set defaults in case FEN is incomplete */
16834     board[EP_STATUS] = EP_UNKNOWN;
16835     for(i=0; i<nrCastlingRights; i++ ) {
16836         board[CASTLING][i] =
16837             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16838     }   /* assume possible unless obviously impossible */
16839     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16840     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16841     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16842                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16843     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16844     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16845     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16846                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16847     FENrulePlies = 0;
16848
16849     while(*p==' ') p++;
16850     if(nrCastlingRights) {
16851       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16852           /* castling indicator present, so default becomes no castlings */
16853           for(i=0; i<nrCastlingRights; i++ ) {
16854                  board[CASTLING][i] = NoRights;
16855           }
16856       }
16857       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16858              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16859              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16860              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16861         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16862
16863         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16864             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16865             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16866         }
16867         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16868             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16869         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16870                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16871         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16872                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16873         switch(c) {
16874           case'K':
16875               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16876               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16877               board[CASTLING][2] = whiteKingFile;
16878               break;
16879           case'Q':
16880               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16881               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16882               board[CASTLING][2] = whiteKingFile;
16883               break;
16884           case'k':
16885               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16886               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16887               board[CASTLING][5] = blackKingFile;
16888               break;
16889           case'q':
16890               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16891               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16892               board[CASTLING][5] = blackKingFile;
16893           case '-':
16894               break;
16895           default: /* FRC castlings */
16896               if(c >= 'a') { /* black rights */
16897                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16898                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16899                   if(i == BOARD_RGHT) break;
16900                   board[CASTLING][5] = i;
16901                   c -= AAA;
16902                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16903                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16904                   if(c > i)
16905                       board[CASTLING][3] = c;
16906                   else
16907                       board[CASTLING][4] = c;
16908               } else { /* white rights */
16909                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16910                     if(board[0][i] == WhiteKing) break;
16911                   if(i == BOARD_RGHT) break;
16912                   board[CASTLING][2] = i;
16913                   c -= AAA - 'a' + 'A';
16914                   if(board[0][c] >= WhiteKing) break;
16915                   if(c > i)
16916                       board[CASTLING][0] = c;
16917                   else
16918                       board[CASTLING][1] = c;
16919               }
16920         }
16921       }
16922       for(i=0; i<nrCastlingRights; i++)
16923         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16924     if (appData.debugMode) {
16925         fprintf(debugFP, "FEN castling rights:");
16926         for(i=0; i<nrCastlingRights; i++)
16927         fprintf(debugFP, " %d", board[CASTLING][i]);
16928         fprintf(debugFP, "\n");
16929     }
16930
16931       while(*p==' ') p++;
16932     }
16933
16934     /* read e.p. field in games that know e.p. capture */
16935     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16936        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16937       if(*p=='-') {
16938         p++; board[EP_STATUS] = EP_NONE;
16939       } else {
16940          char c = *p++ - AAA;
16941
16942          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16943          if(*p >= '0' && *p <='9') p++;
16944          board[EP_STATUS] = c;
16945       }
16946     }
16947
16948
16949     if(sscanf(p, "%d", &i) == 1) {
16950         FENrulePlies = i; /* 50-move ply counter */
16951         /* (The move number is still ignored)    */
16952     }
16953
16954     return TRUE;
16955 }
16956
16957 void
16958 EditPositionPasteFEN (char *fen)
16959 {
16960   if (fen != NULL) {
16961     Board initial_position;
16962
16963     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16964       DisplayError(_("Bad FEN position in clipboard"), 0);
16965       return ;
16966     } else {
16967       int savedBlackPlaysFirst = blackPlaysFirst;
16968       EditPositionEvent();
16969       blackPlaysFirst = savedBlackPlaysFirst;
16970       CopyBoard(boards[0], initial_position);
16971       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16972       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16973       DisplayBothClocks();
16974       DrawPosition(FALSE, boards[currentMove]);
16975     }
16976   }
16977 }
16978
16979 static char cseq[12] = "\\   ";
16980
16981 Boolean
16982 set_cont_sequence (char *new_seq)
16983 {
16984     int len;
16985     Boolean ret;
16986
16987     // handle bad attempts to set the sequence
16988         if (!new_seq)
16989                 return 0; // acceptable error - no debug
16990
16991     len = strlen(new_seq);
16992     ret = (len > 0) && (len < sizeof(cseq));
16993     if (ret)
16994       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16995     else if (appData.debugMode)
16996       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16997     return ret;
16998 }
16999
17000 /*
17001     reformat a source message so words don't cross the width boundary.  internal
17002     newlines are not removed.  returns the wrapped size (no null character unless
17003     included in source message).  If dest is NULL, only calculate the size required
17004     for the dest buffer.  lp argument indicats line position upon entry, and it's
17005     passed back upon exit.
17006 */
17007 int
17008 wrap (char *dest, char *src, int count, int width, int *lp)
17009 {
17010     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17011
17012     cseq_len = strlen(cseq);
17013     old_line = line = *lp;
17014     ansi = len = clen = 0;
17015
17016     for (i=0; i < count; i++)
17017     {
17018         if (src[i] == '\033')
17019             ansi = 1;
17020
17021         // if we hit the width, back up
17022         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17023         {
17024             // store i & len in case the word is too long
17025             old_i = i, old_len = len;
17026
17027             // find the end of the last word
17028             while (i && src[i] != ' ' && src[i] != '\n')
17029             {
17030                 i--;
17031                 len--;
17032             }
17033
17034             // word too long?  restore i & len before splitting it
17035             if ((old_i-i+clen) >= width)
17036             {
17037                 i = old_i;
17038                 len = old_len;
17039             }
17040
17041             // extra space?
17042             if (i && src[i-1] == ' ')
17043                 len--;
17044
17045             if (src[i] != ' ' && src[i] != '\n')
17046             {
17047                 i--;
17048                 if (len)
17049                     len--;
17050             }
17051
17052             // now append the newline and continuation sequence
17053             if (dest)
17054                 dest[len] = '\n';
17055             len++;
17056             if (dest)
17057                 strncpy(dest+len, cseq, cseq_len);
17058             len += cseq_len;
17059             line = cseq_len;
17060             clen = cseq_len;
17061             continue;
17062         }
17063
17064         if (dest)
17065             dest[len] = src[i];
17066         len++;
17067         if (!ansi)
17068             line++;
17069         if (src[i] == '\n')
17070             line = 0;
17071         if (src[i] == 'm')
17072             ansi = 0;
17073     }
17074     if (dest && appData.debugMode)
17075     {
17076         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17077             count, width, line, len, *lp);
17078         show_bytes(debugFP, src, count);
17079         fprintf(debugFP, "\ndest: ");
17080         show_bytes(debugFP, dest, len);
17081         fprintf(debugFP, "\n");
17082     }
17083     *lp = dest ? line : old_line;
17084
17085     return len;
17086 }
17087
17088 // [HGM] vari: routines for shelving variations
17089 Boolean modeRestore = FALSE;
17090
17091 void
17092 PushInner (int firstMove, int lastMove)
17093 {
17094         int i, j, nrMoves = lastMove - firstMove;
17095
17096         // push current tail of game on stack
17097         savedResult[storedGames] = gameInfo.result;
17098         savedDetails[storedGames] = gameInfo.resultDetails;
17099         gameInfo.resultDetails = NULL;
17100         savedFirst[storedGames] = firstMove;
17101         savedLast [storedGames] = lastMove;
17102         savedFramePtr[storedGames] = framePtr;
17103         framePtr -= nrMoves; // reserve space for the boards
17104         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17105             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17106             for(j=0; j<MOVE_LEN; j++)
17107                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17108             for(j=0; j<2*MOVE_LEN; j++)
17109                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17110             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17111             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17112             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17113             pvInfoList[firstMove+i-1].depth = 0;
17114             commentList[framePtr+i] = commentList[firstMove+i];
17115             commentList[firstMove+i] = NULL;
17116         }
17117
17118         storedGames++;
17119         forwardMostMove = firstMove; // truncate game so we can start variation
17120 }
17121
17122 void
17123 PushTail (int firstMove, int lastMove)
17124 {
17125         if(appData.icsActive) { // only in local mode
17126                 forwardMostMove = currentMove; // mimic old ICS behavior
17127                 return;
17128         }
17129         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17130
17131         PushInner(firstMove, lastMove);
17132         if(storedGames == 1) GreyRevert(FALSE);
17133         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17134 }
17135
17136 void
17137 PopInner (Boolean annotate)
17138 {
17139         int i, j, nrMoves;
17140         char buf[8000], moveBuf[20];
17141
17142         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17143         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17144         nrMoves = savedLast[storedGames] - currentMove;
17145         if(annotate) {
17146                 int cnt = 10;
17147                 if(!WhiteOnMove(currentMove))
17148                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17149                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17150                 for(i=currentMove; i<forwardMostMove; i++) {
17151                         if(WhiteOnMove(i))
17152                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17153                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17154                         strcat(buf, moveBuf);
17155                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17156                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17157                 }
17158                 strcat(buf, ")");
17159         }
17160         for(i=1; i<=nrMoves; i++) { // copy last variation back
17161             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17162             for(j=0; j<MOVE_LEN; j++)
17163                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17164             for(j=0; j<2*MOVE_LEN; j++)
17165                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17166             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17167             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17168             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17169             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17170             commentList[currentMove+i] = commentList[framePtr+i];
17171             commentList[framePtr+i] = NULL;
17172         }
17173         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17174         framePtr = savedFramePtr[storedGames];
17175         gameInfo.result = savedResult[storedGames];
17176         if(gameInfo.resultDetails != NULL) {
17177             free(gameInfo.resultDetails);
17178       }
17179         gameInfo.resultDetails = savedDetails[storedGames];
17180         forwardMostMove = currentMove + nrMoves;
17181 }
17182
17183 Boolean
17184 PopTail (Boolean annotate)
17185 {
17186         if(appData.icsActive) return FALSE; // only in local mode
17187         if(!storedGames) return FALSE; // sanity
17188         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17189
17190         PopInner(annotate);
17191         if(currentMove < forwardMostMove) ForwardEvent(); else
17192         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17193
17194         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17195         return TRUE;
17196 }
17197
17198 void
17199 CleanupTail ()
17200 {       // remove all shelved variations
17201         int i;
17202         for(i=0; i<storedGames; i++) {
17203             if(savedDetails[i])
17204                 free(savedDetails[i]);
17205             savedDetails[i] = NULL;
17206         }
17207         for(i=framePtr; i<MAX_MOVES; i++) {
17208                 if(commentList[i]) free(commentList[i]);
17209                 commentList[i] = NULL;
17210         }
17211         framePtr = MAX_MOVES-1;
17212         storedGames = 0;
17213 }
17214
17215 void
17216 LoadVariation (int index, char *text)
17217 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17218         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17219         int level = 0, move;
17220
17221         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17222         // first find outermost bracketing variation
17223         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17224             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17225                 if(*p == '{') wait = '}'; else
17226                 if(*p == '[') wait = ']'; else
17227                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17228                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17229             }
17230             if(*p == wait) wait = NULLCHAR; // closing ]} found
17231             p++;
17232         }
17233         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17234         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17235         end[1] = NULLCHAR; // clip off comment beyond variation
17236         ToNrEvent(currentMove-1);
17237         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17238         // kludge: use ParsePV() to append variation to game
17239         move = currentMove;
17240         ParsePV(start, TRUE, TRUE);
17241         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17242         ClearPremoveHighlights();
17243         CommentPopDown();
17244         ToNrEvent(currentMove+1);
17245 }
17246