80211026275e9d512ea58756da70ce9a13d58235
[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      gameInfo.boardWidth  = newWidth;
2450      gameInfo.boardHeight = newHeight;
2451      gameInfo.holdingsWidth = newHoldingsWidth;
2452      gameInfo.variant = newVariant;
2453      InitDrawingSizes(-2, 0);
2454    } else gameInfo.variant = newVariant;
2455    CopyBoard(oldBoard, board);   // remember correctly formatted board
2456      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2457    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2458 }
2459
2460 static int loggedOn = FALSE;
2461
2462 /*-- Game start info cache: --*/
2463 int gs_gamenum;
2464 char gs_kind[MSG_SIZ];
2465 static char player1Name[128] = "";
2466 static char player2Name[128] = "";
2467 static char cont_seq[] = "\n\\   ";
2468 static int player1Rating = -1;
2469 static int player2Rating = -1;
2470 /*----------------------------*/
2471
2472 ColorClass curColor = ColorNormal;
2473 int suppressKibitz = 0;
2474
2475 // [HGM] seekgraph
2476 Boolean soughtPending = FALSE;
2477 Boolean seekGraphUp;
2478 #define MAX_SEEK_ADS 200
2479 #define SQUARE 0x80
2480 char *seekAdList[MAX_SEEK_ADS];
2481 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2482 float tcList[MAX_SEEK_ADS];
2483 char colorList[MAX_SEEK_ADS];
2484 int nrOfSeekAds = 0;
2485 int minRating = 1010, maxRating = 2800;
2486 int hMargin = 10, vMargin = 20, h, w;
2487 extern int squareSize, lineGap;
2488
2489 void
2490 PlotSeekAd (int i)
2491 {
2492         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2493         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2494         if(r < minRating+100 && r >=0 ) r = minRating+100;
2495         if(r > maxRating) r = maxRating;
2496         if(tc < 1.) tc = 1.;
2497         if(tc > 95.) tc = 95.;
2498         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2499         y = ((double)r - minRating)/(maxRating - minRating)
2500             * (h-vMargin-squareSize/8-1) + vMargin;
2501         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2502         if(strstr(seekAdList[i], " u ")) color = 1;
2503         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2504            !strstr(seekAdList[i], "bullet") &&
2505            !strstr(seekAdList[i], "blitz") &&
2506            !strstr(seekAdList[i], "standard") ) color = 2;
2507         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2508         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2509 }
2510
2511 void
2512 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2513 {
2514         char buf[MSG_SIZ], *ext = "";
2515         VariantClass v = StringToVariant(type);
2516         if(strstr(type, "wild")) {
2517             ext = type + 4; // append wild number
2518             if(v == VariantFischeRandom) type = "chess960"; else
2519             if(v == VariantLoadable) type = "setup"; else
2520             type = VariantName(v);
2521         }
2522         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2523         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2524             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2525             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2526             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2527             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2528             seekNrList[nrOfSeekAds] = nr;
2529             zList[nrOfSeekAds] = 0;
2530             seekAdList[nrOfSeekAds++] = StrSave(buf);
2531             if(plot) PlotSeekAd(nrOfSeekAds-1);
2532         }
2533 }
2534
2535 void
2536 EraseSeekDot (int i)
2537 {
2538     int x = xList[i], y = yList[i], d=squareSize/4, k;
2539     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2540     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2541     // now replot every dot that overlapped
2542     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2543         int xx = xList[k], yy = yList[k];
2544         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2545             DrawSeekDot(xx, yy, colorList[k]);
2546     }
2547 }
2548
2549 void
2550 RemoveSeekAd (int nr)
2551 {
2552         int i;
2553         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2554             EraseSeekDot(i);
2555             if(seekAdList[i]) free(seekAdList[i]);
2556             seekAdList[i] = seekAdList[--nrOfSeekAds];
2557             seekNrList[i] = seekNrList[nrOfSeekAds];
2558             ratingList[i] = ratingList[nrOfSeekAds];
2559             colorList[i]  = colorList[nrOfSeekAds];
2560             tcList[i] = tcList[nrOfSeekAds];
2561             xList[i]  = xList[nrOfSeekAds];
2562             yList[i]  = yList[nrOfSeekAds];
2563             zList[i]  = zList[nrOfSeekAds];
2564             seekAdList[nrOfSeekAds] = NULL;
2565             break;
2566         }
2567 }
2568
2569 Boolean
2570 MatchSoughtLine (char *line)
2571 {
2572     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2573     int nr, base, inc, u=0; char dummy;
2574
2575     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2576        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2577        (u=1) &&
2578        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2579         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2580         // match: compact and save the line
2581         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2582         return TRUE;
2583     }
2584     return FALSE;
2585 }
2586
2587 int
2588 DrawSeekGraph ()
2589 {
2590     int i;
2591     if(!seekGraphUp) return FALSE;
2592     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2593     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2594
2595     DrawSeekBackground(0, 0, w, h);
2596     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2597     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2598     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2599         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2600         yy = h-1-yy;
2601         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2602         if(i%500 == 0) {
2603             char buf[MSG_SIZ];
2604             snprintf(buf, MSG_SIZ, "%d", i);
2605             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2606         }
2607     }
2608     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2609     for(i=1; i<100; i+=(i<10?1:5)) {
2610         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2611         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2612         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2613             char buf[MSG_SIZ];
2614             snprintf(buf, MSG_SIZ, "%d", i);
2615             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2616         }
2617     }
2618     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2619     return TRUE;
2620 }
2621
2622 int
2623 SeekGraphClick (ClickType click, int x, int y, int moving)
2624 {
2625     static int lastDown = 0, displayed = 0, lastSecond;
2626     if(y < 0) return FALSE;
2627     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2628         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2629         if(!seekGraphUp) return FALSE;
2630         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2631         DrawPosition(TRUE, NULL);
2632         return TRUE;
2633     }
2634     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2635         if(click == Release || moving) return FALSE;
2636         nrOfSeekAds = 0;
2637         soughtPending = TRUE;
2638         SendToICS(ics_prefix);
2639         SendToICS("sought\n"); // should this be "sought all"?
2640     } else { // issue challenge based on clicked ad
2641         int dist = 10000; int i, closest = 0, second = 0;
2642         for(i=0; i<nrOfSeekAds; i++) {
2643             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2644             if(d < dist) { dist = d; closest = i; }
2645             second += (d - zList[i] < 120); // count in-range ads
2646             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2647         }
2648         if(dist < 120) {
2649             char buf[MSG_SIZ];
2650             second = (second > 1);
2651             if(displayed != closest || second != lastSecond) {
2652                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2653                 lastSecond = second; displayed = closest;
2654             }
2655             if(click == Press) {
2656                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2657                 lastDown = closest;
2658                 return TRUE;
2659             } // on press 'hit', only show info
2660             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2661             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2662             SendToICS(ics_prefix);
2663             SendToICS(buf);
2664             return TRUE; // let incoming board of started game pop down the graph
2665         } else if(click == Release) { // release 'miss' is ignored
2666             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2667             if(moving == 2) { // right up-click
2668                 nrOfSeekAds = 0; // refresh graph
2669                 soughtPending = TRUE;
2670                 SendToICS(ics_prefix);
2671                 SendToICS("sought\n"); // should this be "sought all"?
2672             }
2673             return TRUE;
2674         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2675         // press miss or release hit 'pop down' seek graph
2676         seekGraphUp = FALSE;
2677         DrawPosition(TRUE, NULL);
2678     }
2679     return TRUE;
2680 }
2681
2682 void
2683 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2684 {
2685 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2686 #define STARTED_NONE 0
2687 #define STARTED_MOVES 1
2688 #define STARTED_BOARD 2
2689 #define STARTED_OBSERVE 3
2690 #define STARTED_HOLDINGS 4
2691 #define STARTED_CHATTER 5
2692 #define STARTED_COMMENT 6
2693 #define STARTED_MOVES_NOHIDE 7
2694
2695     static int started = STARTED_NONE;
2696     static char parse[20000];
2697     static int parse_pos = 0;
2698     static char buf[BUF_SIZE + 1];
2699     static int firstTime = TRUE, intfSet = FALSE;
2700     static ColorClass prevColor = ColorNormal;
2701     static int savingComment = FALSE;
2702     static int cmatch = 0; // continuation sequence match
2703     char *bp;
2704     char str[MSG_SIZ];
2705     int i, oldi;
2706     int buf_len;
2707     int next_out;
2708     int tkind;
2709     int backup;    /* [DM] For zippy color lines */
2710     char *p;
2711     char talker[MSG_SIZ]; // [HGM] chat
2712     int channel;
2713
2714     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2715
2716     if (appData.debugMode) {
2717       if (!error) {
2718         fprintf(debugFP, "<ICS: ");
2719         show_bytes(debugFP, data, count);
2720         fprintf(debugFP, "\n");
2721       }
2722     }
2723
2724     if (appData.debugMode) { int f = forwardMostMove;
2725         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2726                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2727                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2728     }
2729     if (count > 0) {
2730         /* If last read ended with a partial line that we couldn't parse,
2731            prepend it to the new read and try again. */
2732         if (leftover_len > 0) {
2733             for (i=0; i<leftover_len; i++)
2734               buf[i] = buf[leftover_start + i];
2735         }
2736
2737     /* copy new characters into the buffer */
2738     bp = buf + leftover_len;
2739     buf_len=leftover_len;
2740     for (i=0; i<count; i++)
2741     {
2742         // ignore these
2743         if (data[i] == '\r')
2744             continue;
2745
2746         // join lines split by ICS?
2747         if (!appData.noJoin)
2748         {
2749             /*
2750                 Joining just consists of finding matches against the
2751                 continuation sequence, and discarding that sequence
2752                 if found instead of copying it.  So, until a match
2753                 fails, there's nothing to do since it might be the
2754                 complete sequence, and thus, something we don't want
2755                 copied.
2756             */
2757             if (data[i] == cont_seq[cmatch])
2758             {
2759                 cmatch++;
2760                 if (cmatch == strlen(cont_seq))
2761                 {
2762                     cmatch = 0; // complete match.  just reset the counter
2763
2764                     /*
2765                         it's possible for the ICS to not include the space
2766                         at the end of the last word, making our [correct]
2767                         join operation fuse two separate words.  the server
2768                         does this when the space occurs at the width setting.
2769                     */
2770                     if (!buf_len || buf[buf_len-1] != ' ')
2771                     {
2772                         *bp++ = ' ';
2773                         buf_len++;
2774                     }
2775                 }
2776                 continue;
2777             }
2778             else if (cmatch)
2779             {
2780                 /*
2781                     match failed, so we have to copy what matched before
2782                     falling through and copying this character.  In reality,
2783                     this will only ever be just the newline character, but
2784                     it doesn't hurt to be precise.
2785                 */
2786                 strncpy(bp, cont_seq, cmatch);
2787                 bp += cmatch;
2788                 buf_len += cmatch;
2789                 cmatch = 0;
2790             }
2791         }
2792
2793         // copy this char
2794         *bp++ = data[i];
2795         buf_len++;
2796     }
2797
2798         buf[buf_len] = NULLCHAR;
2799 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2800         next_out = 0;
2801         leftover_start = 0;
2802
2803         i = 0;
2804         while (i < buf_len) {
2805             /* Deal with part of the TELNET option negotiation
2806                protocol.  We refuse to do anything beyond the
2807                defaults, except that we allow the WILL ECHO option,
2808                which ICS uses to turn off password echoing when we are
2809                directly connected to it.  We reject this option
2810                if localLineEditing mode is on (always on in xboard)
2811                and we are talking to port 23, which might be a real
2812                telnet server that will try to keep WILL ECHO on permanently.
2813              */
2814             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2815                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2816                 unsigned char option;
2817                 oldi = i;
2818                 switch ((unsigned char) buf[++i]) {
2819                   case TN_WILL:
2820                     if (appData.debugMode)
2821                       fprintf(debugFP, "\n<WILL ");
2822                     switch (option = (unsigned char) buf[++i]) {
2823                       case TN_ECHO:
2824                         if (appData.debugMode)
2825                           fprintf(debugFP, "ECHO ");
2826                         /* Reply only if this is a change, according
2827                            to the protocol rules. */
2828                         if (remoteEchoOption) break;
2829                         if (appData.localLineEditing &&
2830                             atoi(appData.icsPort) == TN_PORT) {
2831                             TelnetRequest(TN_DONT, TN_ECHO);
2832                         } else {
2833                             EchoOff();
2834                             TelnetRequest(TN_DO, TN_ECHO);
2835                             remoteEchoOption = TRUE;
2836                         }
2837                         break;
2838                       default:
2839                         if (appData.debugMode)
2840                           fprintf(debugFP, "%d ", option);
2841                         /* Whatever this is, we don't want it. */
2842                         TelnetRequest(TN_DONT, option);
2843                         break;
2844                     }
2845                     break;
2846                   case TN_WONT:
2847                     if (appData.debugMode)
2848                       fprintf(debugFP, "\n<WONT ");
2849                     switch (option = (unsigned char) buf[++i]) {
2850                       case TN_ECHO:
2851                         if (appData.debugMode)
2852                           fprintf(debugFP, "ECHO ");
2853                         /* Reply only if this is a change, according
2854                            to the protocol rules. */
2855                         if (!remoteEchoOption) break;
2856                         EchoOn();
2857                         TelnetRequest(TN_DONT, TN_ECHO);
2858                         remoteEchoOption = FALSE;
2859                         break;
2860                       default:
2861                         if (appData.debugMode)
2862                           fprintf(debugFP, "%d ", (unsigned char) option);
2863                         /* Whatever this is, it must already be turned
2864                            off, because we never agree to turn on
2865                            anything non-default, so according to the
2866                            protocol rules, we don't reply. */
2867                         break;
2868                     }
2869                     break;
2870                   case TN_DO:
2871                     if (appData.debugMode)
2872                       fprintf(debugFP, "\n<DO ");
2873                     switch (option = (unsigned char) buf[++i]) {
2874                       default:
2875                         /* Whatever this is, we refuse to do it. */
2876                         if (appData.debugMode)
2877                           fprintf(debugFP, "%d ", option);
2878                         TelnetRequest(TN_WONT, option);
2879                         break;
2880                     }
2881                     break;
2882                   case TN_DONT:
2883                     if (appData.debugMode)
2884                       fprintf(debugFP, "\n<DONT ");
2885                     switch (option = (unsigned char) buf[++i]) {
2886                       default:
2887                         if (appData.debugMode)
2888                           fprintf(debugFP, "%d ", option);
2889                         /* Whatever this is, we are already not doing
2890                            it, because we never agree to do anything
2891                            non-default, so according to the protocol
2892                            rules, we don't reply. */
2893                         break;
2894                     }
2895                     break;
2896                   case TN_IAC:
2897                     if (appData.debugMode)
2898                       fprintf(debugFP, "\n<IAC ");
2899                     /* Doubled IAC; pass it through */
2900                     i--;
2901                     break;
2902                   default:
2903                     if (appData.debugMode)
2904                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2905                     /* Drop all other telnet commands on the floor */
2906                     break;
2907                 }
2908                 if (oldi > next_out)
2909                   SendToPlayer(&buf[next_out], oldi - next_out);
2910                 if (++i > next_out)
2911                   next_out = i;
2912                 continue;
2913             }
2914
2915             /* OK, this at least will *usually* work */
2916             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2917                 loggedOn = TRUE;
2918             }
2919
2920             if (loggedOn && !intfSet) {
2921                 if (ics_type == ICS_ICC) {
2922                   snprintf(str, MSG_SIZ,
2923                           "/set-quietly interface %s\n/set-quietly style 12\n",
2924                           programVersion);
2925                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2926                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2927                 } else if (ics_type == ICS_CHESSNET) {
2928                   snprintf(str, MSG_SIZ, "/style 12\n");
2929                 } else {
2930                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2931                   strcat(str, programVersion);
2932                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2933                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2934                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2935 #ifdef WIN32
2936                   strcat(str, "$iset nohighlight 1\n");
2937 #endif
2938                   strcat(str, "$iset lock 1\n$style 12\n");
2939                 }
2940                 SendToICS(str);
2941                 NotifyFrontendLogin();
2942                 intfSet = TRUE;
2943             }
2944
2945             if (started == STARTED_COMMENT) {
2946                 /* Accumulate characters in comment */
2947                 parse[parse_pos++] = buf[i];
2948                 if (buf[i] == '\n') {
2949                     parse[parse_pos] = NULLCHAR;
2950                     if(chattingPartner>=0) {
2951                         char mess[MSG_SIZ];
2952                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2953                         OutputChatMessage(chattingPartner, mess);
2954                         chattingPartner = -1;
2955                         next_out = i+1; // [HGM] suppress printing in ICS window
2956                     } else
2957                     if(!suppressKibitz) // [HGM] kibitz
2958                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2959                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2960                         int nrDigit = 0, nrAlph = 0, j;
2961                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2962                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2963                         parse[parse_pos] = NULLCHAR;
2964                         // try to be smart: if it does not look like search info, it should go to
2965                         // ICS interaction window after all, not to engine-output window.
2966                         for(j=0; j<parse_pos; j++) { // count letters and digits
2967                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2968                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2969                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2970                         }
2971                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2972                             int depth=0; float score;
2973                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2974                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2975                                 pvInfoList[forwardMostMove-1].depth = depth;
2976                                 pvInfoList[forwardMostMove-1].score = 100*score;
2977                             }
2978                             OutputKibitz(suppressKibitz, parse);
2979                         } else {
2980                             char tmp[MSG_SIZ];
2981                             if(gameMode == IcsObserving) // restore original ICS messages
2982                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2983                             else
2984                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2985                             SendToPlayer(tmp, strlen(tmp));
2986                         }
2987                         next_out = i+1; // [HGM] suppress printing in ICS window
2988                     }
2989                     started = STARTED_NONE;
2990                 } else {
2991                     /* Don't match patterns against characters in comment */
2992                     i++;
2993                     continue;
2994                 }
2995             }
2996             if (started == STARTED_CHATTER) {
2997                 if (buf[i] != '\n') {
2998                     /* Don't match patterns against characters in chatter */
2999                     i++;
3000                     continue;
3001                 }
3002                 started = STARTED_NONE;
3003                 if(suppressKibitz) next_out = i+1;
3004             }
3005
3006             /* Kludge to deal with rcmd protocol */
3007             if (firstTime && looking_at(buf, &i, "\001*")) {
3008                 DisplayFatalError(&buf[1], 0, 1);
3009                 continue;
3010             } else {
3011                 firstTime = FALSE;
3012             }
3013
3014             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3015                 ics_type = ICS_ICC;
3016                 ics_prefix = "/";
3017                 if (appData.debugMode)
3018                   fprintf(debugFP, "ics_type %d\n", ics_type);
3019                 continue;
3020             }
3021             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3022                 ics_type = ICS_FICS;
3023                 ics_prefix = "$";
3024                 if (appData.debugMode)
3025                   fprintf(debugFP, "ics_type %d\n", ics_type);
3026                 continue;
3027             }
3028             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3029                 ics_type = ICS_CHESSNET;
3030                 ics_prefix = "/";
3031                 if (appData.debugMode)
3032                   fprintf(debugFP, "ics_type %d\n", ics_type);
3033                 continue;
3034             }
3035
3036             if (!loggedOn &&
3037                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3038                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3039                  looking_at(buf, &i, "will be \"*\""))) {
3040               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3041               continue;
3042             }
3043
3044             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3045               char buf[MSG_SIZ];
3046               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3047               DisplayIcsInteractionTitle(buf);
3048               have_set_title = TRUE;
3049             }
3050
3051             /* skip finger notes */
3052             if (started == STARTED_NONE &&
3053                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3054                  (buf[i] == '1' && buf[i+1] == '0')) &&
3055                 buf[i+2] == ':' && buf[i+3] == ' ') {
3056               started = STARTED_CHATTER;
3057               i += 3;
3058               continue;
3059             }
3060
3061             oldi = i;
3062             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3063             if(appData.seekGraph) {
3064                 if(soughtPending && MatchSoughtLine(buf+i)) {
3065                     i = strstr(buf+i, "rated") - buf;
3066                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3067                     next_out = leftover_start = i;
3068                     started = STARTED_CHATTER;
3069                     suppressKibitz = TRUE;
3070                     continue;
3071                 }
3072                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3073                         && looking_at(buf, &i, "* ads displayed")) {
3074                     soughtPending = FALSE;
3075                     seekGraphUp = TRUE;
3076                     DrawSeekGraph();
3077                     continue;
3078                 }
3079                 if(appData.autoRefresh) {
3080                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3081                         int s = (ics_type == ICS_ICC); // ICC format differs
3082                         if(seekGraphUp)
3083                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3084                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3085                         looking_at(buf, &i, "*% "); // eat prompt
3086                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3087                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3088                         next_out = i; // suppress
3089                         continue;
3090                     }
3091                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3092                         char *p = star_match[0];
3093                         while(*p) {
3094                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3095                             while(*p && *p++ != ' '); // next
3096                         }
3097                         looking_at(buf, &i, "*% "); // eat prompt
3098                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3099                         next_out = i;
3100                         continue;
3101                     }
3102                 }
3103             }
3104
3105             /* skip formula vars */
3106             if (started == STARTED_NONE &&
3107                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3108               started = STARTED_CHATTER;
3109               i += 3;
3110               continue;
3111             }
3112
3113             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3114             if (appData.autoKibitz && started == STARTED_NONE &&
3115                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3116                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3117                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3118                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3119                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3120                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3121                         suppressKibitz = TRUE;
3122                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3123                         next_out = i;
3124                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3125                                 && (gameMode == IcsPlayingWhite)) ||
3126                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3127                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3128                             started = STARTED_CHATTER; // own kibitz we simply discard
3129                         else {
3130                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3131                             parse_pos = 0; parse[0] = NULLCHAR;
3132                             savingComment = TRUE;
3133                             suppressKibitz = gameMode != IcsObserving ? 2 :
3134                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3135                         }
3136                         continue;
3137                 } else
3138                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3139                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3140                          && atoi(star_match[0])) {
3141                     // suppress the acknowledgements of our own autoKibitz
3142                     char *p;
3143                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3144                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3145                     SendToPlayer(star_match[0], strlen(star_match[0]));
3146                     if(looking_at(buf, &i, "*% ")) // eat prompt
3147                         suppressKibitz = FALSE;
3148                     next_out = i;
3149                     continue;
3150                 }
3151             } // [HGM] kibitz: end of patch
3152
3153             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3154
3155             // [HGM] chat: intercept tells by users for which we have an open chat window
3156             channel = -1;
3157             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3158                                            looking_at(buf, &i, "* whispers:") ||
3159                                            looking_at(buf, &i, "* kibitzes:") ||
3160                                            looking_at(buf, &i, "* shouts:") ||
3161                                            looking_at(buf, &i, "* c-shouts:") ||
3162                                            looking_at(buf, &i, "--> * ") ||
3163                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3164                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3165                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3166                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3167                 int p;
3168                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3169                 chattingPartner = -1;
3170
3171                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3172                 for(p=0; p<MAX_CHAT; p++) {
3173                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3174                     talker[0] = '['; strcat(talker, "] ");
3175                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3176                     chattingPartner = p; break;
3177                     }
3178                 } else
3179                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3180                 for(p=0; p<MAX_CHAT; p++) {
3181                     if(!strcmp("kibitzes", chatPartner[p])) {
3182                         talker[0] = '['; strcat(talker, "] ");
3183                         chattingPartner = p; break;
3184                     }
3185                 } else
3186                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3187                 for(p=0; p<MAX_CHAT; p++) {
3188                     if(!strcmp("whispers", chatPartner[p])) {
3189                         talker[0] = '['; strcat(talker, "] ");
3190                         chattingPartner = p; break;
3191                     }
3192                 } else
3193                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3194                   if(buf[i-8] == '-' && buf[i-3] == 't')
3195                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3196                     if(!strcmp("c-shouts", chatPartner[p])) {
3197                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3198                         chattingPartner = p; break;
3199                     }
3200                   }
3201                   if(chattingPartner < 0)
3202                   for(p=0; p<MAX_CHAT; p++) {
3203                     if(!strcmp("shouts", chatPartner[p])) {
3204                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3205                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3206                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3207                         chattingPartner = p; break;
3208                     }
3209                   }
3210                 }
3211                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3212                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3213                     talker[0] = 0; Colorize(ColorTell, FALSE);
3214                     chattingPartner = p; break;
3215                 }
3216                 if(chattingPartner<0) i = oldi; else {
3217                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3218                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3219                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3220                     started = STARTED_COMMENT;
3221                     parse_pos = 0; parse[0] = NULLCHAR;
3222                     savingComment = 3 + chattingPartner; // counts as TRUE
3223                     suppressKibitz = TRUE;
3224                     continue;
3225                 }
3226             } // [HGM] chat: end of patch
3227
3228           backup = i;
3229             if (appData.zippyTalk || appData.zippyPlay) {
3230                 /* [DM] Backup address for color zippy lines */
3231 #if ZIPPY
3232                if (loggedOn == TRUE)
3233                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3234                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3235 #endif
3236             } // [DM] 'else { ' deleted
3237                 if (
3238                     /* Regular tells and says */
3239                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3240                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3241                     looking_at(buf, &i, "* says: ") ||
3242                     /* Don't color "message" or "messages" output */
3243                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3244                     looking_at(buf, &i, "*. * at *:*: ") ||
3245                     looking_at(buf, &i, "--* (*:*): ") ||
3246                     /* Message notifications (same color as tells) */
3247                     looking_at(buf, &i, "* has left a message ") ||
3248                     looking_at(buf, &i, "* just sent you a message:\n") ||
3249                     /* Whispers and kibitzes */
3250                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3251                     looking_at(buf, &i, "* kibitzes: ") ||
3252                     /* Channel tells */
3253                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3254
3255                   if (tkind == 1 && strchr(star_match[0], ':')) {
3256                       /* Avoid "tells you:" spoofs in channels */
3257                      tkind = 3;
3258                   }
3259                   if (star_match[0][0] == NULLCHAR ||
3260                       strchr(star_match[0], ' ') ||
3261                       (tkind == 3 && strchr(star_match[1], ' '))) {
3262                     /* Reject bogus matches */
3263                     i = oldi;
3264                   } else {
3265                     if (appData.colorize) {
3266                       if (oldi > next_out) {
3267                         SendToPlayer(&buf[next_out], oldi - next_out);
3268                         next_out = oldi;
3269                       }
3270                       switch (tkind) {
3271                       case 1:
3272                         Colorize(ColorTell, FALSE);
3273                         curColor = ColorTell;
3274                         break;
3275                       case 2:
3276                         Colorize(ColorKibitz, FALSE);
3277                         curColor = ColorKibitz;
3278                         break;
3279                       case 3:
3280                         p = strrchr(star_match[1], '(');
3281                         if (p == NULL) {
3282                           p = star_match[1];
3283                         } else {
3284                           p++;
3285                         }
3286                         if (atoi(p) == 1) {
3287                           Colorize(ColorChannel1, FALSE);
3288                           curColor = ColorChannel1;
3289                         } else {
3290                           Colorize(ColorChannel, FALSE);
3291                           curColor = ColorChannel;
3292                         }
3293                         break;
3294                       case 5:
3295                         curColor = ColorNormal;
3296                         break;
3297                       }
3298                     }
3299                     if (started == STARTED_NONE && appData.autoComment &&
3300                         (gameMode == IcsObserving ||
3301                          gameMode == IcsPlayingWhite ||
3302                          gameMode == IcsPlayingBlack)) {
3303                       parse_pos = i - oldi;
3304                       memcpy(parse, &buf[oldi], parse_pos);
3305                       parse[parse_pos] = NULLCHAR;
3306                       started = STARTED_COMMENT;
3307                       savingComment = TRUE;
3308                     } else {
3309                       started = STARTED_CHATTER;
3310                       savingComment = FALSE;
3311                     }
3312                     loggedOn = TRUE;
3313                     continue;
3314                   }
3315                 }
3316
3317                 if (looking_at(buf, &i, "* s-shouts: ") ||
3318                     looking_at(buf, &i, "* c-shouts: ")) {
3319                     if (appData.colorize) {
3320                         if (oldi > next_out) {
3321                             SendToPlayer(&buf[next_out], oldi - next_out);
3322                             next_out = oldi;
3323                         }
3324                         Colorize(ColorSShout, FALSE);
3325                         curColor = ColorSShout;
3326                     }
3327                     loggedOn = TRUE;
3328                     started = STARTED_CHATTER;
3329                     continue;
3330                 }
3331
3332                 if (looking_at(buf, &i, "--->")) {
3333                     loggedOn = TRUE;
3334                     continue;
3335                 }
3336
3337                 if (looking_at(buf, &i, "* shouts: ") ||
3338                     looking_at(buf, &i, "--> ")) {
3339                     if (appData.colorize) {
3340                         if (oldi > next_out) {
3341                             SendToPlayer(&buf[next_out], oldi - next_out);
3342                             next_out = oldi;
3343                         }
3344                         Colorize(ColorShout, FALSE);
3345                         curColor = ColorShout;
3346                     }
3347                     loggedOn = TRUE;
3348                     started = STARTED_CHATTER;
3349                     continue;
3350                 }
3351
3352                 if (looking_at( buf, &i, "Challenge:")) {
3353                     if (appData.colorize) {
3354                         if (oldi > next_out) {
3355                             SendToPlayer(&buf[next_out], oldi - next_out);
3356                             next_out = oldi;
3357                         }
3358                         Colorize(ColorChallenge, FALSE);
3359                         curColor = ColorChallenge;
3360                     }
3361                     loggedOn = TRUE;
3362                     continue;
3363                 }
3364
3365                 if (looking_at(buf, &i, "* offers you") ||
3366                     looking_at(buf, &i, "* offers to be") ||
3367                     looking_at(buf, &i, "* would like to") ||
3368                     looking_at(buf, &i, "* requests to") ||
3369                     looking_at(buf, &i, "Your opponent offers") ||
3370                     looking_at(buf, &i, "Your opponent requests")) {
3371
3372                     if (appData.colorize) {
3373                         if (oldi > next_out) {
3374                             SendToPlayer(&buf[next_out], oldi - next_out);
3375                             next_out = oldi;
3376                         }
3377                         Colorize(ColorRequest, FALSE);
3378                         curColor = ColorRequest;
3379                     }
3380                     continue;
3381                 }
3382
3383                 if (looking_at(buf, &i, "* (*) seeking")) {
3384                     if (appData.colorize) {
3385                         if (oldi > next_out) {
3386                             SendToPlayer(&buf[next_out], oldi - next_out);
3387                             next_out = oldi;
3388                         }
3389                         Colorize(ColorSeek, FALSE);
3390                         curColor = ColorSeek;
3391                     }
3392                     continue;
3393             }
3394
3395           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3396
3397             if (looking_at(buf, &i, "\\   ")) {
3398                 if (prevColor != ColorNormal) {
3399                     if (oldi > next_out) {
3400                         SendToPlayer(&buf[next_out], oldi - next_out);
3401                         next_out = oldi;
3402                     }
3403                     Colorize(prevColor, TRUE);
3404                     curColor = prevColor;
3405                 }
3406                 if (savingComment) {
3407                     parse_pos = i - oldi;
3408                     memcpy(parse, &buf[oldi], parse_pos);
3409                     parse[parse_pos] = NULLCHAR;
3410                     started = STARTED_COMMENT;
3411                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3412                         chattingPartner = savingComment - 3; // kludge to remember the box
3413                 } else {
3414                     started = STARTED_CHATTER;
3415                 }
3416                 continue;
3417             }
3418
3419             if (looking_at(buf, &i, "Black Strength :") ||
3420                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3421                 looking_at(buf, &i, "<10>") ||
3422                 looking_at(buf, &i, "#@#")) {
3423                 /* Wrong board style */
3424                 loggedOn = TRUE;
3425                 SendToICS(ics_prefix);
3426                 SendToICS("set style 12\n");
3427                 SendToICS(ics_prefix);
3428                 SendToICS("refresh\n");
3429                 continue;
3430             }
3431
3432             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3433                 ICSInitScript();
3434                 have_sent_ICS_logon = 1;
3435                 continue;
3436             }
3437
3438             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3439                 (looking_at(buf, &i, "\n<12> ") ||
3440                  looking_at(buf, &i, "<12> "))) {
3441                 loggedOn = TRUE;
3442                 if (oldi > next_out) {
3443                     SendToPlayer(&buf[next_out], oldi - next_out);
3444                 }
3445                 next_out = i;
3446                 started = STARTED_BOARD;
3447                 parse_pos = 0;
3448                 continue;
3449             }
3450
3451             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3452                 looking_at(buf, &i, "<b1> ")) {
3453                 if (oldi > next_out) {
3454                     SendToPlayer(&buf[next_out], oldi - next_out);
3455                 }
3456                 next_out = i;
3457                 started = STARTED_HOLDINGS;
3458                 parse_pos = 0;
3459                 continue;
3460             }
3461
3462             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3463                 loggedOn = TRUE;
3464                 /* Header for a move list -- first line */
3465
3466                 switch (ics_getting_history) {
3467                   case H_FALSE:
3468                     switch (gameMode) {
3469                       case IcsIdle:
3470                       case BeginningOfGame:
3471                         /* User typed "moves" or "oldmoves" while we
3472                            were idle.  Pretend we asked for these
3473                            moves and soak them up so user can step
3474                            through them and/or save them.
3475                            */
3476                         Reset(FALSE, TRUE);
3477                         gameMode = IcsObserving;
3478                         ModeHighlight();
3479                         ics_gamenum = -1;
3480                         ics_getting_history = H_GOT_UNREQ_HEADER;
3481                         break;
3482                       case EditGame: /*?*/
3483                       case EditPosition: /*?*/
3484                         /* Should above feature work in these modes too? */
3485                         /* For now it doesn't */
3486                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3487                         break;
3488                       default:
3489                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3490                         break;
3491                     }
3492                     break;
3493                   case H_REQUESTED:
3494                     /* Is this the right one? */
3495                     if (gameInfo.white && gameInfo.black &&
3496                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3497                         strcmp(gameInfo.black, star_match[2]) == 0) {
3498                         /* All is well */
3499                         ics_getting_history = H_GOT_REQ_HEADER;
3500                     }
3501                     break;
3502                   case H_GOT_REQ_HEADER:
3503                   case H_GOT_UNREQ_HEADER:
3504                   case H_GOT_UNWANTED_HEADER:
3505                   case H_GETTING_MOVES:
3506                     /* Should not happen */
3507                     DisplayError(_("Error gathering move list: two headers"), 0);
3508                     ics_getting_history = H_FALSE;
3509                     break;
3510                 }
3511
3512                 /* Save player ratings into gameInfo if needed */
3513                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3514                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3515                     (gameInfo.whiteRating == -1 ||
3516                      gameInfo.blackRating == -1)) {
3517
3518                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3519                     gameInfo.blackRating = string_to_rating(star_match[3]);
3520                     if (appData.debugMode)
3521                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3522                               gameInfo.whiteRating, gameInfo.blackRating);
3523                 }
3524                 continue;
3525             }
3526
3527             if (looking_at(buf, &i,
3528               "* * match, initial time: * minute*, increment: * second")) {
3529                 /* Header for a move list -- second line */
3530                 /* Initial board will follow if this is a wild game */
3531                 if (gameInfo.event != NULL) free(gameInfo.event);
3532                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3533                 gameInfo.event = StrSave(str);
3534                 /* [HGM] we switched variant. Translate boards if needed. */
3535                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3536                 continue;
3537             }
3538
3539             if (looking_at(buf, &i, "Move  ")) {
3540                 /* Beginning of a move list */
3541                 switch (ics_getting_history) {
3542                   case H_FALSE:
3543                     /* Normally should not happen */
3544                     /* Maybe user hit reset while we were parsing */
3545                     break;
3546                   case H_REQUESTED:
3547                     /* Happens if we are ignoring a move list that is not
3548                      * the one we just requested.  Common if the user
3549                      * tries to observe two games without turning off
3550                      * getMoveList */
3551                     break;
3552                   case H_GETTING_MOVES:
3553                     /* Should not happen */
3554                     DisplayError(_("Error gathering move list: nested"), 0);
3555                     ics_getting_history = H_FALSE;
3556                     break;
3557                   case H_GOT_REQ_HEADER:
3558                     ics_getting_history = H_GETTING_MOVES;
3559                     started = STARTED_MOVES;
3560                     parse_pos = 0;
3561                     if (oldi > next_out) {
3562                         SendToPlayer(&buf[next_out], oldi - next_out);
3563                     }
3564                     break;
3565                   case H_GOT_UNREQ_HEADER:
3566                     ics_getting_history = H_GETTING_MOVES;
3567                     started = STARTED_MOVES_NOHIDE;
3568                     parse_pos = 0;
3569                     break;
3570                   case H_GOT_UNWANTED_HEADER:
3571                     ics_getting_history = H_FALSE;
3572                     break;
3573                 }
3574                 continue;
3575             }
3576
3577             if (looking_at(buf, &i, "% ") ||
3578                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3579                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3580                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3581                     soughtPending = FALSE;
3582                     seekGraphUp = TRUE;
3583                     DrawSeekGraph();
3584                 }
3585                 if(suppressKibitz) next_out = i;
3586                 savingComment = FALSE;
3587                 suppressKibitz = 0;
3588                 switch (started) {
3589                   case STARTED_MOVES:
3590                   case STARTED_MOVES_NOHIDE:
3591                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3592                     parse[parse_pos + i - oldi] = NULLCHAR;
3593                     ParseGameHistory(parse);
3594 #if ZIPPY
3595                     if (appData.zippyPlay && first.initDone) {
3596                         FeedMovesToProgram(&first, forwardMostMove);
3597                         if (gameMode == IcsPlayingWhite) {
3598                             if (WhiteOnMove(forwardMostMove)) {
3599                                 if (first.sendTime) {
3600                                   if (first.useColors) {
3601                                     SendToProgram("black\n", &first);
3602                                   }
3603                                   SendTimeRemaining(&first, TRUE);
3604                                 }
3605                                 if (first.useColors) {
3606                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3607                                 }
3608                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3609                                 first.maybeThinking = TRUE;
3610                             } else {
3611                                 if (first.usePlayother) {
3612                                   if (first.sendTime) {
3613                                     SendTimeRemaining(&first, TRUE);
3614                                   }
3615                                   SendToProgram("playother\n", &first);
3616                                   firstMove = FALSE;
3617                                 } else {
3618                                   firstMove = TRUE;
3619                                 }
3620                             }
3621                         } else if (gameMode == IcsPlayingBlack) {
3622                             if (!WhiteOnMove(forwardMostMove)) {
3623                                 if (first.sendTime) {
3624                                   if (first.useColors) {
3625                                     SendToProgram("white\n", &first);
3626                                   }
3627                                   SendTimeRemaining(&first, FALSE);
3628                                 }
3629                                 if (first.useColors) {
3630                                   SendToProgram("black\n", &first);
3631                                 }
3632                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3633                                 first.maybeThinking = TRUE;
3634                             } else {
3635                                 if (first.usePlayother) {
3636                                   if (first.sendTime) {
3637                                     SendTimeRemaining(&first, FALSE);
3638                                   }
3639                                   SendToProgram("playother\n", &first);
3640                                   firstMove = FALSE;
3641                                 } else {
3642                                   firstMove = TRUE;
3643                                 }
3644                             }
3645                         }
3646                     }
3647 #endif
3648                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3649                         /* Moves came from oldmoves or moves command
3650                            while we weren't doing anything else.
3651                            */
3652                         currentMove = forwardMostMove;
3653                         ClearHighlights();/*!!could figure this out*/
3654                         flipView = appData.flipView;
3655                         DrawPosition(TRUE, boards[currentMove]);
3656                         DisplayBothClocks();
3657                         snprintf(str, MSG_SIZ, "%s %s %s",
3658                                 gameInfo.white, _("vs."),  gameInfo.black);
3659                         DisplayTitle(str);
3660                         gameMode = IcsIdle;
3661                     } else {
3662                         /* Moves were history of an active game */
3663                         if (gameInfo.resultDetails != NULL) {
3664                             free(gameInfo.resultDetails);
3665                             gameInfo.resultDetails = NULL;
3666                         }
3667                     }
3668                     HistorySet(parseList, backwardMostMove,
3669                                forwardMostMove, currentMove-1);
3670                     DisplayMove(currentMove - 1);
3671                     if (started == STARTED_MOVES) next_out = i;
3672                     started = STARTED_NONE;
3673                     ics_getting_history = H_FALSE;
3674                     break;
3675
3676                   case STARTED_OBSERVE:
3677                     started = STARTED_NONE;
3678                     SendToICS(ics_prefix);
3679                     SendToICS("refresh\n");
3680                     break;
3681
3682                   default:
3683                     break;
3684                 }
3685                 if(bookHit) { // [HGM] book: simulate book reply
3686                     static char bookMove[MSG_SIZ]; // a bit generous?
3687
3688                     programStats.nodes = programStats.depth = programStats.time =
3689                     programStats.score = programStats.got_only_move = 0;
3690                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3691
3692                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3693                     strcat(bookMove, bookHit);
3694                     HandleMachineMove(bookMove, &first);
3695                 }
3696                 continue;
3697             }
3698
3699             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3700                  started == STARTED_HOLDINGS ||
3701                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3702                 /* Accumulate characters in move list or board */
3703                 parse[parse_pos++] = buf[i];
3704             }
3705
3706             /* Start of game messages.  Mostly we detect start of game
3707                when the first board image arrives.  On some versions
3708                of the ICS, though, we need to do a "refresh" after starting
3709                to observe in order to get the current board right away. */
3710             if (looking_at(buf, &i, "Adding game * to observation list")) {
3711                 started = STARTED_OBSERVE;
3712                 continue;
3713             }
3714
3715             /* Handle auto-observe */
3716             if (appData.autoObserve &&
3717                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3718                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3719                 char *player;
3720                 /* Choose the player that was highlighted, if any. */
3721                 if (star_match[0][0] == '\033' ||
3722                     star_match[1][0] != '\033') {
3723                     player = star_match[0];
3724                 } else {
3725                     player = star_match[2];
3726                 }
3727                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3728                         ics_prefix, StripHighlightAndTitle(player));
3729                 SendToICS(str);
3730
3731                 /* Save ratings from notify string */
3732                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3733                 player1Rating = string_to_rating(star_match[1]);
3734                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3735                 player2Rating = string_to_rating(star_match[3]);
3736
3737                 if (appData.debugMode)
3738                   fprintf(debugFP,
3739                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3740                           player1Name, player1Rating,
3741                           player2Name, player2Rating);
3742
3743                 continue;
3744             }
3745
3746             /* Deal with automatic examine mode after a game,
3747                and with IcsObserving -> IcsExamining transition */
3748             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3749                 looking_at(buf, &i, "has made you an examiner of game *")) {
3750
3751                 int gamenum = atoi(star_match[0]);
3752                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3753                     gamenum == ics_gamenum) {
3754                     /* We were already playing or observing this game;
3755                        no need to refetch history */
3756                     gameMode = IcsExamining;
3757                     if (pausing) {
3758                         pauseExamForwardMostMove = forwardMostMove;
3759                     } else if (currentMove < forwardMostMove) {
3760                         ForwardInner(forwardMostMove);
3761                     }
3762                 } else {
3763                     /* I don't think this case really can happen */
3764                     SendToICS(ics_prefix);
3765                     SendToICS("refresh\n");
3766                 }
3767                 continue;
3768             }
3769
3770             /* Error messages */
3771 //          if (ics_user_moved) {
3772             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3773                 if (looking_at(buf, &i, "Illegal move") ||
3774                     looking_at(buf, &i, "Not a legal move") ||
3775                     looking_at(buf, &i, "Your king is in check") ||
3776                     looking_at(buf, &i, "It isn't your turn") ||
3777                     looking_at(buf, &i, "It is not your move")) {
3778                     /* Illegal move */
3779                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3780                         currentMove = forwardMostMove-1;
3781                         DisplayMove(currentMove - 1); /* before DMError */
3782                         DrawPosition(FALSE, boards[currentMove]);
3783                         SwitchClocks(forwardMostMove-1); // [HGM] race
3784                         DisplayBothClocks();
3785                     }
3786                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3787                     ics_user_moved = 0;
3788                     continue;
3789                 }
3790             }
3791
3792             if (looking_at(buf, &i, "still have time") ||
3793                 looking_at(buf, &i, "not out of time") ||
3794                 looking_at(buf, &i, "either player is out of time") ||
3795                 looking_at(buf, &i, "has timeseal; checking")) {
3796                 /* We must have called his flag a little too soon */
3797                 whiteFlag = blackFlag = FALSE;
3798                 continue;
3799             }
3800
3801             if (looking_at(buf, &i, "added * seconds to") ||
3802                 looking_at(buf, &i, "seconds were added to")) {
3803                 /* Update the clocks */
3804                 SendToICS(ics_prefix);
3805                 SendToICS("refresh\n");
3806                 continue;
3807             }
3808
3809             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3810                 ics_clock_paused = TRUE;
3811                 StopClocks();
3812                 continue;
3813             }
3814
3815             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3816                 ics_clock_paused = FALSE;
3817                 StartClocks();
3818                 continue;
3819             }
3820
3821             /* Grab player ratings from the Creating: message.
3822                Note we have to check for the special case when
3823                the ICS inserts things like [white] or [black]. */
3824             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3825                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3826                 /* star_matches:
3827                    0    player 1 name (not necessarily white)
3828                    1    player 1 rating
3829                    2    empty, white, or black (IGNORED)
3830                    3    player 2 name (not necessarily black)
3831                    4    player 2 rating
3832
3833                    The names/ratings are sorted out when the game
3834                    actually starts (below).
3835                 */
3836                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3837                 player1Rating = string_to_rating(star_match[1]);
3838                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3839                 player2Rating = string_to_rating(star_match[4]);
3840
3841                 if (appData.debugMode)
3842                   fprintf(debugFP,
3843                           "Ratings from 'Creating:' %s %d, %s %d\n",
3844                           player1Name, player1Rating,
3845                           player2Name, player2Rating);
3846
3847                 continue;
3848             }
3849
3850             /* Improved generic start/end-of-game messages */
3851             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3852                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3853                 /* If tkind == 0: */
3854                 /* star_match[0] is the game number */
3855                 /*           [1] is the white player's name */
3856                 /*           [2] is the black player's name */
3857                 /* For end-of-game: */
3858                 /*           [3] is the reason for the game end */
3859                 /*           [4] is a PGN end game-token, preceded by " " */
3860                 /* For start-of-game: */
3861                 /*           [3] begins with "Creating" or "Continuing" */
3862                 /*           [4] is " *" or empty (don't care). */
3863                 int gamenum = atoi(star_match[0]);
3864                 char *whitename, *blackname, *why, *endtoken;
3865                 ChessMove endtype = EndOfFile;
3866
3867                 if (tkind == 0) {
3868                   whitename = star_match[1];
3869                   blackname = star_match[2];
3870                   why = star_match[3];
3871                   endtoken = star_match[4];
3872                 } else {
3873                   whitename = star_match[1];
3874                   blackname = star_match[3];
3875                   why = star_match[5];
3876                   endtoken = star_match[6];
3877                 }
3878
3879                 /* Game start messages */
3880                 if (strncmp(why, "Creating ", 9) == 0 ||
3881                     strncmp(why, "Continuing ", 11) == 0) {
3882                     gs_gamenum = gamenum;
3883                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3884                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3885                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3886 #if ZIPPY
3887                     if (appData.zippyPlay) {
3888                         ZippyGameStart(whitename, blackname);
3889                     }
3890 #endif /*ZIPPY*/
3891                     partnerBoardValid = FALSE; // [HGM] bughouse
3892                     continue;
3893                 }
3894
3895                 /* Game end messages */
3896                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3897                     ics_gamenum != gamenum) {
3898                     continue;
3899                 }
3900                 while (endtoken[0] == ' ') endtoken++;
3901                 switch (endtoken[0]) {
3902                   case '*':
3903                   default:
3904                     endtype = GameUnfinished;
3905                     break;
3906                   case '0':
3907                     endtype = BlackWins;
3908                     break;
3909                   case '1':
3910                     if (endtoken[1] == '/')
3911                       endtype = GameIsDrawn;
3912                     else
3913                       endtype = WhiteWins;
3914                     break;
3915                 }
3916                 GameEnds(endtype, why, GE_ICS);
3917 #if ZIPPY
3918                 if (appData.zippyPlay && first.initDone) {
3919                     ZippyGameEnd(endtype, why);
3920                     if (first.pr == NoProc) {
3921                       /* Start the next process early so that we'll
3922                          be ready for the next challenge */
3923                       StartChessProgram(&first);
3924                     }
3925                     /* Send "new" early, in case this command takes
3926                        a long time to finish, so that we'll be ready
3927                        for the next challenge. */
3928                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3929                     Reset(TRUE, TRUE);
3930                 }
3931 #endif /*ZIPPY*/
3932                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3933                 continue;
3934             }
3935
3936             if (looking_at(buf, &i, "Removing game * from observation") ||
3937                 looking_at(buf, &i, "no longer observing game *") ||
3938                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3939                 if (gameMode == IcsObserving &&
3940                     atoi(star_match[0]) == ics_gamenum)
3941                   {
3942                       /* icsEngineAnalyze */
3943                       if (appData.icsEngineAnalyze) {
3944                             ExitAnalyzeMode();
3945                             ModeHighlight();
3946                       }
3947                       StopClocks();
3948                       gameMode = IcsIdle;
3949                       ics_gamenum = -1;
3950                       ics_user_moved = FALSE;
3951                   }
3952                 continue;
3953             }
3954
3955             if (looking_at(buf, &i, "no longer examining game *")) {
3956                 if (gameMode == IcsExamining &&
3957                     atoi(star_match[0]) == ics_gamenum)
3958                   {
3959                       gameMode = IcsIdle;
3960                       ics_gamenum = -1;
3961                       ics_user_moved = FALSE;
3962                   }
3963                 continue;
3964             }
3965
3966             /* Advance leftover_start past any newlines we find,
3967                so only partial lines can get reparsed */
3968             if (looking_at(buf, &i, "\n")) {
3969                 prevColor = curColor;
3970                 if (curColor != ColorNormal) {
3971                     if (oldi > next_out) {
3972                         SendToPlayer(&buf[next_out], oldi - next_out);
3973                         next_out = oldi;
3974                     }
3975                     Colorize(ColorNormal, FALSE);
3976                     curColor = ColorNormal;
3977                 }
3978                 if (started == STARTED_BOARD) {
3979                     started = STARTED_NONE;
3980                     parse[parse_pos] = NULLCHAR;
3981                     ParseBoard12(parse);
3982                     ics_user_moved = 0;
3983
3984                     /* Send premove here */
3985                     if (appData.premove) {
3986                       char str[MSG_SIZ];
3987                       if (currentMove == 0 &&
3988                           gameMode == IcsPlayingWhite &&
3989                           appData.premoveWhite) {
3990                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3991                         if (appData.debugMode)
3992                           fprintf(debugFP, "Sending premove:\n");
3993                         SendToICS(str);
3994                       } else if (currentMove == 1 &&
3995                                  gameMode == IcsPlayingBlack &&
3996                                  appData.premoveBlack) {
3997                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3998                         if (appData.debugMode)
3999                           fprintf(debugFP, "Sending premove:\n");
4000                         SendToICS(str);
4001                       } else if (gotPremove) {
4002                         gotPremove = 0;
4003                         ClearPremoveHighlights();
4004                         if (appData.debugMode)
4005                           fprintf(debugFP, "Sending premove:\n");
4006                           UserMoveEvent(premoveFromX, premoveFromY,
4007                                         premoveToX, premoveToY,
4008                                         premovePromoChar);
4009                       }
4010                     }
4011
4012                     /* Usually suppress following prompt */
4013                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4014                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4015                         if (looking_at(buf, &i, "*% ")) {
4016                             savingComment = FALSE;
4017                             suppressKibitz = 0;
4018                         }
4019                     }
4020                     next_out = i;
4021                 } else if (started == STARTED_HOLDINGS) {
4022                     int gamenum;
4023                     char new_piece[MSG_SIZ];
4024                     started = STARTED_NONE;
4025                     parse[parse_pos] = NULLCHAR;
4026                     if (appData.debugMode)
4027                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4028                                                         parse, currentMove);
4029                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4030                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4031                         if (gameInfo.variant == VariantNormal) {
4032                           /* [HGM] We seem to switch variant during a game!
4033                            * Presumably no holdings were displayed, so we have
4034                            * to move the position two files to the right to
4035                            * create room for them!
4036                            */
4037                           VariantClass newVariant;
4038                           switch(gameInfo.boardWidth) { // base guess on board width
4039                                 case 9:  newVariant = VariantShogi; break;
4040                                 case 10: newVariant = VariantGreat; break;
4041                                 default: newVariant = VariantCrazyhouse; break;
4042                           }
4043                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4044                           /* Get a move list just to see the header, which
4045                              will tell us whether this is really bug or zh */
4046                           if (ics_getting_history == H_FALSE) {
4047                             ics_getting_history = H_REQUESTED;
4048                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4049                             SendToICS(str);
4050                           }
4051                         }
4052                         new_piece[0] = NULLCHAR;
4053                         sscanf(parse, "game %d white [%s black [%s <- %s",
4054                                &gamenum, white_holding, black_holding,
4055                                new_piece);
4056                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4057                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4058                         /* [HGM] copy holdings to board holdings area */
4059                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4060                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4061                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4062 #if ZIPPY
4063                         if (appData.zippyPlay && first.initDone) {
4064                             ZippyHoldings(white_holding, black_holding,
4065                                           new_piece);
4066                         }
4067 #endif /*ZIPPY*/
4068                         if (tinyLayout || smallLayout) {
4069                             char wh[16], bh[16];
4070                             PackHolding(wh, white_holding);
4071                             PackHolding(bh, black_holding);
4072                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4073                                     gameInfo.white, gameInfo.black);
4074                         } else {
4075                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4076                                     gameInfo.white, white_holding, _("vs."),
4077                                     gameInfo.black, black_holding);
4078                         }
4079                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4080                         DrawPosition(FALSE, boards[currentMove]);
4081                         DisplayTitle(str);
4082                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4083                         sscanf(parse, "game %d white [%s black [%s <- %s",
4084                                &gamenum, white_holding, black_holding,
4085                                new_piece);
4086                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4087                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4088                         /* [HGM] copy holdings to partner-board holdings area */
4089                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4090                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4091                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4092                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4093                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4094                       }
4095                     }
4096                     /* Suppress following prompt */
4097                     if (looking_at(buf, &i, "*% ")) {
4098                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4099                         savingComment = FALSE;
4100                         suppressKibitz = 0;
4101                     }
4102                     next_out = i;
4103                 }
4104                 continue;
4105             }
4106
4107             i++;                /* skip unparsed character and loop back */
4108         }
4109
4110         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4111 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4112 //          SendToPlayer(&buf[next_out], i - next_out);
4113             started != STARTED_HOLDINGS && leftover_start > next_out) {
4114             SendToPlayer(&buf[next_out], leftover_start - next_out);
4115             next_out = i;
4116         }
4117
4118         leftover_len = buf_len - leftover_start;
4119         /* if buffer ends with something we couldn't parse,
4120            reparse it after appending the next read */
4121
4122     } else if (count == 0) {
4123         RemoveInputSource(isr);
4124         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4125     } else {
4126         DisplayFatalError(_("Error reading from ICS"), error, 1);
4127     }
4128 }
4129
4130
4131 /* Board style 12 looks like this:
4132
4133    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4134
4135  * The "<12> " is stripped before it gets to this routine.  The two
4136  * trailing 0's (flip state and clock ticking) are later addition, and
4137  * some chess servers may not have them, or may have only the first.
4138  * Additional trailing fields may be added in the future.
4139  */
4140
4141 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4142
4143 #define RELATION_OBSERVING_PLAYED    0
4144 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4145 #define RELATION_PLAYING_MYMOVE      1
4146 #define RELATION_PLAYING_NOTMYMOVE  -1
4147 #define RELATION_EXAMINING           2
4148 #define RELATION_ISOLATED_BOARD     -3
4149 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4150
4151 void
4152 ParseBoard12 (char *string)
4153 {
4154     GameMode newGameMode;
4155     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4156     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4157     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4158     char to_play, board_chars[200];
4159     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4160     char black[32], white[32];
4161     Board board;
4162     int prevMove = currentMove;
4163     int ticking = 2;
4164     ChessMove moveType;
4165     int fromX, fromY, toX, toY;
4166     char promoChar;
4167     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4168     char *bookHit = NULL; // [HGM] book
4169     Boolean weird = FALSE, reqFlag = FALSE;
4170
4171     fromX = fromY = toX = toY = -1;
4172
4173     newGame = FALSE;
4174
4175     if (appData.debugMode)
4176       fprintf(debugFP, _("Parsing board: %s\n"), string);
4177
4178     move_str[0] = NULLCHAR;
4179     elapsed_time[0] = NULLCHAR;
4180     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4181         int  i = 0, j;
4182         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4183             if(string[i] == ' ') { ranks++; files = 0; }
4184             else files++;
4185             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4186             i++;
4187         }
4188         for(j = 0; j <i; j++) board_chars[j] = string[j];
4189         board_chars[i] = '\0';
4190         string += i + 1;
4191     }
4192     n = sscanf(string, PATTERN, &to_play, &double_push,
4193                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4194                &gamenum, white, black, &relation, &basetime, &increment,
4195                &white_stren, &black_stren, &white_time, &black_time,
4196                &moveNum, str, elapsed_time, move_str, &ics_flip,
4197                &ticking);
4198
4199     if (n < 21) {
4200         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4201         DisplayError(str, 0);
4202         return;
4203     }
4204
4205     /* Convert the move number to internal form */
4206     moveNum = (moveNum - 1) * 2;
4207     if (to_play == 'B') moveNum++;
4208     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4209       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4210                         0, 1);
4211       return;
4212     }
4213
4214     switch (relation) {
4215       case RELATION_OBSERVING_PLAYED:
4216       case RELATION_OBSERVING_STATIC:
4217         if (gamenum == -1) {
4218             /* Old ICC buglet */
4219             relation = RELATION_OBSERVING_STATIC;
4220         }
4221         newGameMode = IcsObserving;
4222         break;
4223       case RELATION_PLAYING_MYMOVE:
4224       case RELATION_PLAYING_NOTMYMOVE:
4225         newGameMode =
4226           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4227             IcsPlayingWhite : IcsPlayingBlack;
4228         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4229         break;
4230       case RELATION_EXAMINING:
4231         newGameMode = IcsExamining;
4232         break;
4233       case RELATION_ISOLATED_BOARD:
4234       default:
4235         /* Just display this board.  If user was doing something else,
4236            we will forget about it until the next board comes. */
4237         newGameMode = IcsIdle;
4238         break;
4239       case RELATION_STARTING_POSITION:
4240         newGameMode = gameMode;
4241         break;
4242     }
4243
4244     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4245          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4246       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4247       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4248       char *toSqr;
4249       for (k = 0; k < ranks; k++) {
4250         for (j = 0; j < files; j++)
4251           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4252         if(gameInfo.holdingsWidth > 1) {
4253              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4254              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4255         }
4256       }
4257       CopyBoard(partnerBoard, board);
4258       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4259         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4260         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4261       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4262       if(toSqr = strchr(str, '-')) {
4263         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4264         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4265       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4266       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4267       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4268       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4269       if(twoBoards) {
4270           DisplayWhiteClock(white_time*fac, to_play == 'W');
4271           DisplayBlackClock(black_time*fac, to_play != 'W');
4272           activePartner = to_play;
4273           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4274                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4275       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4276                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4277       DisplayMessage(partnerStatus, "");
4278         partnerBoardValid = TRUE;
4279       return;
4280     }
4281
4282     /* Modify behavior for initial board display on move listing
4283        of wild games.
4284        */
4285     switch (ics_getting_history) {
4286       case H_FALSE:
4287       case H_REQUESTED:
4288         break;
4289       case H_GOT_REQ_HEADER:
4290       case H_GOT_UNREQ_HEADER:
4291         /* This is the initial position of the current game */
4292         gamenum = ics_gamenum;
4293         moveNum = 0;            /* old ICS bug workaround */
4294         if (to_play == 'B') {
4295           startedFromSetupPosition = TRUE;
4296           blackPlaysFirst = TRUE;
4297           moveNum = 1;
4298           if (forwardMostMove == 0) forwardMostMove = 1;
4299           if (backwardMostMove == 0) backwardMostMove = 1;
4300           if (currentMove == 0) currentMove = 1;
4301         }
4302         newGameMode = gameMode;
4303         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4304         break;
4305       case H_GOT_UNWANTED_HEADER:
4306         /* This is an initial board that we don't want */
4307         return;
4308       case H_GETTING_MOVES:
4309         /* Should not happen */
4310         DisplayError(_("Error gathering move list: extra board"), 0);
4311         ics_getting_history = H_FALSE;
4312         return;
4313     }
4314
4315    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4316                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4317      /* [HGM] We seem to have switched variant unexpectedly
4318       * Try to guess new variant from board size
4319       */
4320           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4321           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4322           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4323           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4324           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4325           if(!weird) newVariant = VariantNormal;
4326           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4327           /* Get a move list just to see the header, which
4328              will tell us whether this is really bug or zh */
4329           if (ics_getting_history == H_FALSE) {
4330             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4331             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4332             SendToICS(str);
4333           }
4334     }
4335
4336     /* Take action if this is the first board of a new game, or of a
4337        different game than is currently being displayed.  */
4338     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4339         relation == RELATION_ISOLATED_BOARD) {
4340
4341         /* Forget the old game and get the history (if any) of the new one */
4342         if (gameMode != BeginningOfGame) {
4343           Reset(TRUE, TRUE);
4344         }
4345         newGame = TRUE;
4346         if (appData.autoRaiseBoard) BoardToTop();
4347         prevMove = -3;
4348         if (gamenum == -1) {
4349             newGameMode = IcsIdle;
4350         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4351                    appData.getMoveList && !reqFlag) {
4352             /* Need to get game history */
4353             ics_getting_history = H_REQUESTED;
4354             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4355             SendToICS(str);
4356         }
4357
4358         /* Initially flip the board to have black on the bottom if playing
4359            black or if the ICS flip flag is set, but let the user change
4360            it with the Flip View button. */
4361         flipView = appData.autoFlipView ?
4362           (newGameMode == IcsPlayingBlack) || ics_flip :
4363           appData.flipView;
4364
4365         /* Done with values from previous mode; copy in new ones */
4366         gameMode = newGameMode;
4367         ModeHighlight();
4368         ics_gamenum = gamenum;
4369         if (gamenum == gs_gamenum) {
4370             int klen = strlen(gs_kind);
4371             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4372             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4373             gameInfo.event = StrSave(str);
4374         } else {
4375             gameInfo.event = StrSave("ICS game");
4376         }
4377         gameInfo.site = StrSave(appData.icsHost);
4378         gameInfo.date = PGNDate();
4379         gameInfo.round = StrSave("-");
4380         gameInfo.white = StrSave(white);
4381         gameInfo.black = StrSave(black);
4382         timeControl = basetime * 60 * 1000;
4383         timeControl_2 = 0;
4384         timeIncrement = increment * 1000;
4385         movesPerSession = 0;
4386         gameInfo.timeControl = TimeControlTagValue();
4387         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4388   if (appData.debugMode) {
4389     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4390     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4391     setbuf(debugFP, NULL);
4392   }
4393
4394         gameInfo.outOfBook = NULL;
4395
4396         /* Do we have the ratings? */
4397         if (strcmp(player1Name, white) == 0 &&
4398             strcmp(player2Name, black) == 0) {
4399             if (appData.debugMode)
4400               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4401                       player1Rating, player2Rating);
4402             gameInfo.whiteRating = player1Rating;
4403             gameInfo.blackRating = player2Rating;
4404         } else if (strcmp(player2Name, white) == 0 &&
4405                    strcmp(player1Name, black) == 0) {
4406             if (appData.debugMode)
4407               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4408                       player2Rating, player1Rating);
4409             gameInfo.whiteRating = player2Rating;
4410             gameInfo.blackRating = player1Rating;
4411         }
4412         player1Name[0] = player2Name[0] = NULLCHAR;
4413
4414         /* Silence shouts if requested */
4415         if (appData.quietPlay &&
4416             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4417             SendToICS(ics_prefix);
4418             SendToICS("set shout 0\n");
4419         }
4420     }
4421
4422     /* Deal with midgame name changes */
4423     if (!newGame) {
4424         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4425             if (gameInfo.white) free(gameInfo.white);
4426             gameInfo.white = StrSave(white);
4427         }
4428         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4429             if (gameInfo.black) free(gameInfo.black);
4430             gameInfo.black = StrSave(black);
4431         }
4432     }
4433
4434     /* Throw away game result if anything actually changes in examine mode */
4435     if (gameMode == IcsExamining && !newGame) {
4436         gameInfo.result = GameUnfinished;
4437         if (gameInfo.resultDetails != NULL) {
4438             free(gameInfo.resultDetails);
4439             gameInfo.resultDetails = NULL;
4440         }
4441     }
4442
4443     /* In pausing && IcsExamining mode, we ignore boards coming
4444        in if they are in a different variation than we are. */
4445     if (pauseExamInvalid) return;
4446     if (pausing && gameMode == IcsExamining) {
4447         if (moveNum <= pauseExamForwardMostMove) {
4448             pauseExamInvalid = TRUE;
4449             forwardMostMove = pauseExamForwardMostMove;
4450             return;
4451         }
4452     }
4453
4454   if (appData.debugMode) {
4455     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4456   }
4457     /* Parse the board */
4458     for (k = 0; k < ranks; k++) {
4459       for (j = 0; j < files; j++)
4460         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4461       if(gameInfo.holdingsWidth > 1) {
4462            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4463            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4464       }
4465     }
4466     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4467       board[5][BOARD_RGHT+1] = WhiteAngel;
4468       board[6][BOARD_RGHT+1] = WhiteMarshall;
4469       board[1][0] = BlackMarshall;
4470       board[2][0] = BlackAngel;
4471       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4472     }
4473     CopyBoard(boards[moveNum], board);
4474     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4475     if (moveNum == 0) {
4476         startedFromSetupPosition =
4477           !CompareBoards(board, initialPosition);
4478         if(startedFromSetupPosition)
4479             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4480     }
4481
4482     /* [HGM] Set castling rights. Take the outermost Rooks,
4483        to make it also work for FRC opening positions. Note that board12
4484        is really defective for later FRC positions, as it has no way to
4485        indicate which Rook can castle if they are on the same side of King.
4486        For the initial position we grant rights to the outermost Rooks,
4487        and remember thos rights, and we then copy them on positions
4488        later in an FRC game. This means WB might not recognize castlings with
4489        Rooks that have moved back to their original position as illegal,
4490        but in ICS mode that is not its job anyway.
4491     */
4492     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4493     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4494
4495         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4496             if(board[0][i] == WhiteRook) j = i;
4497         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4498         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4499             if(board[0][i] == WhiteRook) j = i;
4500         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4501         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4502             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4503         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4504         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4505             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4506         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4507
4508         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4509         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4510         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4511             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4512         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4513             if(board[BOARD_HEIGHT-1][k] == bKing)
4514                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4515         if(gameInfo.variant == VariantTwoKings) {
4516             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4517             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4518             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4519         }
4520     } else { int r;
4521         r = boards[moveNum][CASTLING][0] = initialRights[0];
4522         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4523         r = boards[moveNum][CASTLING][1] = initialRights[1];
4524         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4525         r = boards[moveNum][CASTLING][3] = initialRights[3];
4526         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4527         r = boards[moveNum][CASTLING][4] = initialRights[4];
4528         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4529         /* wildcastle kludge: always assume King has rights */
4530         r = boards[moveNum][CASTLING][2] = initialRights[2];
4531         r = boards[moveNum][CASTLING][5] = initialRights[5];
4532     }
4533     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4534     boards[moveNum][EP_STATUS] = EP_NONE;
4535     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4536     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4537     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4538
4539
4540     if (ics_getting_history == H_GOT_REQ_HEADER ||
4541         ics_getting_history == H_GOT_UNREQ_HEADER) {
4542         /* This was an initial position from a move list, not
4543            the current position */
4544         return;
4545     }
4546
4547     /* Update currentMove and known move number limits */
4548     newMove = newGame || moveNum > forwardMostMove;
4549
4550     if (newGame) {
4551         forwardMostMove = backwardMostMove = currentMove = moveNum;
4552         if (gameMode == IcsExamining && moveNum == 0) {
4553           /* Workaround for ICS limitation: we are not told the wild
4554              type when starting to examine a game.  But if we ask for
4555              the move list, the move list header will tell us */
4556             ics_getting_history = H_REQUESTED;
4557             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4558             SendToICS(str);
4559         }
4560     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4561                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4562 #if ZIPPY
4563         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4564         /* [HGM] applied this also to an engine that is silently watching        */
4565         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4566             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4567             gameInfo.variant == currentlyInitializedVariant) {
4568           takeback = forwardMostMove - moveNum;
4569           for (i = 0; i < takeback; i++) {
4570             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4571             SendToProgram("undo\n", &first);
4572           }
4573         }
4574 #endif
4575
4576         forwardMostMove = moveNum;
4577         if (!pausing || currentMove > forwardMostMove)
4578           currentMove = forwardMostMove;
4579     } else {
4580         /* New part of history that is not contiguous with old part */
4581         if (pausing && gameMode == IcsExamining) {
4582             pauseExamInvalid = TRUE;
4583             forwardMostMove = pauseExamForwardMostMove;
4584             return;
4585         }
4586         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4587 #if ZIPPY
4588             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4589                 // [HGM] when we will receive the move list we now request, it will be
4590                 // fed to the engine from the first move on. So if the engine is not
4591                 // in the initial position now, bring it there.
4592                 InitChessProgram(&first, 0);
4593             }
4594 #endif
4595             ics_getting_history = H_REQUESTED;
4596             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4597             SendToICS(str);
4598         }
4599         forwardMostMove = backwardMostMove = currentMove = moveNum;
4600     }
4601
4602     /* Update the clocks */
4603     if (strchr(elapsed_time, '.')) {
4604       /* Time is in ms */
4605       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4606       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4607     } else {
4608       /* Time is in seconds */
4609       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4610       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4611     }
4612
4613
4614 #if ZIPPY
4615     if (appData.zippyPlay && newGame &&
4616         gameMode != IcsObserving && gameMode != IcsIdle &&
4617         gameMode != IcsExamining)
4618       ZippyFirstBoard(moveNum, basetime, increment);
4619 #endif
4620
4621     /* Put the move on the move list, first converting
4622        to canonical algebraic form. */
4623     if (moveNum > 0) {
4624   if (appData.debugMode) {
4625     if (appData.debugMode) { int f = forwardMostMove;
4626         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4627                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4628                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4629     }
4630     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4631     fprintf(debugFP, "moveNum = %d\n", moveNum);
4632     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4633     setbuf(debugFP, NULL);
4634   }
4635         if (moveNum <= backwardMostMove) {
4636             /* We don't know what the board looked like before
4637                this move.  Punt. */
4638           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4639             strcat(parseList[moveNum - 1], " ");
4640             strcat(parseList[moveNum - 1], elapsed_time);
4641             moveList[moveNum - 1][0] = NULLCHAR;
4642         } else if (strcmp(move_str, "none") == 0) {
4643             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4644             /* Again, we don't know what the board looked like;
4645                this is really the start of the game. */
4646             parseList[moveNum - 1][0] = NULLCHAR;
4647             moveList[moveNum - 1][0] = NULLCHAR;
4648             backwardMostMove = moveNum;
4649             startedFromSetupPosition = TRUE;
4650             fromX = fromY = toX = toY = -1;
4651         } else {
4652           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4653           //                 So we parse the long-algebraic move string in stead of the SAN move
4654           int valid; char buf[MSG_SIZ], *prom;
4655
4656           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4657                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4658           // str looks something like "Q/a1-a2"; kill the slash
4659           if(str[1] == '/')
4660             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4661           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4662           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4663                 strcat(buf, prom); // long move lacks promo specification!
4664           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4665                 if(appData.debugMode)
4666                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4667                 safeStrCpy(move_str, buf, MSG_SIZ);
4668           }
4669           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4670                                 &fromX, &fromY, &toX, &toY, &promoChar)
4671                || ParseOneMove(buf, moveNum - 1, &moveType,
4672                                 &fromX, &fromY, &toX, &toY, &promoChar);
4673           // end of long SAN patch
4674           if (valid) {
4675             (void) CoordsToAlgebraic(boards[moveNum - 1],
4676                                      PosFlags(moveNum - 1),
4677                                      fromY, fromX, toY, toX, promoChar,
4678                                      parseList[moveNum-1]);
4679             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4680               case MT_NONE:
4681               case MT_STALEMATE:
4682               default:
4683                 break;
4684               case MT_CHECK:
4685                 if(gameInfo.variant != VariantShogi)
4686                     strcat(parseList[moveNum - 1], "+");
4687                 break;
4688               case MT_CHECKMATE:
4689               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4690                 strcat(parseList[moveNum - 1], "#");
4691                 break;
4692             }
4693             strcat(parseList[moveNum - 1], " ");
4694             strcat(parseList[moveNum - 1], elapsed_time);
4695             /* currentMoveString is set as a side-effect of ParseOneMove */
4696             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4697             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4698             strcat(moveList[moveNum - 1], "\n");
4699
4700             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4701                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4702               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4703                 ChessSquare old, new = boards[moveNum][k][j];
4704                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4705                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4706                   if(old == new) continue;
4707                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4708                   else if(new == WhiteWazir || new == BlackWazir) {
4709                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4710                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4711                       else boards[moveNum][k][j] = old; // preserve type of Gold
4712                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4713                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4714               }
4715           } else {
4716             /* Move from ICS was illegal!?  Punt. */
4717             if (appData.debugMode) {
4718               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4719               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4720             }
4721             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4722             strcat(parseList[moveNum - 1], " ");
4723             strcat(parseList[moveNum - 1], elapsed_time);
4724             moveList[moveNum - 1][0] = NULLCHAR;
4725             fromX = fromY = toX = toY = -1;
4726           }
4727         }
4728   if (appData.debugMode) {
4729     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4730     setbuf(debugFP, NULL);
4731   }
4732
4733 #if ZIPPY
4734         /* Send move to chess program (BEFORE animating it). */
4735         if (appData.zippyPlay && !newGame && newMove &&
4736            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4737
4738             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4739                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4740                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4741                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4742                             move_str);
4743                     DisplayError(str, 0);
4744                 } else {
4745                     if (first.sendTime) {
4746                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4747                     }
4748                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4749                     if (firstMove && !bookHit) {
4750                         firstMove = FALSE;
4751                         if (first.useColors) {
4752                           SendToProgram(gameMode == IcsPlayingWhite ?
4753                                         "white\ngo\n" :
4754                                         "black\ngo\n", &first);
4755                         } else {
4756                           SendToProgram("go\n", &first);
4757                         }
4758                         first.maybeThinking = TRUE;
4759                     }
4760                 }
4761             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4762               if (moveList[moveNum - 1][0] == NULLCHAR) {
4763                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4764                 DisplayError(str, 0);
4765               } else {
4766                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4767                 SendMoveToProgram(moveNum - 1, &first);
4768               }
4769             }
4770         }
4771 #endif
4772     }
4773
4774     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4775         /* If move comes from a remote source, animate it.  If it
4776            isn't remote, it will have already been animated. */
4777         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4778             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4779         }
4780         if (!pausing && appData.highlightLastMove) {
4781             SetHighlights(fromX, fromY, toX, toY);
4782         }
4783     }
4784
4785     /* Start the clocks */
4786     whiteFlag = blackFlag = FALSE;
4787     appData.clockMode = !(basetime == 0 && increment == 0);
4788     if (ticking == 0) {
4789       ics_clock_paused = TRUE;
4790       StopClocks();
4791     } else if (ticking == 1) {
4792       ics_clock_paused = FALSE;
4793     }
4794     if (gameMode == IcsIdle ||
4795         relation == RELATION_OBSERVING_STATIC ||
4796         relation == RELATION_EXAMINING ||
4797         ics_clock_paused)
4798       DisplayBothClocks();
4799     else
4800       StartClocks();
4801
4802     /* Display opponents and material strengths */
4803     if (gameInfo.variant != VariantBughouse &&
4804         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4805         if (tinyLayout || smallLayout) {
4806             if(gameInfo.variant == VariantNormal)
4807               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4808                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4809                     basetime, increment);
4810             else
4811               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4812                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4813                     basetime, increment, (int) gameInfo.variant);
4814         } else {
4815             if(gameInfo.variant == VariantNormal)
4816               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4817                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4818                     basetime, increment);
4819             else
4820               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4821                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4822                     basetime, increment, VariantName(gameInfo.variant));
4823         }
4824         DisplayTitle(str);
4825   if (appData.debugMode) {
4826     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4827   }
4828     }
4829
4830
4831     /* Display the board */
4832     if (!pausing && !appData.noGUI) {
4833
4834       if (appData.premove)
4835           if (!gotPremove ||
4836              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4837              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4838               ClearPremoveHighlights();
4839
4840       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4841         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4842       DrawPosition(j, boards[currentMove]);
4843
4844       DisplayMove(moveNum - 1);
4845       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4846             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4847               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4848         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4849       }
4850     }
4851
4852     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4853 #if ZIPPY
4854     if(bookHit) { // [HGM] book: simulate book reply
4855         static char bookMove[MSG_SIZ]; // a bit generous?
4856
4857         programStats.nodes = programStats.depth = programStats.time =
4858         programStats.score = programStats.got_only_move = 0;
4859         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4860
4861         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4862         strcat(bookMove, bookHit);
4863         HandleMachineMove(bookMove, &first);
4864     }
4865 #endif
4866 }
4867
4868 void
4869 GetMoveListEvent ()
4870 {
4871     char buf[MSG_SIZ];
4872     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4873         ics_getting_history = H_REQUESTED;
4874         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4875         SendToICS(buf);
4876     }
4877 }
4878
4879 void
4880 AnalysisPeriodicEvent (int force)
4881 {
4882     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4883          && !force) || !appData.periodicUpdates)
4884       return;
4885
4886     /* Send . command to Crafty to collect stats */
4887     SendToProgram(".\n", &first);
4888
4889     /* Don't send another until we get a response (this makes
4890        us stop sending to old Crafty's which don't understand
4891        the "." command (sending illegal cmds resets node count & time,
4892        which looks bad)) */
4893     programStats.ok_to_send = 0;
4894 }
4895
4896 void
4897 ics_update_width (int new_width)
4898 {
4899         ics_printf("set width %d\n", new_width);
4900 }
4901
4902 void
4903 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4904 {
4905     char buf[MSG_SIZ];
4906
4907     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4908         // null move in variant where engine does not understand it (for analysis purposes)
4909         SendBoard(cps, moveNum + 1); // send position after move in stead.
4910         return;
4911     }
4912     if (cps->useUsermove) {
4913       SendToProgram("usermove ", cps);
4914     }
4915     if (cps->useSAN) {
4916       char *space;
4917       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4918         int len = space - parseList[moveNum];
4919         memcpy(buf, parseList[moveNum], len);
4920         buf[len++] = '\n';
4921         buf[len] = NULLCHAR;
4922       } else {
4923         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4924       }
4925       SendToProgram(buf, cps);
4926     } else {
4927       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4928         AlphaRank(moveList[moveNum], 4);
4929         SendToProgram(moveList[moveNum], cps);
4930         AlphaRank(moveList[moveNum], 4); // and back
4931       } else
4932       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4933        * the engine. It would be nice to have a better way to identify castle
4934        * moves here. */
4935       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4936                                                                          && cps->useOOCastle) {
4937         int fromX = moveList[moveNum][0] - AAA;
4938         int fromY = moveList[moveNum][1] - ONE;
4939         int toX = moveList[moveNum][2] - AAA;
4940         int toY = moveList[moveNum][3] - ONE;
4941         if((boards[moveNum][fromY][fromX] == WhiteKing
4942             && boards[moveNum][toY][toX] == WhiteRook)
4943            || (boards[moveNum][fromY][fromX] == BlackKing
4944                && boards[moveNum][toY][toX] == BlackRook)) {
4945           if(toX > fromX) SendToProgram("O-O\n", cps);
4946           else SendToProgram("O-O-O\n", cps);
4947         }
4948         else SendToProgram(moveList[moveNum], cps);
4949       } else
4950       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4951         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4952           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4953           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4954                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4955         } else
4956           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4957                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4958         SendToProgram(buf, cps);
4959       }
4960       else SendToProgram(moveList[moveNum], cps);
4961       /* End of additions by Tord */
4962     }
4963
4964     /* [HGM] setting up the opening has brought engine in force mode! */
4965     /*       Send 'go' if we are in a mode where machine should play. */
4966     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4967         (gameMode == TwoMachinesPlay   ||
4968 #if ZIPPY
4969          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4970 #endif
4971          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4972         SendToProgram("go\n", cps);
4973   if (appData.debugMode) {
4974     fprintf(debugFP, "(extra)\n");
4975   }
4976     }
4977     setboardSpoiledMachineBlack = 0;
4978 }
4979
4980 void
4981 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4982 {
4983     char user_move[MSG_SIZ];
4984     char suffix[4];
4985
4986     if(gameInfo.variant == VariantSChess && promoChar) {
4987         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4988         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4989     } else suffix[0] = NULLCHAR;
4990
4991     switch (moveType) {
4992       default:
4993         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4994                 (int)moveType, fromX, fromY, toX, toY);
4995         DisplayError(user_move + strlen("say "), 0);
4996         break;
4997       case WhiteKingSideCastle:
4998       case BlackKingSideCastle:
4999       case WhiteQueenSideCastleWild:
5000       case BlackQueenSideCastleWild:
5001       /* PUSH Fabien */
5002       case WhiteHSideCastleFR:
5003       case BlackHSideCastleFR:
5004       /* POP Fabien */
5005         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5006         break;
5007       case WhiteQueenSideCastle:
5008       case BlackQueenSideCastle:
5009       case WhiteKingSideCastleWild:
5010       case BlackKingSideCastleWild:
5011       /* PUSH Fabien */
5012       case WhiteASideCastleFR:
5013       case BlackASideCastleFR:
5014       /* POP Fabien */
5015         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5016         break;
5017       case WhiteNonPromotion:
5018       case BlackNonPromotion:
5019         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5020         break;
5021       case WhitePromotion:
5022       case BlackPromotion:
5023         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5024           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5025                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5026                 PieceToChar(WhiteFerz));
5027         else if(gameInfo.variant == VariantGreat)
5028           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5029                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5030                 PieceToChar(WhiteMan));
5031         else
5032           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5033                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5034                 promoChar);
5035         break;
5036       case WhiteDrop:
5037       case BlackDrop:
5038       drop:
5039         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5040                  ToUpper(PieceToChar((ChessSquare) fromX)),
5041                  AAA + toX, ONE + toY);
5042         break;
5043       case IllegalMove:  /* could be a variant we don't quite understand */
5044         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5045       case NormalMove:
5046       case WhiteCapturesEnPassant:
5047       case BlackCapturesEnPassant:
5048         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5049                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5050         break;
5051     }
5052     SendToICS(user_move);
5053     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5054         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5055 }
5056
5057 void
5058 UploadGameEvent ()
5059 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5060     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5061     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5062     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5063       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5064       return;
5065     }
5066     if(gameMode != IcsExamining) { // is this ever not the case?
5067         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5068
5069         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5070           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5071         } else { // on FICS we must first go to general examine mode
5072           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5073         }
5074         if(gameInfo.variant != VariantNormal) {
5075             // try figure out wild number, as xboard names are not always valid on ICS
5076             for(i=1; i<=36; i++) {
5077               snprintf(buf, MSG_SIZ, "wild/%d", i);
5078                 if(StringToVariant(buf) == gameInfo.variant) break;
5079             }
5080             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5081             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5082             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5083         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5084         SendToICS(ics_prefix);
5085         SendToICS(buf);
5086         if(startedFromSetupPosition || backwardMostMove != 0) {
5087           fen = PositionToFEN(backwardMostMove, NULL);
5088           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5089             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5090             SendToICS(buf);
5091           } else { // FICS: everything has to set by separate bsetup commands
5092             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5093             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5094             SendToICS(buf);
5095             if(!WhiteOnMove(backwardMostMove)) {
5096                 SendToICS("bsetup tomove black\n");
5097             }
5098             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5099             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5100             SendToICS(buf);
5101             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5102             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5103             SendToICS(buf);
5104             i = boards[backwardMostMove][EP_STATUS];
5105             if(i >= 0) { // set e.p.
5106               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5107                 SendToICS(buf);
5108             }
5109             bsetup++;
5110           }
5111         }
5112       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5113             SendToICS("bsetup done\n"); // switch to normal examining.
5114     }
5115     for(i = backwardMostMove; i<last; i++) {
5116         char buf[20];
5117         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5118         SendToICS(buf);
5119     }
5120     SendToICS(ics_prefix);
5121     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5122 }
5123
5124 void
5125 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5126 {
5127     if (rf == DROP_RANK) {
5128       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5129       sprintf(move, "%c@%c%c\n",
5130                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5131     } else {
5132         if (promoChar == 'x' || promoChar == NULLCHAR) {
5133           sprintf(move, "%c%c%c%c\n",
5134                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5135         } else {
5136             sprintf(move, "%c%c%c%c%c\n",
5137                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5138         }
5139     }
5140 }
5141
5142 void
5143 ProcessICSInitScript (FILE *f)
5144 {
5145     char buf[MSG_SIZ];
5146
5147     while (fgets(buf, MSG_SIZ, f)) {
5148         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5149     }
5150
5151     fclose(f);
5152 }
5153
5154
5155 static int lastX, lastY, selectFlag, dragging;
5156
5157 void
5158 Sweep (int step)
5159 {
5160     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5161     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5162     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5163     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5164     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5165     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5166     do {
5167         promoSweep -= step;
5168         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5169         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5170         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5171         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5172         if(!step) step = -1;
5173     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5174             appData.testLegality && (promoSweep == king ||
5175             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5176     ChangeDragPiece(promoSweep);
5177 }
5178
5179 int
5180 PromoScroll (int x, int y)
5181 {
5182   int step = 0;
5183
5184   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5185   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5186   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5187   if(!step) return FALSE;
5188   lastX = x; lastY = y;
5189   if((promoSweep < BlackPawn) == flipView) step = -step;
5190   if(step > 0) selectFlag = 1;
5191   if(!selectFlag) Sweep(step);
5192   return FALSE;
5193 }
5194
5195 void
5196 NextPiece (int step)
5197 {
5198     ChessSquare piece = boards[currentMove][toY][toX];
5199     do {
5200         pieceSweep -= step;
5201         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5202         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5203         if(!step) step = -1;
5204     } while(PieceToChar(pieceSweep) == '.');
5205     boards[currentMove][toY][toX] = pieceSweep;
5206     DrawPosition(FALSE, boards[currentMove]);
5207     boards[currentMove][toY][toX] = piece;
5208 }
5209 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5210 void
5211 AlphaRank (char *move, int n)
5212 {
5213 //    char *p = move, c; int x, y;
5214
5215     if (appData.debugMode) {
5216         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5217     }
5218
5219     if(move[1]=='*' &&
5220        move[2]>='0' && move[2]<='9' &&
5221        move[3]>='a' && move[3]<='x'    ) {
5222         move[1] = '@';
5223         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5224         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5225     } else
5226     if(move[0]>='0' && move[0]<='9' &&
5227        move[1]>='a' && move[1]<='x' &&
5228        move[2]>='0' && move[2]<='9' &&
5229        move[3]>='a' && move[3]<='x'    ) {
5230         /* input move, Shogi -> normal */
5231         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5232         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5233         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5234         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5235     } else
5236     if(move[1]=='@' &&
5237        move[3]>='0' && move[3]<='9' &&
5238        move[2]>='a' && move[2]<='x'    ) {
5239         move[1] = '*';
5240         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5241         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5242     } else
5243     if(
5244        move[0]>='a' && move[0]<='x' &&
5245        move[3]>='0' && move[3]<='9' &&
5246        move[2]>='a' && move[2]<='x'    ) {
5247          /* output move, normal -> Shogi */
5248         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5249         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5250         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5251         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5252         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5253     }
5254     if (appData.debugMode) {
5255         fprintf(debugFP, "   out = '%s'\n", move);
5256     }
5257 }
5258
5259 char yy_textstr[8000];
5260
5261 /* Parser for moves from gnuchess, ICS, or user typein box */
5262 Boolean
5263 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5264 {
5265     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5266
5267     switch (*moveType) {
5268       case WhitePromotion:
5269       case BlackPromotion:
5270       case WhiteNonPromotion:
5271       case BlackNonPromotion:
5272       case NormalMove:
5273       case WhiteCapturesEnPassant:
5274       case BlackCapturesEnPassant:
5275       case WhiteKingSideCastle:
5276       case WhiteQueenSideCastle:
5277       case BlackKingSideCastle:
5278       case BlackQueenSideCastle:
5279       case WhiteKingSideCastleWild:
5280       case WhiteQueenSideCastleWild:
5281       case BlackKingSideCastleWild:
5282       case BlackQueenSideCastleWild:
5283       /* Code added by Tord: */
5284       case WhiteHSideCastleFR:
5285       case WhiteASideCastleFR:
5286       case BlackHSideCastleFR:
5287       case BlackASideCastleFR:
5288       /* End of code added by Tord */
5289       case IllegalMove:         /* bug or odd chess variant */
5290         *fromX = currentMoveString[0] - AAA;
5291         *fromY = currentMoveString[1] - ONE;
5292         *toX = currentMoveString[2] - AAA;
5293         *toY = currentMoveString[3] - ONE;
5294         *promoChar = currentMoveString[4];
5295         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5296             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5297     if (appData.debugMode) {
5298         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5299     }
5300             *fromX = *fromY = *toX = *toY = 0;
5301             return FALSE;
5302         }
5303         if (appData.testLegality) {
5304           return (*moveType != IllegalMove);
5305         } else {
5306           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5307                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5308         }
5309
5310       case WhiteDrop:
5311       case BlackDrop:
5312         *fromX = *moveType == WhiteDrop ?
5313           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5314           (int) CharToPiece(ToLower(currentMoveString[0]));
5315         *fromY = DROP_RANK;
5316         *toX = currentMoveString[2] - AAA;
5317         *toY = currentMoveString[3] - ONE;
5318         *promoChar = NULLCHAR;
5319         return TRUE;
5320
5321       case AmbiguousMove:
5322       case ImpossibleMove:
5323       case EndOfFile:
5324       case ElapsedTime:
5325       case Comment:
5326       case PGNTag:
5327       case NAG:
5328       case WhiteWins:
5329       case BlackWins:
5330       case GameIsDrawn:
5331       default:
5332     if (appData.debugMode) {
5333         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5334     }
5335         /* bug? */
5336         *fromX = *fromY = *toX = *toY = 0;
5337         *promoChar = NULLCHAR;
5338         return FALSE;
5339     }
5340 }
5341
5342 Boolean pushed = FALSE;
5343 char *lastParseAttempt;
5344
5345 void
5346 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5347 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5348   int fromX, fromY, toX, toY; char promoChar;
5349   ChessMove moveType;
5350   Boolean valid;
5351   int nr = 0;
5352
5353   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5354     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5355     pushed = TRUE;
5356   }
5357   endPV = forwardMostMove;
5358   do {
5359     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5360     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5361     lastParseAttempt = pv;
5362     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5363     if(!valid && nr == 0 &&
5364        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5365         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5366         // Hande case where played move is different from leading PV move
5367         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5368         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5369         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5370         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5371           endPV += 2; // if position different, keep this
5372           moveList[endPV-1][0] = fromX + AAA;
5373           moveList[endPV-1][1] = fromY + ONE;
5374           moveList[endPV-1][2] = toX + AAA;
5375           moveList[endPV-1][3] = toY + ONE;
5376           parseList[endPV-1][0] = NULLCHAR;
5377           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5378         }
5379       }
5380     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5381     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5382     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5383     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5384         valid++; // allow comments in PV
5385         continue;
5386     }
5387     nr++;
5388     if(endPV+1 > framePtr) break; // no space, truncate
5389     if(!valid) break;
5390     endPV++;
5391     CopyBoard(boards[endPV], boards[endPV-1]);
5392     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5393     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5394     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5395     CoordsToAlgebraic(boards[endPV - 1],
5396                              PosFlags(endPV - 1),
5397                              fromY, fromX, toY, toX, promoChar,
5398                              parseList[endPV - 1]);
5399   } while(valid);
5400   if(atEnd == 2) return; // used hidden, for PV conversion
5401   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5402   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5403   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5404                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5405   DrawPosition(TRUE, boards[currentMove]);
5406 }
5407
5408 int
5409 MultiPV (ChessProgramState *cps)
5410 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5411         int i;
5412         for(i=0; i<cps->nrOptions; i++)
5413             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5414                 return i;
5415         return -1;
5416 }
5417
5418 Boolean
5419 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5420 {
5421         int startPV, multi, lineStart, origIndex = index;
5422         char *p, buf2[MSG_SIZ];
5423
5424         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5425         lastX = x; lastY = y;
5426         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5427         lineStart = startPV = index;
5428         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5429         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5430         index = startPV;
5431         do{ while(buf[index] && buf[index] != '\n') index++;
5432         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5433         buf[index] = 0;
5434         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5435                 int n = first.option[multi].value;
5436                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5437                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5438                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5439                 first.option[multi].value = n;
5440                 *start = *end = 0;
5441                 return FALSE;
5442         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5443                 ExcludeClick(origIndex - lineStart);
5444                 return FALSE;
5445         }
5446         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5447         *start = startPV; *end = index-1;
5448         return TRUE;
5449 }
5450
5451 char *
5452 PvToSAN (char *pv)
5453 {
5454         static char buf[10*MSG_SIZ];
5455         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5456         *buf = NULLCHAR;
5457         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5458         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5459         for(i = forwardMostMove; i<endPV; i++){
5460             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5461             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5462             k += strlen(buf+k);
5463         }
5464         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5465         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5466         endPV = savedEnd;
5467         return buf;
5468 }
5469
5470 Boolean
5471 LoadPV (int x, int y)
5472 { // called on right mouse click to load PV
5473   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5474   lastX = x; lastY = y;
5475   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5476   return TRUE;
5477 }
5478
5479 void
5480 UnLoadPV ()
5481 {
5482   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5483   if(endPV < 0) return;
5484   if(appData.autoCopyPV) CopyFENToClipboard();
5485   endPV = -1;
5486   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5487         Boolean saveAnimate = appData.animate;
5488         if(pushed) {
5489             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5490                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5491             } else storedGames--; // abandon shelved tail of original game
5492         }
5493         pushed = FALSE;
5494         forwardMostMove = currentMove;
5495         currentMove = oldFMM;
5496         appData.animate = FALSE;
5497         ToNrEvent(forwardMostMove);
5498         appData.animate = saveAnimate;
5499   }
5500   currentMove = forwardMostMove;
5501   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5502   ClearPremoveHighlights();
5503   DrawPosition(TRUE, boards[currentMove]);
5504 }
5505
5506 void
5507 MovePV (int x, int y, int h)
5508 { // step through PV based on mouse coordinates (called on mouse move)
5509   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5510
5511   // we must somehow check if right button is still down (might be released off board!)
5512   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5513   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5514   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5515   if(!step) return;
5516   lastX = x; lastY = y;
5517
5518   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5519   if(endPV < 0) return;
5520   if(y < margin) step = 1; else
5521   if(y > h - margin) step = -1;
5522   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5523   currentMove += step;
5524   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5525   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5526                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5527   DrawPosition(FALSE, boards[currentMove]);
5528 }
5529
5530
5531 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5532 // All positions will have equal probability, but the current method will not provide a unique
5533 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5534 #define DARK 1
5535 #define LITE 2
5536 #define ANY 3
5537
5538 int squaresLeft[4];
5539 int piecesLeft[(int)BlackPawn];
5540 int seed, nrOfShuffles;
5541
5542 void
5543 GetPositionNumber ()
5544 {       // sets global variable seed
5545         int i;
5546
5547         seed = appData.defaultFrcPosition;
5548         if(seed < 0) { // randomize based on time for negative FRC position numbers
5549                 for(i=0; i<50; i++) seed += random();
5550                 seed = random() ^ random() >> 8 ^ random() << 8;
5551                 if(seed<0) seed = -seed;
5552         }
5553 }
5554
5555 int
5556 put (Board board, int pieceType, int rank, int n, int shade)
5557 // put the piece on the (n-1)-th empty squares of the given shade
5558 {
5559         int i;
5560
5561         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5562                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5563                         board[rank][i] = (ChessSquare) pieceType;
5564                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5565                         squaresLeft[ANY]--;
5566                         piecesLeft[pieceType]--;
5567                         return i;
5568                 }
5569         }
5570         return -1;
5571 }
5572
5573
5574 void
5575 AddOnePiece (Board board, int pieceType, int rank, int shade)
5576 // calculate where the next piece goes, (any empty square), and put it there
5577 {
5578         int i;
5579
5580         i = seed % squaresLeft[shade];
5581         nrOfShuffles *= squaresLeft[shade];
5582         seed /= squaresLeft[shade];
5583         put(board, pieceType, rank, i, shade);
5584 }
5585
5586 void
5587 AddTwoPieces (Board board, int pieceType, int rank)
5588 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5589 {
5590         int i, n=squaresLeft[ANY], j=n-1, k;
5591
5592         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5593         i = seed % k;  // pick one
5594         nrOfShuffles *= k;
5595         seed /= k;
5596         while(i >= j) i -= j--;
5597         j = n - 1 - j; i += j;
5598         put(board, pieceType, rank, j, ANY);
5599         put(board, pieceType, rank, i, ANY);
5600 }
5601
5602 void
5603 SetUpShuffle (Board board, int number)
5604 {
5605         int i, p, first=1;
5606
5607         GetPositionNumber(); nrOfShuffles = 1;
5608
5609         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5610         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5611         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5612
5613         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5614
5615         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5616             p = (int) board[0][i];
5617             if(p < (int) BlackPawn) piecesLeft[p] ++;
5618             board[0][i] = EmptySquare;
5619         }
5620
5621         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5622             // shuffles restricted to allow normal castling put KRR first
5623             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5624                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5625             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5626                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5627             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5628                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5629             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5630                 put(board, WhiteRook, 0, 0, ANY);
5631             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5632         }
5633
5634         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5635             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5636             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5637                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5638                 while(piecesLeft[p] >= 2) {
5639                     AddOnePiece(board, p, 0, LITE);
5640                     AddOnePiece(board, p, 0, DARK);
5641                 }
5642                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5643             }
5644
5645         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5646             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5647             // but we leave King and Rooks for last, to possibly obey FRC restriction
5648             if(p == (int)WhiteRook) continue;
5649             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5650             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5651         }
5652
5653         // now everything is placed, except perhaps King (Unicorn) and Rooks
5654
5655         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5656             // Last King gets castling rights
5657             while(piecesLeft[(int)WhiteUnicorn]) {
5658                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5659                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5660             }
5661
5662             while(piecesLeft[(int)WhiteKing]) {
5663                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5664                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5665             }
5666
5667
5668         } else {
5669             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5670             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5671         }
5672
5673         // Only Rooks can be left; simply place them all
5674         while(piecesLeft[(int)WhiteRook]) {
5675                 i = put(board, WhiteRook, 0, 0, ANY);
5676                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5677                         if(first) {
5678                                 first=0;
5679                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5680                         }
5681                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5682                 }
5683         }
5684         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5685             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5686         }
5687
5688         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5689 }
5690
5691 int
5692 SetCharTable (char *table, const char * map)
5693 /* [HGM] moved here from winboard.c because of its general usefulness */
5694 /*       Basically a safe strcpy that uses the last character as King */
5695 {
5696     int result = FALSE; int NrPieces;
5697
5698     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5699                     && NrPieces >= 12 && !(NrPieces&1)) {
5700         int i; /* [HGM] Accept even length from 12 to 34 */
5701
5702         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5703         for( i=0; i<NrPieces/2-1; i++ ) {
5704             table[i] = map[i];
5705             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5706         }
5707         table[(int) WhiteKing]  = map[NrPieces/2-1];
5708         table[(int) BlackKing]  = map[NrPieces-1];
5709
5710         result = TRUE;
5711     }
5712
5713     return result;
5714 }
5715
5716 void
5717 Prelude (Board board)
5718 {       // [HGM] superchess: random selection of exo-pieces
5719         int i, j, k; ChessSquare p;
5720         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5721
5722         GetPositionNumber(); // use FRC position number
5723
5724         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5725             SetCharTable(pieceToChar, appData.pieceToCharTable);
5726             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5727                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5728         }
5729
5730         j = seed%4;                 seed /= 4;
5731         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5732         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5733         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5734         j = seed%3 + (seed%3 >= j); seed /= 3;
5735         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5736         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5737         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5738         j = seed%3;                 seed /= 3;
5739         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5740         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5741         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5742         j = seed%2 + (seed%2 >= j); seed /= 2;
5743         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5744         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5745         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5746         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5747         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5748         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5749         put(board, exoPieces[0],    0, 0, ANY);
5750         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5751 }
5752
5753 void
5754 InitPosition (int redraw)
5755 {
5756     ChessSquare (* pieces)[BOARD_FILES];
5757     int i, j, pawnRow, overrule,
5758     oldx = gameInfo.boardWidth,
5759     oldy = gameInfo.boardHeight,
5760     oldh = gameInfo.holdingsWidth;
5761     static int oldv;
5762
5763     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5764
5765     /* [AS] Initialize pv info list [HGM] and game status */
5766     {
5767         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5768             pvInfoList[i].depth = 0;
5769             boards[i][EP_STATUS] = EP_NONE;
5770             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5771         }
5772
5773         initialRulePlies = 0; /* 50-move counter start */
5774
5775         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5776         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5777     }
5778
5779
5780     /* [HGM] logic here is completely changed. In stead of full positions */
5781     /* the initialized data only consist of the two backranks. The switch */
5782     /* selects which one we will use, which is than copied to the Board   */
5783     /* initialPosition, which for the rest is initialized by Pawns and    */
5784     /* empty squares. This initial position is then copied to boards[0],  */
5785     /* possibly after shuffling, so that it remains available.            */
5786
5787     gameInfo.holdingsWidth = 0; /* default board sizes */
5788     gameInfo.boardWidth    = 8;
5789     gameInfo.boardHeight   = 8;
5790     gameInfo.holdingsSize  = 0;
5791     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5792     for(i=0; i<BOARD_FILES-2; i++)
5793       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5794     initialPosition[EP_STATUS] = EP_NONE;
5795     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5796     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5797          SetCharTable(pieceNickName, appData.pieceNickNames);
5798     else SetCharTable(pieceNickName, "............");
5799     pieces = FIDEArray;
5800
5801     switch (gameInfo.variant) {
5802     case VariantFischeRandom:
5803       shuffleOpenings = TRUE;
5804     default:
5805       break;
5806     case VariantShatranj:
5807       pieces = ShatranjArray;
5808       nrCastlingRights = 0;
5809       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5810       break;
5811     case VariantMakruk:
5812       pieces = makrukArray;
5813       nrCastlingRights = 0;
5814       startedFromSetupPosition = TRUE;
5815       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5816       break;
5817     case VariantTwoKings:
5818       pieces = twoKingsArray;
5819       break;
5820     case VariantGrand:
5821       pieces = GrandArray;
5822       nrCastlingRights = 0;
5823       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5824       gameInfo.boardWidth = 10;
5825       gameInfo.boardHeight = 10;
5826       gameInfo.holdingsSize = 7;
5827       break;
5828     case VariantCapaRandom:
5829       shuffleOpenings = TRUE;
5830     case VariantCapablanca:
5831       pieces = CapablancaArray;
5832       gameInfo.boardWidth = 10;
5833       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5834       break;
5835     case VariantGothic:
5836       pieces = GothicArray;
5837       gameInfo.boardWidth = 10;
5838       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5839       break;
5840     case VariantSChess:
5841       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5842       gameInfo.holdingsSize = 7;
5843       break;
5844     case VariantJanus:
5845       pieces = JanusArray;
5846       gameInfo.boardWidth = 10;
5847       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5848       nrCastlingRights = 6;
5849         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5850         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5851         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5852         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5853         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5854         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5855       break;
5856     case VariantFalcon:
5857       pieces = FalconArray;
5858       gameInfo.boardWidth = 10;
5859       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5860       break;
5861     case VariantXiangqi:
5862       pieces = XiangqiArray;
5863       gameInfo.boardWidth  = 9;
5864       gameInfo.boardHeight = 10;
5865       nrCastlingRights = 0;
5866       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5867       break;
5868     case VariantShogi:
5869       pieces = ShogiArray;
5870       gameInfo.boardWidth  = 9;
5871       gameInfo.boardHeight = 9;
5872       gameInfo.holdingsSize = 7;
5873       nrCastlingRights = 0;
5874       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5875       break;
5876     case VariantCourier:
5877       pieces = CourierArray;
5878       gameInfo.boardWidth  = 12;
5879       nrCastlingRights = 0;
5880       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5881       break;
5882     case VariantKnightmate:
5883       pieces = KnightmateArray;
5884       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5885       break;
5886     case VariantSpartan:
5887       pieces = SpartanArray;
5888       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5889       break;
5890     case VariantFairy:
5891       pieces = fairyArray;
5892       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5893       break;
5894     case VariantGreat:
5895       pieces = GreatArray;
5896       gameInfo.boardWidth = 10;
5897       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5898       gameInfo.holdingsSize = 8;
5899       break;
5900     case VariantSuper:
5901       pieces = FIDEArray;
5902       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5903       gameInfo.holdingsSize = 8;
5904       startedFromSetupPosition = TRUE;
5905       break;
5906     case VariantCrazyhouse:
5907     case VariantBughouse:
5908       pieces = FIDEArray;
5909       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5910       gameInfo.holdingsSize = 5;
5911       break;
5912     case VariantWildCastle:
5913       pieces = FIDEArray;
5914       /* !!?shuffle with kings guaranteed to be on d or e file */
5915       shuffleOpenings = 1;
5916       break;
5917     case VariantNoCastle:
5918       pieces = FIDEArray;
5919       nrCastlingRights = 0;
5920       /* !!?unconstrained back-rank shuffle */
5921       shuffleOpenings = 1;
5922       break;
5923     }
5924
5925     overrule = 0;
5926     if(appData.NrFiles >= 0) {
5927         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5928         gameInfo.boardWidth = appData.NrFiles;
5929     }
5930     if(appData.NrRanks >= 0) {
5931         gameInfo.boardHeight = appData.NrRanks;
5932     }
5933     if(appData.holdingsSize >= 0) {
5934         i = appData.holdingsSize;
5935         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5936         gameInfo.holdingsSize = i;
5937     }
5938     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5939     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5940         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5941
5942     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5943     if(pawnRow < 1) pawnRow = 1;
5944     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5945
5946     /* User pieceToChar list overrules defaults */
5947     if(appData.pieceToCharTable != NULL)
5948         SetCharTable(pieceToChar, appData.pieceToCharTable);
5949
5950     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5951
5952         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5953             s = (ChessSquare) 0; /* account holding counts in guard band */
5954         for( i=0; i<BOARD_HEIGHT; i++ )
5955             initialPosition[i][j] = s;
5956
5957         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5958         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5959         initialPosition[pawnRow][j] = WhitePawn;
5960         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5961         if(gameInfo.variant == VariantXiangqi) {
5962             if(j&1) {
5963                 initialPosition[pawnRow][j] =
5964                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5965                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5966                    initialPosition[2][j] = WhiteCannon;
5967                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5968                 }
5969             }
5970         }
5971         if(gameInfo.variant == VariantGrand) {
5972             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5973                initialPosition[0][j] = WhiteRook;
5974                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5975             }
5976         }
5977         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5978     }
5979     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5980
5981             j=BOARD_LEFT+1;
5982             initialPosition[1][j] = WhiteBishop;
5983             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5984             j=BOARD_RGHT-2;
5985             initialPosition[1][j] = WhiteRook;
5986             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5987     }
5988
5989     if( nrCastlingRights == -1) {
5990         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5991         /*       This sets default castling rights from none to normal corners   */
5992         /* Variants with other castling rights must set them themselves above    */
5993         nrCastlingRights = 6;
5994
5995         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5996         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5997         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5998         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5999         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6000         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6001      }
6002
6003      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6004      if(gameInfo.variant == VariantGreat) { // promotion commoners
6005         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6006         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6007         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6008         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6009      }
6010      if( gameInfo.variant == VariantSChess ) {
6011       initialPosition[1][0] = BlackMarshall;
6012       initialPosition[2][0] = BlackAngel;
6013       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6014       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6015       initialPosition[1][1] = initialPosition[2][1] = 
6016       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6017      }
6018   if (appData.debugMode) {
6019     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6020   }
6021     if(shuffleOpenings) {
6022         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6023         startedFromSetupPosition = TRUE;
6024     }
6025     if(startedFromPositionFile) {
6026       /* [HGM] loadPos: use PositionFile for every new game */
6027       CopyBoard(initialPosition, filePosition);
6028       for(i=0; i<nrCastlingRights; i++)
6029           initialRights[i] = filePosition[CASTLING][i];
6030       startedFromSetupPosition = TRUE;
6031     }
6032
6033     CopyBoard(boards[0], initialPosition);
6034
6035     if(oldx != gameInfo.boardWidth ||
6036        oldy != gameInfo.boardHeight ||
6037        oldv != gameInfo.variant ||
6038        oldh != gameInfo.holdingsWidth
6039                                          )
6040             InitDrawingSizes(-2 ,0);
6041
6042     oldv = gameInfo.variant;
6043     if (redraw)
6044       DrawPosition(TRUE, boards[currentMove]);
6045 }
6046
6047 void
6048 SendBoard (ChessProgramState *cps, int moveNum)
6049 {
6050     char message[MSG_SIZ];
6051
6052     if (cps->useSetboard) {
6053       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6054       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6055       SendToProgram(message, cps);
6056       free(fen);
6057
6058     } else {
6059       ChessSquare *bp;
6060       int i, j, left=0, right=BOARD_WIDTH;
6061       /* Kludge to set black to move, avoiding the troublesome and now
6062        * deprecated "black" command.
6063        */
6064       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6065         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6066
6067       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6068
6069       SendToProgram("edit\n", cps);
6070       SendToProgram("#\n", cps);
6071       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6072         bp = &boards[moveNum][i][left];
6073         for (j = left; j < right; j++, bp++) {
6074           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6075           if ((int) *bp < (int) BlackPawn) {
6076             if(j == BOARD_RGHT+1)
6077                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6078             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6079             if(message[0] == '+' || message[0] == '~') {
6080               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6081                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6082                         AAA + j, ONE + i);
6083             }
6084             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6085                 message[1] = BOARD_RGHT   - 1 - j + '1';
6086                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6087             }
6088             SendToProgram(message, cps);
6089           }
6090         }
6091       }
6092
6093       SendToProgram("c\n", cps);
6094       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6095         bp = &boards[moveNum][i][left];
6096         for (j = left; j < right; j++, bp++) {
6097           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6098           if (((int) *bp != (int) EmptySquare)
6099               && ((int) *bp >= (int) BlackPawn)) {
6100             if(j == BOARD_LEFT-2)
6101                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6102             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6103                     AAA + j, ONE + i);
6104             if(message[0] == '+' || message[0] == '~') {
6105               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6106                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6107                         AAA + j, ONE + i);
6108             }
6109             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6110                 message[1] = BOARD_RGHT   - 1 - j + '1';
6111                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6112             }
6113             SendToProgram(message, cps);
6114           }
6115         }
6116       }
6117
6118       SendToProgram(".\n", cps);
6119     }
6120     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6121 }
6122
6123 char exclusionHeader[MSG_SIZ];
6124 int exCnt, excludePtr;
6125 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6126 static Exclusion excluTab[200];
6127 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6128
6129 static void
6130 WriteMap (int s)
6131 {
6132     int j;
6133     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6134     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6135 }
6136
6137 static void
6138 ClearMap ()
6139 {
6140     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6141     excludePtr = 24; exCnt = 0;
6142     WriteMap(0);
6143 }
6144
6145 static void
6146 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6147 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6148     char buf[2*MOVE_LEN], *p;
6149     Exclusion *e = excluTab;
6150     int i;
6151     for(i=0; i<exCnt; i++)
6152         if(e[i].ff == fromX && e[i].fr == fromY &&
6153            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6154     if(i == exCnt) { // was not in exclude list; add it
6155         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6156         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6157             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6158             return; // abort
6159         }
6160         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6161         excludePtr++; e[i].mark = excludePtr++;
6162         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6163         exCnt++;
6164     }
6165     exclusionHeader[e[i].mark] = state;
6166 }
6167
6168 static int
6169 ExcludeOneMove (int fromY, int fromX, int toY, int toX, signed char promoChar, char state)
6170 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6171     char buf[MSG_SIZ];
6172     int j, k;
6173     ChessMove moveType;
6174     if(promoChar == -1) { // kludge to indicate best move
6175         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6176             return 1; // if unparsable, abort
6177     }
6178     // update exclusion map (resolving toggle by consulting existing state)
6179     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6180     j = k%8; k >>= 3;
6181     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6182     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6183          excludeMap[k] |=   1<<j;
6184     else excludeMap[k] &= ~(1<<j);
6185     // update header
6186     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6187     // inform engine
6188     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6189     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6190     SendToProgram(buf, &first);
6191     return (state == '+');
6192 }
6193
6194 static void
6195 ExcludeClick (int index)
6196 {
6197     int i, j;
6198     Exclusion *e = excluTab;
6199     if(index < 25) { // none, best or tail clicked
6200         if(index < 13) { // none: include all
6201             WriteMap(0); // clear map
6202             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6203             SendToProgram("include all\n", &first); // and inform engine
6204         } else if(index > 18) { // tail
6205             if(exclusionHeader[19] == '-') { // tail was excluded
6206                 SendToProgram("include all\n", &first);
6207                 WriteMap(0); // clear map completely
6208                 // now re-exclude selected moves
6209                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6210                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6211             } else { // tail was included or in mixed state
6212                 SendToProgram("exclude all\n", &first);
6213                 WriteMap(0xFF); // fill map completely
6214                 // now re-include selected moves
6215                 j = 0; // count them
6216                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6217                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6218                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6219             }
6220         } else { // best
6221             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6222         }
6223     } else {
6224         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6225             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6226             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6227             break;
6228         }
6229     }
6230 }
6231
6232 ChessSquare
6233 DefaultPromoChoice (int white)
6234 {
6235     ChessSquare result;
6236     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6237         result = WhiteFerz; // no choice
6238     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6239         result= WhiteKing; // in Suicide Q is the last thing we want
6240     else if(gameInfo.variant == VariantSpartan)
6241         result = white ? WhiteQueen : WhiteAngel;
6242     else result = WhiteQueen;
6243     if(!white) result = WHITE_TO_BLACK result;
6244     return result;
6245 }
6246
6247 static int autoQueen; // [HGM] oneclick
6248
6249 int
6250 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6251 {
6252     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6253     /* [HGM] add Shogi promotions */
6254     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6255     ChessSquare piece;
6256     ChessMove moveType;
6257     Boolean premove;
6258
6259     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6260     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6261
6262     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6263       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6264         return FALSE;
6265
6266     piece = boards[currentMove][fromY][fromX];
6267     if(gameInfo.variant == VariantShogi) {
6268         promotionZoneSize = BOARD_HEIGHT/3;
6269         highestPromotingPiece = (int)WhiteFerz;
6270     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6271         promotionZoneSize = 3;
6272     }
6273
6274     // Treat Lance as Pawn when it is not representing Amazon
6275     if(gameInfo.variant != VariantSuper) {
6276         if(piece == WhiteLance) piece = WhitePawn; else
6277         if(piece == BlackLance) piece = BlackPawn;
6278     }
6279
6280     // next weed out all moves that do not touch the promotion zone at all
6281     if((int)piece >= BlackPawn) {
6282         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6283              return FALSE;
6284         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6285     } else {
6286         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6287            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6288     }
6289
6290     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6291
6292     // weed out mandatory Shogi promotions
6293     if(gameInfo.variant == VariantShogi) {
6294         if(piece >= BlackPawn) {
6295             if(toY == 0 && piece == BlackPawn ||
6296                toY == 0 && piece == BlackQueen ||
6297                toY <= 1 && piece == BlackKnight) {
6298                 *promoChoice = '+';
6299                 return FALSE;
6300             }
6301         } else {
6302             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6303                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6304                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6305                 *promoChoice = '+';
6306                 return FALSE;
6307             }
6308         }
6309     }
6310
6311     // weed out obviously illegal Pawn moves
6312     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6313         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6314         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6315         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6316         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6317         // note we are not allowed to test for valid (non-)capture, due to premove
6318     }
6319
6320     // we either have a choice what to promote to, or (in Shogi) whether to promote
6321     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6322         *promoChoice = PieceToChar(BlackFerz);  // no choice
6323         return FALSE;
6324     }
6325     // no sense asking what we must promote to if it is going to explode...
6326     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6327         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6328         return FALSE;
6329     }
6330     // give caller the default choice even if we will not make it
6331     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6332     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6333     if(        sweepSelect && gameInfo.variant != VariantGreat
6334                            && gameInfo.variant != VariantGrand
6335                            && gameInfo.variant != VariantSuper) return FALSE;
6336     if(autoQueen) return FALSE; // predetermined
6337
6338     // suppress promotion popup on illegal moves that are not premoves
6339     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6340               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6341     if(appData.testLegality && !premove) {
6342         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6343                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6344         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6345             return FALSE;
6346     }
6347
6348     return TRUE;
6349 }
6350
6351 int
6352 InPalace (int row, int column)
6353 {   /* [HGM] for Xiangqi */
6354     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6355          column < (BOARD_WIDTH + 4)/2 &&
6356          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6357     return FALSE;
6358 }
6359
6360 int
6361 PieceForSquare (int x, int y)
6362 {
6363   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6364      return -1;
6365   else
6366      return boards[currentMove][y][x];
6367 }
6368
6369 int
6370 OKToStartUserMove (int x, int y)
6371 {
6372     ChessSquare from_piece;
6373     int white_piece;
6374
6375     if (matchMode) return FALSE;
6376     if (gameMode == EditPosition) return TRUE;
6377
6378     if (x >= 0 && y >= 0)
6379       from_piece = boards[currentMove][y][x];
6380     else
6381       from_piece = EmptySquare;
6382
6383     if (from_piece == EmptySquare) return FALSE;
6384
6385     white_piece = (int)from_piece >= (int)WhitePawn &&
6386       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6387
6388     switch (gameMode) {
6389       case AnalyzeFile:
6390       case TwoMachinesPlay:
6391       case EndOfGame:
6392         return FALSE;
6393
6394       case IcsObserving:
6395       case IcsIdle:
6396         return FALSE;
6397
6398       case MachinePlaysWhite:
6399       case IcsPlayingBlack:
6400         if (appData.zippyPlay) return FALSE;
6401         if (white_piece) {
6402             DisplayMoveError(_("You are playing Black"));
6403             return FALSE;
6404         }
6405         break;
6406
6407       case MachinePlaysBlack:
6408       case IcsPlayingWhite:
6409         if (appData.zippyPlay) return FALSE;
6410         if (!white_piece) {
6411             DisplayMoveError(_("You are playing White"));
6412             return FALSE;
6413         }
6414         break;
6415
6416       case PlayFromGameFile:
6417             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6418       case EditGame:
6419         if (!white_piece && WhiteOnMove(currentMove)) {
6420             DisplayMoveError(_("It is White's turn"));
6421             return FALSE;
6422         }
6423         if (white_piece && !WhiteOnMove(currentMove)) {
6424             DisplayMoveError(_("It is Black's turn"));
6425             return FALSE;
6426         }
6427         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6428             /* Editing correspondence game history */
6429             /* Could disallow this or prompt for confirmation */
6430             cmailOldMove = -1;
6431         }
6432         break;
6433
6434       case BeginningOfGame:
6435         if (appData.icsActive) return FALSE;
6436         if (!appData.noChessProgram) {
6437             if (!white_piece) {
6438                 DisplayMoveError(_("You are playing White"));
6439                 return FALSE;
6440             }
6441         }
6442         break;
6443
6444       case Training:
6445         if (!white_piece && WhiteOnMove(currentMove)) {
6446             DisplayMoveError(_("It is White's turn"));
6447             return FALSE;
6448         }
6449         if (white_piece && !WhiteOnMove(currentMove)) {
6450             DisplayMoveError(_("It is Black's turn"));
6451             return FALSE;
6452         }
6453         break;
6454
6455       default:
6456       case IcsExamining:
6457         break;
6458     }
6459     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6460         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6461         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6462         && gameMode != AnalyzeFile && gameMode != Training) {
6463         DisplayMoveError(_("Displayed position is not current"));
6464         return FALSE;
6465     }
6466     return TRUE;
6467 }
6468
6469 Boolean
6470 OnlyMove (int *x, int *y, Boolean captures) 
6471 {
6472     DisambiguateClosure cl;
6473     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6474     switch(gameMode) {
6475       case MachinePlaysBlack:
6476       case IcsPlayingWhite:
6477       case BeginningOfGame:
6478         if(!WhiteOnMove(currentMove)) return FALSE;
6479         break;
6480       case MachinePlaysWhite:
6481       case IcsPlayingBlack:
6482         if(WhiteOnMove(currentMove)) return FALSE;
6483         break;
6484       case EditGame:
6485         break;
6486       default:
6487         return FALSE;
6488     }
6489     cl.pieceIn = EmptySquare;
6490     cl.rfIn = *y;
6491     cl.ffIn = *x;
6492     cl.rtIn = -1;
6493     cl.ftIn = -1;
6494     cl.promoCharIn = NULLCHAR;
6495     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6496     if( cl.kind == NormalMove ||
6497         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6498         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6499         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6500       fromX = cl.ff;
6501       fromY = cl.rf;
6502       *x = cl.ft;
6503       *y = cl.rt;
6504       return TRUE;
6505     }
6506     if(cl.kind != ImpossibleMove) return FALSE;
6507     cl.pieceIn = EmptySquare;
6508     cl.rfIn = -1;
6509     cl.ffIn = -1;
6510     cl.rtIn = *y;
6511     cl.ftIn = *x;
6512     cl.promoCharIn = NULLCHAR;
6513     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6514     if( cl.kind == NormalMove ||
6515         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6516         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6517         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6518       fromX = cl.ff;
6519       fromY = cl.rf;
6520       *x = cl.ft;
6521       *y = cl.rt;
6522       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6523       return TRUE;
6524     }
6525     return FALSE;
6526 }
6527
6528 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6529 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6530 int lastLoadGameUseList = FALSE;
6531 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6532 ChessMove lastLoadGameStart = EndOfFile;
6533 int doubleClick;
6534
6535 void
6536 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6537 {
6538     ChessMove moveType;
6539     ChessSquare pdown, pup;
6540     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6541
6542
6543     /* Check if the user is playing in turn.  This is complicated because we
6544        let the user "pick up" a piece before it is his turn.  So the piece he
6545        tried to pick up may have been captured by the time he puts it down!
6546        Therefore we use the color the user is supposed to be playing in this
6547        test, not the color of the piece that is currently on the starting
6548        square---except in EditGame mode, where the user is playing both
6549        sides; fortunately there the capture race can't happen.  (It can
6550        now happen in IcsExamining mode, but that's just too bad.  The user
6551        will get a somewhat confusing message in that case.)
6552        */
6553
6554     switch (gameMode) {
6555       case AnalyzeFile:
6556       case TwoMachinesPlay:
6557       case EndOfGame:
6558       case IcsObserving:
6559       case IcsIdle:
6560         /* We switched into a game mode where moves are not accepted,
6561            perhaps while the mouse button was down. */
6562         return;
6563
6564       case MachinePlaysWhite:
6565         /* User is moving for Black */
6566         if (WhiteOnMove(currentMove)) {
6567             DisplayMoveError(_("It is White's turn"));
6568             return;
6569         }
6570         break;
6571
6572       case MachinePlaysBlack:
6573         /* User is moving for White */
6574         if (!WhiteOnMove(currentMove)) {
6575             DisplayMoveError(_("It is Black's turn"));
6576             return;
6577         }
6578         break;
6579
6580       case PlayFromGameFile:
6581             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6582       case EditGame:
6583       case IcsExamining:
6584       case BeginningOfGame:
6585       case AnalyzeMode:
6586       case Training:
6587         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6588         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6589             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6590             /* User is moving for Black */
6591             if (WhiteOnMove(currentMove)) {
6592                 DisplayMoveError(_("It is White's turn"));
6593                 return;
6594             }
6595         } else {
6596             /* User is moving for White */
6597             if (!WhiteOnMove(currentMove)) {
6598                 DisplayMoveError(_("It is Black's turn"));
6599                 return;
6600             }
6601         }
6602         break;
6603
6604       case IcsPlayingBlack:
6605         /* User is moving for Black */
6606         if (WhiteOnMove(currentMove)) {
6607             if (!appData.premove) {
6608                 DisplayMoveError(_("It is White's turn"));
6609             } else if (toX >= 0 && toY >= 0) {
6610                 premoveToX = toX;
6611                 premoveToY = toY;
6612                 premoveFromX = fromX;
6613                 premoveFromY = fromY;
6614                 premovePromoChar = promoChar;
6615                 gotPremove = 1;
6616                 if (appData.debugMode)
6617                     fprintf(debugFP, "Got premove: fromX %d,"
6618                             "fromY %d, toX %d, toY %d\n",
6619                             fromX, fromY, toX, toY);
6620             }
6621             return;
6622         }
6623         break;
6624
6625       case IcsPlayingWhite:
6626         /* User is moving for White */
6627         if (!WhiteOnMove(currentMove)) {
6628             if (!appData.premove) {
6629                 DisplayMoveError(_("It is Black's turn"));
6630             } else if (toX >= 0 && toY >= 0) {
6631                 premoveToX = toX;
6632                 premoveToY = toY;
6633                 premoveFromX = fromX;
6634                 premoveFromY = fromY;
6635                 premovePromoChar = promoChar;
6636                 gotPremove = 1;
6637                 if (appData.debugMode)
6638                     fprintf(debugFP, "Got premove: fromX %d,"
6639                             "fromY %d, toX %d, toY %d\n",
6640                             fromX, fromY, toX, toY);
6641             }
6642             return;
6643         }
6644         break;
6645
6646       default:
6647         break;
6648
6649       case EditPosition:
6650         /* EditPosition, empty square, or different color piece;
6651            click-click move is possible */
6652         if (toX == -2 || toY == -2) {
6653             boards[0][fromY][fromX] = EmptySquare;
6654             DrawPosition(FALSE, boards[currentMove]);
6655             return;
6656         } else if (toX >= 0 && toY >= 0) {
6657             boards[0][toY][toX] = boards[0][fromY][fromX];
6658             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6659                 if(boards[0][fromY][0] != EmptySquare) {
6660                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6661                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6662                 }
6663             } else
6664             if(fromX == BOARD_RGHT+1) {
6665                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6666                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6667                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6668                 }
6669             } else
6670             boards[0][fromY][fromX] = gatingPiece;
6671             DrawPosition(FALSE, boards[currentMove]);
6672             return;
6673         }
6674         return;
6675     }
6676
6677     if(toX < 0 || toY < 0) return;
6678     pdown = boards[currentMove][fromY][fromX];
6679     pup = boards[currentMove][toY][toX];
6680
6681     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6682     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6683          if( pup != EmptySquare ) return;
6684          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6685            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6686                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6687            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6688            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6689            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6690            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6691          fromY = DROP_RANK;
6692     }
6693
6694     /* [HGM] always test for legality, to get promotion info */
6695     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6696                                          fromY, fromX, toY, toX, promoChar);
6697
6698     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6699
6700     /* [HGM] but possibly ignore an IllegalMove result */
6701     if (appData.testLegality) {
6702         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6703             DisplayMoveError(_("Illegal move"));
6704             return;
6705         }
6706     }
6707
6708     if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6709         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6710              ClearPremoveHighlights(); // was included
6711         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6712         return;
6713     }
6714
6715     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6716 }
6717
6718 /* Common tail of UserMoveEvent and DropMenuEvent */
6719 int
6720 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6721 {
6722     char *bookHit = 0;
6723
6724     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6725         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6726         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6727         if(WhiteOnMove(currentMove)) {
6728             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6729         } else {
6730             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6731         }
6732     }
6733
6734     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6735        move type in caller when we know the move is a legal promotion */
6736     if(moveType == NormalMove && promoChar)
6737         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6738
6739     /* [HGM] <popupFix> The following if has been moved here from
6740        UserMoveEvent(). Because it seemed to belong here (why not allow
6741        piece drops in training games?), and because it can only be
6742        performed after it is known to what we promote. */
6743     if (gameMode == Training) {
6744       /* compare the move played on the board to the next move in the
6745        * game. If they match, display the move and the opponent's response.
6746        * If they don't match, display an error message.
6747        */
6748       int saveAnimate;
6749       Board testBoard;
6750       CopyBoard(testBoard, boards[currentMove]);
6751       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6752
6753       if (CompareBoards(testBoard, boards[currentMove+1])) {
6754         ForwardInner(currentMove+1);
6755
6756         /* Autoplay the opponent's response.
6757          * if appData.animate was TRUE when Training mode was entered,
6758          * the response will be animated.
6759          */
6760         saveAnimate = appData.animate;
6761         appData.animate = animateTraining;
6762         ForwardInner(currentMove+1);
6763         appData.animate = saveAnimate;
6764
6765         /* check for the end of the game */
6766         if (currentMove >= forwardMostMove) {
6767           gameMode = PlayFromGameFile;
6768           ModeHighlight();
6769           SetTrainingModeOff();
6770           DisplayInformation(_("End of game"));
6771         }
6772       } else {
6773         DisplayError(_("Incorrect move"), 0);
6774       }
6775       return 1;
6776     }
6777
6778   /* Ok, now we know that the move is good, so we can kill
6779      the previous line in Analysis Mode */
6780   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6781                                 && currentMove < forwardMostMove) {
6782     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6783     else forwardMostMove = currentMove;
6784   }
6785
6786   ClearMap();
6787
6788   /* If we need the chess program but it's dead, restart it */
6789   ResurrectChessProgram();
6790
6791   /* A user move restarts a paused game*/
6792   if (pausing)
6793     PauseEvent();
6794
6795   thinkOutput[0] = NULLCHAR;
6796
6797   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6798
6799   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6800     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6801     return 1;
6802   }
6803
6804   if (gameMode == BeginningOfGame) {
6805     if (appData.noChessProgram) {
6806       gameMode = EditGame;
6807       SetGameInfo();
6808     } else {
6809       char buf[MSG_SIZ];
6810       gameMode = MachinePlaysBlack;
6811       StartClocks();
6812       SetGameInfo();
6813       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6814       DisplayTitle(buf);
6815       if (first.sendName) {
6816         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6817         SendToProgram(buf, &first);
6818       }
6819       StartClocks();
6820     }
6821     ModeHighlight();
6822   }
6823
6824   /* Relay move to ICS or chess engine */
6825   if (appData.icsActive) {
6826     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6827         gameMode == IcsExamining) {
6828       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6829         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6830         SendToICS("draw ");
6831         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6832       }
6833       // also send plain move, in case ICS does not understand atomic claims
6834       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6835       ics_user_moved = 1;
6836     }
6837   } else {
6838     if (first.sendTime && (gameMode == BeginningOfGame ||
6839                            gameMode == MachinePlaysWhite ||
6840                            gameMode == MachinePlaysBlack)) {
6841       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6842     }
6843     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6844          // [HGM] book: if program might be playing, let it use book
6845         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6846         first.maybeThinking = TRUE;
6847     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6848         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6849         SendBoard(&first, currentMove+1);
6850     } else SendMoveToProgram(forwardMostMove-1, &first);
6851     if (currentMove == cmailOldMove + 1) {
6852       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6853     }
6854   }
6855
6856   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6857
6858   switch (gameMode) {
6859   case EditGame:
6860     if(appData.testLegality)
6861     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6862     case MT_NONE:
6863     case MT_CHECK:
6864       break;
6865     case MT_CHECKMATE:
6866     case MT_STAINMATE:
6867       if (WhiteOnMove(currentMove)) {
6868         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6869       } else {
6870         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6871       }
6872       break;
6873     case MT_STALEMATE:
6874       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6875       break;
6876     }
6877     break;
6878
6879   case MachinePlaysBlack:
6880   case MachinePlaysWhite:
6881     /* disable certain menu options while machine is thinking */
6882     SetMachineThinkingEnables();
6883     break;
6884
6885   default:
6886     break;
6887   }
6888
6889   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6890   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6891
6892   if(bookHit) { // [HGM] book: simulate book reply
6893         static char bookMove[MSG_SIZ]; // a bit generous?
6894
6895         programStats.nodes = programStats.depth = programStats.time =
6896         programStats.score = programStats.got_only_move = 0;
6897         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6898
6899         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6900         strcat(bookMove, bookHit);
6901         HandleMachineMove(bookMove, &first);
6902   }
6903   return 1;
6904 }
6905
6906 void
6907 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6908 {
6909     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6910     Markers *m = (Markers *) closure;
6911     if(rf == fromY && ff == fromX)
6912         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6913                          || kind == WhiteCapturesEnPassant
6914                          || kind == BlackCapturesEnPassant);
6915     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6916 }
6917
6918 void
6919 MarkTargetSquares (int clear)
6920 {
6921   int x, y;
6922   if(clear) // no reason to ever suppress clearing
6923     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6924   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6925      !appData.testLegality || gameMode == EditPosition) return;
6926   if(!clear) {
6927     int capt = 0;
6928     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6929     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6930       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6931       if(capt)
6932       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6933     }
6934   }
6935   DrawPosition(TRUE, NULL);
6936 }
6937
6938 int
6939 Explode (Board board, int fromX, int fromY, int toX, int toY)
6940 {
6941     if(gameInfo.variant == VariantAtomic &&
6942        (board[toY][toX] != EmptySquare ||                     // capture?
6943         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6944                          board[fromY][fromX] == BlackPawn   )
6945       )) {
6946         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6947         return TRUE;
6948     }
6949     return FALSE;
6950 }
6951
6952 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6953
6954 int
6955 CanPromote (ChessSquare piece, int y)
6956 {
6957         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6958         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6959         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6960            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6961            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6962                                                   gameInfo.variant == VariantMakruk) return FALSE;
6963         return (piece == BlackPawn && y == 1 ||
6964                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6965                 piece == BlackLance && y == 1 ||
6966                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6967 }
6968
6969 void
6970 LeftClick (ClickType clickType, int xPix, int yPix)
6971 {
6972     int x, y;
6973     Boolean saveAnimate;
6974     static int second = 0, promotionChoice = 0, clearFlag = 0;
6975     char promoChoice = NULLCHAR;
6976     ChessSquare piece;
6977     static TimeMark lastClickTime, prevClickTime;
6978
6979     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
6980
6981     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
6982
6983     if (clickType == Press) ErrorPopDown();
6984
6985     x = EventToSquare(xPix, BOARD_WIDTH);
6986     y = EventToSquare(yPix, BOARD_HEIGHT);
6987     if (!flipView && y >= 0) {
6988         y = BOARD_HEIGHT - 1 - y;
6989     }
6990     if (flipView && x >= 0) {
6991         x = BOARD_WIDTH - 1 - x;
6992     }
6993
6994     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6995         defaultPromoChoice = promoSweep;
6996         promoSweep = EmptySquare;   // terminate sweep
6997         promoDefaultAltered = TRUE;
6998         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6999     }
7000
7001     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7002         if(clickType == Release) return; // ignore upclick of click-click destination
7003         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7004         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7005         if(gameInfo.holdingsWidth &&
7006                 (WhiteOnMove(currentMove)
7007                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7008                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7009             // click in right holdings, for determining promotion piece
7010             ChessSquare p = boards[currentMove][y][x];
7011             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7012             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7013             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7014                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7015                 fromX = fromY = -1;
7016                 return;
7017             }
7018         }
7019         DrawPosition(FALSE, boards[currentMove]);
7020         return;
7021     }
7022
7023     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7024     if(clickType == Press
7025             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7026               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7027               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7028         return;
7029
7030     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7031         // could be static click on premove from-square: abort premove
7032         gotPremove = 0;
7033         ClearPremoveHighlights();
7034     }
7035
7036     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7037         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7038
7039     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7040         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7041                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7042         defaultPromoChoice = DefaultPromoChoice(side);
7043     }
7044
7045     autoQueen = appData.alwaysPromoteToQueen;
7046
7047     if (fromX == -1) {
7048       int originalY = y;
7049       gatingPiece = EmptySquare;
7050       if (clickType != Press) {
7051         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7052             DragPieceEnd(xPix, yPix); dragging = 0;
7053             DrawPosition(FALSE, NULL);
7054         }
7055         return;
7056       }
7057       doubleClick = FALSE;
7058       fromX = x; fromY = y; toX = toY = -1;
7059       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7060          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7061          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7062             /* First square */
7063             if (OKToStartUserMove(fromX, fromY)) {
7064                 second = 0;
7065                 MarkTargetSquares(0);
7066                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7067                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7068                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7069                     promoSweep = defaultPromoChoice;
7070                     selectFlag = 0; lastX = xPix; lastY = yPix;
7071                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7072                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7073                 }
7074                 if (appData.highlightDragging) {
7075                     SetHighlights(fromX, fromY, -1, -1);
7076                 }
7077             } else fromX = fromY = -1;
7078             return;
7079         }
7080     }
7081
7082     /* fromX != -1 */
7083     if (clickType == Press && gameMode != EditPosition) {
7084         ChessSquare fromP;
7085         ChessSquare toP;
7086         int frc;
7087
7088         // ignore off-board to clicks
7089         if(y < 0 || x < 0) return;
7090
7091         /* Check if clicking again on the same color piece */
7092         fromP = boards[currentMove][fromY][fromX];
7093         toP = boards[currentMove][y][x];
7094         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7095         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7096              WhitePawn <= toP && toP <= WhiteKing &&
7097              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7098              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7099             (BlackPawn <= fromP && fromP <= BlackKing &&
7100              BlackPawn <= toP && toP <= BlackKing &&
7101              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7102              !(fromP == BlackKing && toP == BlackRook && frc))) {
7103             /* Clicked again on same color piece -- changed his mind */
7104             second = (x == fromX && y == fromY);
7105             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7106                 second = FALSE; // first double-click rather than scond click
7107                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7108             }
7109             promoDefaultAltered = FALSE;
7110             MarkTargetSquares(1);
7111            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7112             if (appData.highlightDragging) {
7113                 SetHighlights(x, y, -1, -1);
7114             } else {
7115                 ClearHighlights();
7116             }
7117             if (OKToStartUserMove(x, y)) {
7118                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7119                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7120                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7121                  gatingPiece = boards[currentMove][fromY][fromX];
7122                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7123                 fromX = x;
7124                 fromY = y; dragging = 1;
7125                 MarkTargetSquares(0);
7126                 DragPieceBegin(xPix, yPix, FALSE);
7127                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7128                     promoSweep = defaultPromoChoice;
7129                     selectFlag = 0; lastX = xPix; lastY = yPix;
7130                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7131                 }
7132             }
7133            }
7134            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7135            second = FALSE; 
7136         }
7137         // ignore clicks on holdings
7138         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7139     }
7140
7141     if (clickType == Release && x == fromX && y == fromY) {
7142         DragPieceEnd(xPix, yPix); dragging = 0;
7143         if(clearFlag) {
7144             // a deferred attempt to click-click move an empty square on top of a piece
7145             boards[currentMove][y][x] = EmptySquare;
7146             ClearHighlights();
7147             DrawPosition(FALSE, boards[currentMove]);
7148             fromX = fromY = -1; clearFlag = 0;
7149             return;
7150         }
7151         if (appData.animateDragging) {
7152             /* Undo animation damage if any */
7153             DrawPosition(FALSE, NULL);
7154         }
7155         if (second) {
7156             /* Second up/down in same square; just abort move */
7157             second = 0;
7158             fromX = fromY = -1;
7159             gatingPiece = EmptySquare;
7160             ClearHighlights();
7161             gotPremove = 0;
7162             ClearPremoveHighlights();
7163         } else {
7164             /* First upclick in same square; start click-click mode */
7165             SetHighlights(x, y, -1, -1);
7166         }
7167         return;
7168     }
7169
7170     clearFlag = 0;
7171
7172     /* we now have a different from- and (possibly off-board) to-square */
7173     /* Completed move */
7174     toX = x;
7175     toY = y;
7176     saveAnimate = appData.animate;
7177     MarkTargetSquares(1);
7178     if (clickType == Press) {
7179         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7180             // must be Edit Position mode with empty-square selected
7181             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7182             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7183             return;
7184         }
7185         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7186             ChessSquare piece = boards[currentMove][fromY][fromX];
7187             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7188             promoSweep = defaultPromoChoice;
7189             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7190             selectFlag = 0; lastX = xPix; lastY = yPix;
7191             Sweep(0); // Pawn that is going to promote: preview promotion piece
7192             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7193             DrawPosition(FALSE, boards[currentMove]);
7194             return;
7195         }
7196         /* Finish clickclick move */
7197         if (appData.animate || appData.highlightLastMove) {
7198             SetHighlights(fromX, fromY, toX, toY);
7199         } else {
7200             ClearHighlights();
7201         }
7202     } else {
7203         /* Finish drag move */
7204         if (appData.highlightLastMove) {
7205             SetHighlights(fromX, fromY, toX, toY);
7206         } else {
7207             ClearHighlights();
7208         }
7209         DragPieceEnd(xPix, yPix); dragging = 0;
7210         /* Don't animate move and drag both */
7211         appData.animate = FALSE;
7212     }
7213
7214     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7215     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7216         ChessSquare piece = boards[currentMove][fromY][fromX];
7217         if(gameMode == EditPosition && piece != EmptySquare &&
7218            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7219             int n;
7220
7221             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7222                 n = PieceToNumber(piece - (int)BlackPawn);
7223                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7224                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7225                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7226             } else
7227             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7228                 n = PieceToNumber(piece);
7229                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7230                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7231                 boards[currentMove][n][BOARD_WIDTH-2]++;
7232             }
7233             boards[currentMove][fromY][fromX] = EmptySquare;
7234         }
7235         ClearHighlights();
7236         fromX = fromY = -1;
7237         DrawPosition(TRUE, boards[currentMove]);
7238         return;
7239     }
7240
7241     // off-board moves should not be highlighted
7242     if(x < 0 || y < 0) ClearHighlights();
7243
7244     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7245
7246     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7247         SetHighlights(fromX, fromY, toX, toY);
7248         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7249             // [HGM] super: promotion to captured piece selected from holdings
7250             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7251             promotionChoice = TRUE;
7252             // kludge follows to temporarily execute move on display, without promoting yet
7253             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7254             boards[currentMove][toY][toX] = p;
7255             DrawPosition(FALSE, boards[currentMove]);
7256             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7257             boards[currentMove][toY][toX] = q;
7258             DisplayMessage("Click in holdings to choose piece", "");
7259             return;
7260         }
7261         PromotionPopUp();
7262     } else {
7263         int oldMove = currentMove;
7264         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7265         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7266         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7267         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7268            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7269             DrawPosition(TRUE, boards[currentMove]);
7270         fromX = fromY = -1;
7271     }
7272     appData.animate = saveAnimate;
7273     if (appData.animate || appData.animateDragging) {
7274         /* Undo animation damage if needed */
7275         DrawPosition(FALSE, NULL);
7276     }
7277 }
7278
7279 int
7280 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7281 {   // front-end-free part taken out of PieceMenuPopup
7282     int whichMenu; int xSqr, ySqr;
7283
7284     if(seekGraphUp) { // [HGM] seekgraph
7285         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7286         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7287         return -2;
7288     }
7289
7290     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7291          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7292         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7293         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7294         if(action == Press)   {
7295             originalFlip = flipView;
7296             flipView = !flipView; // temporarily flip board to see game from partners perspective
7297             DrawPosition(TRUE, partnerBoard);
7298             DisplayMessage(partnerStatus, "");
7299             partnerUp = TRUE;
7300         } else if(action == Release) {
7301             flipView = originalFlip;
7302             DrawPosition(TRUE, boards[currentMove]);
7303             partnerUp = FALSE;
7304         }
7305         return -2;
7306     }
7307
7308     xSqr = EventToSquare(x, BOARD_WIDTH);
7309     ySqr = EventToSquare(y, BOARD_HEIGHT);
7310     if (action == Release) {
7311         if(pieceSweep != EmptySquare) {
7312             EditPositionMenuEvent(pieceSweep, toX, toY);
7313             pieceSweep = EmptySquare;
7314         } else UnLoadPV(); // [HGM] pv
7315     }
7316     if (action != Press) return -2; // return code to be ignored
7317     switch (gameMode) {
7318       case IcsExamining:
7319         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7320       case EditPosition:
7321         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7322         if (xSqr < 0 || ySqr < 0) return -1;
7323         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7324         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7325         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7326         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7327         NextPiece(0);
7328         return 2; // grab
7329       case IcsObserving:
7330         if(!appData.icsEngineAnalyze) return -1;
7331       case IcsPlayingWhite:
7332       case IcsPlayingBlack:
7333         if(!appData.zippyPlay) goto noZip;
7334       case AnalyzeMode:
7335       case AnalyzeFile:
7336       case MachinePlaysWhite:
7337       case MachinePlaysBlack:
7338       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7339         if (!appData.dropMenu) {
7340           LoadPV(x, y);
7341           return 2; // flag front-end to grab mouse events
7342         }
7343         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7344            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7345       case EditGame:
7346       noZip:
7347         if (xSqr < 0 || ySqr < 0) return -1;
7348         if (!appData.dropMenu || appData.testLegality &&
7349             gameInfo.variant != VariantBughouse &&
7350             gameInfo.variant != VariantCrazyhouse) return -1;
7351         whichMenu = 1; // drop menu
7352         break;
7353       default:
7354         return -1;
7355     }
7356
7357     if (((*fromX = xSqr) < 0) ||
7358         ((*fromY = ySqr) < 0)) {
7359         *fromX = *fromY = -1;
7360         return -1;
7361     }
7362     if (flipView)
7363       *fromX = BOARD_WIDTH - 1 - *fromX;
7364     else
7365       *fromY = BOARD_HEIGHT - 1 - *fromY;
7366
7367     return whichMenu;
7368 }
7369
7370 void
7371 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7372 {
7373 //    char * hint = lastHint;
7374     FrontEndProgramStats stats;
7375
7376     stats.which = cps == &first ? 0 : 1;
7377     stats.depth = cpstats->depth;
7378     stats.nodes = cpstats->nodes;
7379     stats.score = cpstats->score;
7380     stats.time = cpstats->time;
7381     stats.pv = cpstats->movelist;
7382     stats.hint = lastHint;
7383     stats.an_move_index = 0;
7384     stats.an_move_count = 0;
7385
7386     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7387         stats.hint = cpstats->move_name;
7388         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7389         stats.an_move_count = cpstats->nr_moves;
7390     }
7391
7392     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
7393
7394     SetProgramStats( &stats );
7395 }
7396
7397 void
7398 ClearEngineOutputPane (int which)
7399 {
7400     static FrontEndProgramStats dummyStats;
7401     dummyStats.which = which;
7402     dummyStats.pv = "#";
7403     SetProgramStats( &dummyStats );
7404 }
7405
7406 #define MAXPLAYERS 500
7407
7408 char *
7409 TourneyStandings (int display)
7410 {
7411     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7412     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7413     char result, *p, *names[MAXPLAYERS];
7414
7415     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7416         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7417     names[0] = p = strdup(appData.participants);
7418     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7419
7420     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7421
7422     while(result = appData.results[nr]) {
7423         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7424         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7425         wScore = bScore = 0;
7426         switch(result) {
7427           case '+': wScore = 2; break;
7428           case '-': bScore = 2; break;
7429           case '=': wScore = bScore = 1; break;
7430           case ' ':
7431           case '*': return strdup("busy"); // tourney not finished
7432         }
7433         score[w] += wScore;
7434         score[b] += bScore;
7435         games[w]++;
7436         games[b]++;
7437         nr++;
7438     }
7439     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7440     for(w=0; w<nPlayers; w++) {
7441         bScore = -1;
7442         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7443         ranking[w] = b; points[w] = bScore; score[b] = -2;
7444     }
7445     p = malloc(nPlayers*34+1);
7446     for(w=0; w<nPlayers && w<display; w++)
7447         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7448     free(names[0]);
7449     return p;
7450 }
7451
7452 void
7453 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7454 {       // count all piece types
7455         int p, f, r;
7456         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7457         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7458         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7459                 p = board[r][f];
7460                 pCnt[p]++;
7461                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7462                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7463                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7464                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7465                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7466                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7467         }
7468 }
7469
7470 int
7471 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7472 {
7473         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7474         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7475
7476         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7477         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7478         if(myPawns == 2 && nMine == 3) // KPP
7479             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7480         if(myPawns == 1 && nMine == 2) // KP
7481             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7482         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7483             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7484         if(myPawns) return FALSE;
7485         if(pCnt[WhiteRook+side])
7486             return pCnt[BlackRook-side] ||
7487                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7488                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7489                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7490         if(pCnt[WhiteCannon+side]) {
7491             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7492             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7493         }
7494         if(pCnt[WhiteKnight+side])
7495             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7496         return FALSE;
7497 }
7498
7499 int
7500 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7501 {
7502         VariantClass v = gameInfo.variant;
7503
7504         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7505         if(v == VariantShatranj) return TRUE; // always winnable through baring
7506         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7507         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7508
7509         if(v == VariantXiangqi) {
7510                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7511
7512                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7513                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7514                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7515                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7516                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7517                 if(stale) // we have at least one last-rank P plus perhaps C
7518                     return majors // KPKX
7519                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7520                 else // KCA*E*
7521                     return pCnt[WhiteFerz+side] // KCAK
7522                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7523                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7524                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7525
7526         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7527                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7528
7529                 if(nMine == 1) return FALSE; // bare King
7530                 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
7531                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7532                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7533                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7534                 if(pCnt[WhiteKnight+side])
7535                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7536                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7537                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7538                 if(nBishops)
7539                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7540                 if(pCnt[WhiteAlfil+side])
7541                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7542                 if(pCnt[WhiteWazir+side])
7543                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7544         }
7545
7546         return TRUE;
7547 }
7548
7549 int
7550 CompareWithRights (Board b1, Board b2)
7551 {
7552     int rights = 0;
7553     if(!CompareBoards(b1, b2)) return FALSE;
7554     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7555     /* compare castling rights */
7556     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7557            rights++; /* King lost rights, while rook still had them */
7558     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7559         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7560            rights++; /* but at least one rook lost them */
7561     }
7562     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7563            rights++;
7564     if( b1[CASTLING][5] != NoRights ) {
7565         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7566            rights++;
7567     }
7568     return rights == 0;
7569 }
7570
7571 int
7572 Adjudicate (ChessProgramState *cps)
7573 {       // [HGM] some adjudications useful with buggy engines
7574         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7575         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7576         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7577         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7578         int k, count = 0; static int bare = 1;
7579         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7580         Boolean canAdjudicate = !appData.icsActive;
7581
7582         // most tests only when we understand the game, i.e. legality-checking on
7583             if( appData.testLegality )
7584             {   /* [HGM] Some more adjudications for obstinate engines */
7585                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7586                 static int moveCount = 6;
7587                 ChessMove result;
7588                 char *reason = NULL;
7589
7590                 /* Count what is on board. */
7591                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7592
7593                 /* Some material-based adjudications that have to be made before stalemate test */
7594                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7595                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7596                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7597                      if(canAdjudicate && appData.checkMates) {
7598                          if(engineOpponent)
7599                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7600                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7601                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7602                          return 1;
7603                      }
7604                 }
7605
7606                 /* Bare King in Shatranj (loses) or Losers (wins) */
7607                 if( nrW == 1 || nrB == 1) {
7608                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7609                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7610                      if(canAdjudicate && appData.checkMates) {
7611                          if(engineOpponent)
7612                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7613                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7614                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7615                          return 1;
7616                      }
7617                   } else
7618                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7619                   {    /* bare King */
7620                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7621                         if(canAdjudicate && appData.checkMates) {
7622                             /* but only adjudicate if adjudication enabled */
7623                             if(engineOpponent)
7624                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7625                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7626                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7627                             return 1;
7628                         }
7629                   }
7630                 } else bare = 1;
7631
7632
7633             // don't wait for engine to announce game end if we can judge ourselves
7634             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7635               case MT_CHECK:
7636                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7637                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7638                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7639                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7640                             checkCnt++;
7641                         if(checkCnt >= 2) {
7642                             reason = "Xboard adjudication: 3rd check";
7643                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7644                             break;
7645                         }
7646                     }
7647                 }
7648               case MT_NONE:
7649               default:
7650                 break;
7651               case MT_STALEMATE:
7652               case MT_STAINMATE:
7653                 reason = "Xboard adjudication: Stalemate";
7654                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7655                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7656                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7657                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7658                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7659                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7660                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7661                                                                         EP_CHECKMATE : EP_WINS);
7662                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7663                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7664                 }
7665                 break;
7666               case MT_CHECKMATE:
7667                 reason = "Xboard adjudication: Checkmate";
7668                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7669                 break;
7670             }
7671
7672                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7673                     case EP_STALEMATE:
7674                         result = GameIsDrawn; break;
7675                     case EP_CHECKMATE:
7676                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7677                     case EP_WINS:
7678                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7679                     default:
7680                         result = EndOfFile;
7681                 }
7682                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7683                     if(engineOpponent)
7684                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7685                     GameEnds( result, reason, GE_XBOARD );
7686                     return 1;
7687                 }
7688
7689                 /* Next absolutely insufficient mating material. */
7690                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7691                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7692                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7693
7694                      /* always flag draws, for judging claims */
7695                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7696
7697                      if(canAdjudicate && appData.materialDraws) {
7698                          /* but only adjudicate them if adjudication enabled */
7699                          if(engineOpponent) {
7700                            SendToProgram("force\n", engineOpponent); // suppress reply
7701                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7702                          }
7703                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7704                          return 1;
7705                      }
7706                 }
7707
7708                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7709                 if(gameInfo.variant == VariantXiangqi ?
7710                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7711                  : nrW + nrB == 4 &&
7712                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7713                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7714                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7715                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7716                    ) ) {
7717                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7718                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7719                           if(engineOpponent) {
7720                             SendToProgram("force\n", engineOpponent); // suppress reply
7721                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7722                           }
7723                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7724                           return 1;
7725                      }
7726                 } else moveCount = 6;
7727             }
7728
7729         // Repetition draws and 50-move rule can be applied independently of legality testing
7730
7731                 /* Check for rep-draws */
7732                 count = 0;
7733                 for(k = forwardMostMove-2;
7734                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7735                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7736                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7737                     k-=2)
7738                 {   int rights=0;
7739                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7740                         /* compare castling rights */
7741                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7742                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7743                                 rights++; /* King lost rights, while rook still had them */
7744                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7745                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7746                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7747                                    rights++; /* but at least one rook lost them */
7748                         }
7749                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7750                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7751                                 rights++;
7752                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7753                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7754                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7755                                    rights++;
7756                         }
7757                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7758                             && appData.drawRepeats > 1) {
7759                              /* adjudicate after user-specified nr of repeats */
7760                              int result = GameIsDrawn;
7761                              char *details = "XBoard adjudication: repetition draw";
7762                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7763                                 // [HGM] xiangqi: check for forbidden perpetuals
7764                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7765                                 for(m=forwardMostMove; m>k; m-=2) {
7766                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7767                                         ourPerpetual = 0; // the current mover did not always check
7768                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7769                                         hisPerpetual = 0; // the opponent did not always check
7770                                 }
7771                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7772                                                                         ourPerpetual, hisPerpetual);
7773                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7774                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7775                                     details = "Xboard adjudication: perpetual checking";
7776                                 } else
7777                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7778                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7779                                 } else
7780                                 // Now check for perpetual chases
7781                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7782                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7783                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7784                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7785                                         static char resdet[MSG_SIZ];
7786                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7787                                         details = resdet;
7788                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7789                                     } else
7790                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7791                                         break; // Abort repetition-checking loop.
7792                                 }
7793                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7794                              }
7795                              if(engineOpponent) {
7796                                SendToProgram("force\n", engineOpponent); // suppress reply
7797                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7798                              }
7799                              GameEnds( result, details, GE_XBOARD );
7800                              return 1;
7801                         }
7802                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7803                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7804                     }
7805                 }
7806
7807                 /* Now we test for 50-move draws. Determine ply count */
7808                 count = forwardMostMove;
7809                 /* look for last irreversble move */
7810                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7811                     count--;
7812                 /* if we hit starting position, add initial plies */
7813                 if( count == backwardMostMove )
7814                     count -= initialRulePlies;
7815                 count = forwardMostMove - count;
7816                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7817                         // adjust reversible move counter for checks in Xiangqi
7818                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7819                         if(i < backwardMostMove) i = backwardMostMove;
7820                         while(i <= forwardMostMove) {
7821                                 lastCheck = inCheck; // check evasion does not count
7822                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7823                                 if(inCheck || lastCheck) count--; // check does not count
7824                                 i++;
7825                         }
7826                 }
7827                 if( count >= 100)
7828                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7829                          /* this is used to judge if draw claims are legal */
7830                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7831                          if(engineOpponent) {
7832                            SendToProgram("force\n", engineOpponent); // suppress reply
7833                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7834                          }
7835                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7836                          return 1;
7837                 }
7838
7839                 /* if draw offer is pending, treat it as a draw claim
7840                  * when draw condition present, to allow engines a way to
7841                  * claim draws before making their move to avoid a race
7842                  * condition occurring after their move
7843                  */
7844                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7845                          char *p = NULL;
7846                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7847                              p = "Draw claim: 50-move rule";
7848                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7849                              p = "Draw claim: 3-fold repetition";
7850                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7851                              p = "Draw claim: insufficient mating material";
7852                          if( p != NULL && canAdjudicate) {
7853                              if(engineOpponent) {
7854                                SendToProgram("force\n", engineOpponent); // suppress reply
7855                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7856                              }
7857                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7858                              return 1;
7859                          }
7860                 }
7861
7862                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7863                     if(engineOpponent) {
7864                       SendToProgram("force\n", engineOpponent); // suppress reply
7865                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7866                     }
7867                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7868                     return 1;
7869                 }
7870         return 0;
7871 }
7872
7873 char *
7874 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7875 {   // [HGM] book: this routine intercepts moves to simulate book replies
7876     char *bookHit = NULL;
7877
7878     //first determine if the incoming move brings opponent into his book
7879     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7880         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7881     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7882     if(bookHit != NULL && !cps->bookSuspend) {
7883         // make sure opponent is not going to reply after receiving move to book position
7884         SendToProgram("force\n", cps);
7885         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7886     }
7887     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7888     // now arrange restart after book miss
7889     if(bookHit) {
7890         // after a book hit we never send 'go', and the code after the call to this routine
7891         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7892         char buf[MSG_SIZ], *move = bookHit;
7893         if(cps->useSAN) {
7894             int fromX, fromY, toX, toY;
7895             char promoChar;
7896             ChessMove moveType;
7897             move = buf + 30;
7898             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7899                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7900                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7901                                     PosFlags(forwardMostMove),
7902                                     fromY, fromX, toY, toX, promoChar, move);
7903             } else {
7904                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7905                 bookHit = NULL;
7906             }
7907         }
7908         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7909         SendToProgram(buf, cps);
7910         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7911     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7912         SendToProgram("go\n", cps);
7913         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7914     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7915         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7916             SendToProgram("go\n", cps);
7917         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7918     }
7919     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7920 }
7921
7922 int
7923 LoadError (char *errmess, ChessProgramState *cps)
7924 {   // unloads engine and switches back to -ncp mode if it was first
7925     if(cps->initDone) return FALSE;
7926     cps->isr = NULL; // this should suppress further error popups from breaking pipes
7927     DestroyChildProcess(cps->pr, 9 ); // just to be sure
7928     cps->pr = NoProc; 
7929     if(cps == &first) {
7930         appData.noChessProgram = TRUE;
7931         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
7932         gameMode = BeginningOfGame; ModeHighlight();
7933         SetNCPMode();
7934     }
7935     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
7936     DisplayMessage("", ""); // erase waiting message
7937     if(errmess) DisplayError(errmess, 0); // announce reason, if given
7938     return TRUE;
7939 }
7940
7941 char *savedMessage;
7942 ChessProgramState *savedState;
7943 void
7944 DeferredBookMove (void)
7945 {
7946         if(savedState->lastPing != savedState->lastPong)
7947                     ScheduleDelayedEvent(DeferredBookMove, 10);
7948         else
7949         HandleMachineMove(savedMessage, savedState);
7950 }
7951
7952 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7953
7954 void
7955 HandleMachineMove (char *message, ChessProgramState *cps)
7956 {
7957     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7958     char realname[MSG_SIZ];
7959     int fromX, fromY, toX, toY;
7960     ChessMove moveType;
7961     char promoChar;
7962     char *p, *pv=buf1;
7963     int machineWhite, oldError;
7964     char *bookHit;
7965
7966     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7967         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7968         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7969             DisplayError(_("Invalid pairing from pairing engine"), 0);
7970             return;
7971         }
7972         pairingReceived = 1;
7973         NextMatchGame();
7974         return; // Skim the pairing messages here.
7975     }
7976
7977     oldError = cps->userError; cps->userError = 0;
7978
7979 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7980     /*
7981      * Kludge to ignore BEL characters
7982      */
7983     while (*message == '\007') message++;
7984
7985     /*
7986      * [HGM] engine debug message: ignore lines starting with '#' character
7987      */
7988     if(cps->debug && *message == '#') return;
7989
7990     /*
7991      * Look for book output
7992      */
7993     if (cps == &first && bookRequested) {
7994         if (message[0] == '\t' || message[0] == ' ') {
7995             /* Part of the book output is here; append it */
7996             strcat(bookOutput, message);
7997             strcat(bookOutput, "  \n");
7998             return;
7999         } else if (bookOutput[0] != NULLCHAR) {
8000             /* All of book output has arrived; display it */
8001             char *p = bookOutput;
8002             while (*p != NULLCHAR) {
8003                 if (*p == '\t') *p = ' ';
8004                 p++;
8005             }
8006             DisplayInformation(bookOutput);
8007             bookRequested = FALSE;
8008             /* Fall through to parse the current output */
8009         }
8010     }
8011
8012     /*
8013      * Look for machine move.
8014      */
8015     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8016         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8017     {
8018         /* This method is only useful on engines that support ping */
8019         if (cps->lastPing != cps->lastPong) {
8020           if (gameMode == BeginningOfGame) {
8021             /* Extra move from before last new; ignore */
8022             if (appData.debugMode) {
8023                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8024             }
8025           } else {
8026             if (appData.debugMode) {
8027                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8028                         cps->which, gameMode);
8029             }
8030
8031             SendToProgram("undo\n", cps);
8032           }
8033           return;
8034         }
8035
8036         switch (gameMode) {
8037           case BeginningOfGame:
8038             /* Extra move from before last reset; ignore */
8039             if (appData.debugMode) {
8040                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8041             }
8042             return;
8043
8044           case EndOfGame:
8045           case IcsIdle:
8046           default:
8047             /* Extra move after we tried to stop.  The mode test is
8048                not a reliable way of detecting this problem, but it's
8049                the best we can do on engines that don't support ping.
8050             */
8051             if (appData.debugMode) {
8052                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8053                         cps->which, gameMode);
8054             }
8055             SendToProgram("undo\n", cps);
8056             return;
8057
8058           case MachinePlaysWhite:
8059           case IcsPlayingWhite:
8060             machineWhite = TRUE;
8061             break;
8062
8063           case MachinePlaysBlack:
8064           case IcsPlayingBlack:
8065             machineWhite = FALSE;
8066             break;
8067
8068           case TwoMachinesPlay:
8069             machineWhite = (cps->twoMachinesColor[0] == 'w');
8070             break;
8071         }
8072         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8073             if (appData.debugMode) {
8074                 fprintf(debugFP,
8075                         "Ignoring move out of turn by %s, gameMode %d"
8076                         ", forwardMost %d\n",
8077                         cps->which, gameMode, forwardMostMove);
8078             }
8079             return;
8080         }
8081
8082         if(cps->alphaRank) AlphaRank(machineMove, 4);
8083         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8084                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8085             /* Machine move could not be parsed; ignore it. */
8086           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8087                     machineMove, _(cps->which));
8088             DisplayError(buf1, 0);
8089             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8090                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8091             if (gameMode == TwoMachinesPlay) {
8092               GameEnds(machineWhite ? BlackWins : WhiteWins,
8093                        buf1, GE_XBOARD);
8094             }
8095             return;
8096         }
8097
8098         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8099         /* So we have to redo legality test with true e.p. status here,  */
8100         /* to make sure an illegal e.p. capture does not slip through,   */
8101         /* to cause a forfeit on a justified illegal-move complaint      */
8102         /* of the opponent.                                              */
8103         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8104            ChessMove moveType;
8105            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8106                              fromY, fromX, toY, toX, promoChar);
8107             if(moveType == IllegalMove) {
8108               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8109                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8110                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8111                            buf1, GE_XBOARD);
8112                 return;
8113            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8114            /* [HGM] Kludge to handle engines that send FRC-style castling
8115               when they shouldn't (like TSCP-Gothic) */
8116            switch(moveType) {
8117              case WhiteASideCastleFR:
8118              case BlackASideCastleFR:
8119                toX+=2;
8120                currentMoveString[2]++;
8121                break;
8122              case WhiteHSideCastleFR:
8123              case BlackHSideCastleFR:
8124                toX--;
8125                currentMoveString[2]--;
8126                break;
8127              default: ; // nothing to do, but suppresses warning of pedantic compilers
8128            }
8129         }
8130         hintRequested = FALSE;
8131         lastHint[0] = NULLCHAR;
8132         bookRequested = FALSE;
8133         /* Program may be pondering now */
8134         cps->maybeThinking = TRUE;
8135         if (cps->sendTime == 2) cps->sendTime = 1;
8136         if (cps->offeredDraw) cps->offeredDraw--;
8137
8138         /* [AS] Save move info*/
8139         pvInfoList[ forwardMostMove ].score = programStats.score;
8140         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8141         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8142
8143         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8144
8145         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8146         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8147             int count = 0;
8148
8149             while( count < adjudicateLossPlies ) {
8150                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8151
8152                 if( count & 1 ) {
8153                     score = -score; /* Flip score for winning side */
8154                 }
8155
8156                 if( score > adjudicateLossThreshold ) {
8157                     break;
8158                 }
8159
8160                 count++;
8161             }
8162
8163             if( count >= adjudicateLossPlies ) {
8164                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8165
8166                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8167                     "Xboard adjudication",
8168                     GE_XBOARD );
8169
8170                 return;
8171             }
8172         }
8173
8174         if(Adjudicate(cps)) {
8175             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8176             return; // [HGM] adjudicate: for all automatic game ends
8177         }
8178
8179 #if ZIPPY
8180         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8181             first.initDone) {
8182           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8183                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8184                 SendToICS("draw ");
8185                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8186           }
8187           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8188           ics_user_moved = 1;
8189           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8190                 char buf[3*MSG_SIZ];
8191
8192                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8193                         programStats.score / 100.,
8194                         programStats.depth,
8195                         programStats.time / 100.,
8196                         (unsigned int)programStats.nodes,
8197                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8198                         programStats.movelist);
8199                 SendToICS(buf);
8200 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8201           }
8202         }
8203 #endif
8204
8205         /* [AS] Clear stats for next move */
8206         ClearProgramStats();
8207         thinkOutput[0] = NULLCHAR;
8208         hiddenThinkOutputState = 0;
8209
8210         bookHit = NULL;
8211         if (gameMode == TwoMachinesPlay) {
8212             /* [HGM] relaying draw offers moved to after reception of move */
8213             /* and interpreting offer as claim if it brings draw condition */
8214             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8215                 SendToProgram("draw\n", cps->other);
8216             }
8217             if (cps->other->sendTime) {
8218                 SendTimeRemaining(cps->other,
8219                                   cps->other->twoMachinesColor[0] == 'w');
8220             }
8221             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8222             if (firstMove && !bookHit) {
8223                 firstMove = FALSE;
8224                 if (cps->other->useColors) {
8225                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8226                 }
8227                 SendToProgram("go\n", cps->other);
8228             }
8229             cps->other->maybeThinking = TRUE;
8230         }
8231
8232         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8233
8234         if (!pausing && appData.ringBellAfterMoves) {
8235             RingBell();
8236         }
8237
8238         /*
8239          * Reenable menu items that were disabled while
8240          * machine was thinking
8241          */
8242         if (gameMode != TwoMachinesPlay)
8243             SetUserThinkingEnables();
8244
8245         // [HGM] book: after book hit opponent has received move and is now in force mode
8246         // force the book reply into it, and then fake that it outputted this move by jumping
8247         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8248         if(bookHit) {
8249                 static char bookMove[MSG_SIZ]; // a bit generous?
8250
8251                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8252                 strcat(bookMove, bookHit);
8253                 message = bookMove;
8254                 cps = cps->other;
8255                 programStats.nodes = programStats.depth = programStats.time =
8256                 programStats.score = programStats.got_only_move = 0;
8257                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8258
8259                 if(cps->lastPing != cps->lastPong) {
8260                     savedMessage = message; // args for deferred call
8261                     savedState = cps;
8262                     ScheduleDelayedEvent(DeferredBookMove, 10);
8263                     return;
8264                 }
8265                 goto FakeBookMove;
8266         }
8267
8268         return;
8269     }
8270
8271     /* Set special modes for chess engines.  Later something general
8272      *  could be added here; for now there is just one kludge feature,
8273      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8274      *  when "xboard" is given as an interactive command.
8275      */
8276     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8277         cps->useSigint = FALSE;
8278         cps->useSigterm = FALSE;
8279     }
8280     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8281       ParseFeatures(message+8, cps);
8282       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8283     }
8284
8285     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8286                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8287       int dummy, s=6; char buf[MSG_SIZ];
8288       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8289       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8290       if(startedFromSetupPosition) return;
8291       ParseFEN(boards[0], &dummy, message+s);
8292       DrawPosition(TRUE, boards[0]);
8293       startedFromSetupPosition = TRUE;
8294       return;
8295     }
8296     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8297      * want this, I was asked to put it in, and obliged.
8298      */
8299     if (!strncmp(message, "setboard ", 9)) {
8300         Board initial_position;
8301
8302         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8303
8304         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8305             DisplayError(_("Bad FEN received from engine"), 0);
8306             return ;
8307         } else {
8308            Reset(TRUE, FALSE);
8309            CopyBoard(boards[0], initial_position);
8310            initialRulePlies = FENrulePlies;
8311            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8312            else gameMode = MachinePlaysBlack;
8313            DrawPosition(FALSE, boards[currentMove]);
8314         }
8315         return;
8316     }
8317
8318     /*
8319      * Look for communication commands
8320      */
8321     if (!strncmp(message, "telluser ", 9)) {
8322         if(message[9] == '\\' && message[10] == '\\')
8323             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8324         PlayTellSound();
8325         DisplayNote(message + 9);
8326         return;
8327     }
8328     if (!strncmp(message, "tellusererror ", 14)) {
8329         cps->userError = 1;
8330         if(message[14] == '\\' && message[15] == '\\')
8331             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8332         PlayTellSound();
8333         DisplayError(message + 14, 0);
8334         return;
8335     }
8336     if (!strncmp(message, "tellopponent ", 13)) {
8337       if (appData.icsActive) {
8338         if (loggedOn) {
8339           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8340           SendToICS(buf1);
8341         }
8342       } else {
8343         DisplayNote(message + 13);
8344       }
8345       return;
8346     }
8347     if (!strncmp(message, "tellothers ", 11)) {
8348       if (appData.icsActive) {
8349         if (loggedOn) {
8350           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8351           SendToICS(buf1);
8352         }
8353       }
8354       return;
8355     }
8356     if (!strncmp(message, "tellall ", 8)) {
8357       if (appData.icsActive) {
8358         if (loggedOn) {
8359           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8360           SendToICS(buf1);
8361         }
8362       } else {
8363         DisplayNote(message + 8);
8364       }
8365       return;
8366     }
8367     if (strncmp(message, "warning", 7) == 0) {
8368         /* Undocumented feature, use tellusererror in new code */
8369         DisplayError(message, 0);
8370         return;
8371     }
8372     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8373         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8374         strcat(realname, " query");
8375         AskQuestion(realname, buf2, buf1, cps->pr);
8376         return;
8377     }
8378     /* Commands from the engine directly to ICS.  We don't allow these to be
8379      *  sent until we are logged on. Crafty kibitzes have been known to
8380      *  interfere with the login process.
8381      */
8382     if (loggedOn) {
8383         if (!strncmp(message, "tellics ", 8)) {
8384             SendToICS(message + 8);
8385             SendToICS("\n");
8386             return;
8387         }
8388         if (!strncmp(message, "tellicsnoalias ", 15)) {
8389             SendToICS(ics_prefix);
8390             SendToICS(message + 15);
8391             SendToICS("\n");
8392             return;
8393         }
8394         /* The following are for backward compatibility only */
8395         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8396             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8397             SendToICS(ics_prefix);
8398             SendToICS(message);
8399             SendToICS("\n");
8400             return;
8401         }
8402     }
8403     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8404         return;
8405     }
8406     /*
8407      * If the move is illegal, cancel it and redraw the board.
8408      * Also deal with other error cases.  Matching is rather loose
8409      * here to accommodate engines written before the spec.
8410      */
8411     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8412         strncmp(message, "Error", 5) == 0) {
8413         if (StrStr(message, "name") ||
8414             StrStr(message, "rating") || StrStr(message, "?") ||
8415             StrStr(message, "result") || StrStr(message, "board") ||
8416             StrStr(message, "bk") || StrStr(message, "computer") ||
8417             StrStr(message, "variant") || StrStr(message, "hint") ||
8418             StrStr(message, "random") || StrStr(message, "depth") ||
8419             StrStr(message, "accepted")) {
8420             return;
8421         }
8422         if (StrStr(message, "protover")) {
8423           /* Program is responding to input, so it's apparently done
8424              initializing, and this error message indicates it is
8425              protocol version 1.  So we don't need to wait any longer
8426              for it to initialize and send feature commands. */
8427           FeatureDone(cps, 1);
8428           cps->protocolVersion = 1;
8429           return;
8430         }
8431         cps->maybeThinking = FALSE;
8432
8433         if (StrStr(message, "draw")) {
8434             /* Program doesn't have "draw" command */
8435             cps->sendDrawOffers = 0;
8436             return;
8437         }
8438         if (cps->sendTime != 1 &&
8439             (StrStr(message, "time") || StrStr(message, "otim"))) {
8440           /* Program apparently doesn't have "time" or "otim" command */
8441           cps->sendTime = 0;
8442           return;
8443         }
8444         if (StrStr(message, "analyze")) {
8445             cps->analysisSupport = FALSE;
8446             cps->analyzing = FALSE;
8447 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8448             EditGameEvent(); // [HGM] try to preserve loaded game
8449             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8450             DisplayError(buf2, 0);
8451             return;
8452         }
8453         if (StrStr(message, "(no matching move)st")) {
8454           /* Special kludge for GNU Chess 4 only */
8455           cps->stKludge = TRUE;
8456           SendTimeControl(cps, movesPerSession, timeControl,
8457                           timeIncrement, appData.searchDepth,
8458                           searchTime);
8459           return;
8460         }
8461         if (StrStr(message, "(no matching move)sd")) {
8462           /* Special kludge for GNU Chess 4 only */
8463           cps->sdKludge = TRUE;
8464           SendTimeControl(cps, movesPerSession, timeControl,
8465                           timeIncrement, appData.searchDepth,
8466                           searchTime);
8467           return;
8468         }
8469         if (!StrStr(message, "llegal")) {
8470             return;
8471         }
8472         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8473             gameMode == IcsIdle) return;
8474         if (forwardMostMove <= backwardMostMove) return;
8475         if (pausing) PauseEvent();
8476       if(appData.forceIllegal) {
8477             // [HGM] illegal: machine refused move; force position after move into it
8478           SendToProgram("force\n", cps);
8479           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8480                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8481                 // when black is to move, while there might be nothing on a2 or black
8482                 // might already have the move. So send the board as if white has the move.
8483                 // But first we must change the stm of the engine, as it refused the last move
8484                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8485                 if(WhiteOnMove(forwardMostMove)) {
8486                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8487                     SendBoard(cps, forwardMostMove); // kludgeless board
8488                 } else {
8489                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8490                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8491                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8492                 }
8493           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8494             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8495                  gameMode == TwoMachinesPlay)
8496               SendToProgram("go\n", cps);
8497             return;
8498       } else
8499         if (gameMode == PlayFromGameFile) {
8500             /* Stop reading this game file */
8501             gameMode = EditGame;
8502             ModeHighlight();
8503         }
8504         /* [HGM] illegal-move claim should forfeit game when Xboard */
8505         /* only passes fully legal moves                            */
8506         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8507             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8508                                 "False illegal-move claim", GE_XBOARD );
8509             return; // do not take back move we tested as valid
8510         }
8511         currentMove = forwardMostMove-1;
8512         DisplayMove(currentMove-1); /* before DisplayMoveError */
8513         SwitchClocks(forwardMostMove-1); // [HGM] race
8514         DisplayBothClocks();
8515         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8516                 parseList[currentMove], _(cps->which));
8517         DisplayMoveError(buf1);
8518         DrawPosition(FALSE, boards[currentMove]);
8519
8520         SetUserThinkingEnables();
8521         return;
8522     }
8523     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8524         /* Program has a broken "time" command that
8525            outputs a string not ending in newline.
8526            Don't use it. */
8527         cps->sendTime = 0;
8528     }
8529
8530     /*
8531      * If chess program startup fails, exit with an error message.
8532      * Attempts to recover here are futile. [HGM] Well, we try anyway
8533      */
8534     if ((StrStr(message, "unknown host") != NULL)
8535         || (StrStr(message, "No remote directory") != NULL)
8536         || (StrStr(message, "not found") != NULL)
8537         || (StrStr(message, "No such file") != NULL)
8538         || (StrStr(message, "can't alloc") != NULL)
8539         || (StrStr(message, "Permission denied") != NULL)) {
8540
8541         cps->maybeThinking = FALSE;
8542         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8543                 _(cps->which), cps->program, cps->host, message);
8544         RemoveInputSource(cps->isr);
8545         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8546             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8547             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8548         }
8549         return;
8550     }
8551
8552     /*
8553      * Look for hint output
8554      */
8555     if (sscanf(message, "Hint: %s", buf1) == 1) {
8556         if (cps == &first && hintRequested) {
8557             hintRequested = FALSE;
8558             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8559                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8560                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8561                                     PosFlags(forwardMostMove),
8562                                     fromY, fromX, toY, toX, promoChar, buf1);
8563                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8564                 DisplayInformation(buf2);
8565             } else {
8566                 /* Hint move could not be parsed!? */
8567               snprintf(buf2, sizeof(buf2),
8568                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8569                         buf1, _(cps->which));
8570                 DisplayError(buf2, 0);
8571             }
8572         } else {
8573           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8574         }
8575         return;
8576     }
8577
8578     /*
8579      * Ignore other messages if game is not in progress
8580      */
8581     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8582         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8583
8584     /*
8585      * look for win, lose, draw, or draw offer
8586      */
8587     if (strncmp(message, "1-0", 3) == 0) {
8588         char *p, *q, *r = "";
8589         p = strchr(message, '{');
8590         if (p) {
8591             q = strchr(p, '}');
8592             if (q) {
8593                 *q = NULLCHAR;
8594                 r = p + 1;
8595             }
8596         }
8597         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8598         return;
8599     } else if (strncmp(message, "0-1", 3) == 0) {
8600         char *p, *q, *r = "";
8601         p = strchr(message, '{');
8602         if (p) {
8603             q = strchr(p, '}');
8604             if (q) {
8605                 *q = NULLCHAR;
8606                 r = p + 1;
8607             }
8608         }
8609         /* Kludge for Arasan 4.1 bug */
8610         if (strcmp(r, "Black resigns") == 0) {
8611             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8612             return;
8613         }
8614         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8615         return;
8616     } else if (strncmp(message, "1/2", 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
8627         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8628         return;
8629
8630     } else if (strncmp(message, "White resign", 12) == 0) {
8631         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8632         return;
8633     } else if (strncmp(message, "Black resign", 12) == 0) {
8634         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8635         return;
8636     } else if (strncmp(message, "White matches", 13) == 0 ||
8637                strncmp(message, "Black matches", 13) == 0   ) {
8638         /* [HGM] ignore GNUShogi noises */
8639         return;
8640     } else if (strncmp(message, "White", 5) == 0 &&
8641                message[5] != '(' &&
8642                StrStr(message, "Black") == NULL) {
8643         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8644         return;
8645     } else if (strncmp(message, "Black", 5) == 0 &&
8646                message[5] != '(') {
8647         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8648         return;
8649     } else if (strcmp(message, "resign") == 0 ||
8650                strcmp(message, "computer resigns") == 0) {
8651         switch (gameMode) {
8652           case MachinePlaysBlack:
8653           case IcsPlayingBlack:
8654             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8655             break;
8656           case MachinePlaysWhite:
8657           case IcsPlayingWhite:
8658             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8659             break;
8660           case TwoMachinesPlay:
8661             if (cps->twoMachinesColor[0] == 'w')
8662               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8663             else
8664               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8665             break;
8666           default:
8667             /* can't happen */
8668             break;
8669         }
8670         return;
8671     } else if (strncmp(message, "opponent mates", 14) == 0) {
8672         switch (gameMode) {
8673           case MachinePlaysBlack:
8674           case IcsPlayingBlack:
8675             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8676             break;
8677           case MachinePlaysWhite:
8678           case IcsPlayingWhite:
8679             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8680             break;
8681           case TwoMachinesPlay:
8682             if (cps->twoMachinesColor[0] == 'w')
8683               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8684             else
8685               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8686             break;
8687           default:
8688             /* can't happen */
8689             break;
8690         }
8691         return;
8692     } else if (strncmp(message, "computer mates", 14) == 0) {
8693         switch (gameMode) {
8694           case MachinePlaysBlack:
8695           case IcsPlayingBlack:
8696             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8697             break;
8698           case MachinePlaysWhite:
8699           case IcsPlayingWhite:
8700             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8701             break;
8702           case TwoMachinesPlay:
8703             if (cps->twoMachinesColor[0] == 'w')
8704               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8705             else
8706               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8707             break;
8708           default:
8709             /* can't happen */
8710             break;
8711         }
8712         return;
8713     } else if (strncmp(message, "checkmate", 9) == 0) {
8714         if (WhiteOnMove(forwardMostMove)) {
8715             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8716         } else {
8717             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8718         }
8719         return;
8720     } else if (strstr(message, "Draw") != NULL ||
8721                strstr(message, "game is a draw") != NULL) {
8722         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8723         return;
8724     } else if (strstr(message, "offer") != NULL &&
8725                strstr(message, "draw") != NULL) {
8726 #if ZIPPY
8727         if (appData.zippyPlay && first.initDone) {
8728             /* Relay offer to ICS */
8729             SendToICS(ics_prefix);
8730             SendToICS("draw\n");
8731         }
8732 #endif
8733         cps->offeredDraw = 2; /* valid until this engine moves twice */
8734         if (gameMode == TwoMachinesPlay) {
8735             if (cps->other->offeredDraw) {
8736                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8737             /* [HGM] in two-machine mode we delay relaying draw offer      */
8738             /* until after we also have move, to see if it is really claim */
8739             }
8740         } else if (gameMode == MachinePlaysWhite ||
8741                    gameMode == MachinePlaysBlack) {
8742           if (userOfferedDraw) {
8743             DisplayInformation(_("Machine accepts your draw offer"));
8744             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8745           } else {
8746             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8747           }
8748         }
8749     }
8750
8751
8752     /*
8753      * Look for thinking output
8754      */
8755     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8756           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8757                                 ) {
8758         int plylev, mvleft, mvtot, curscore, time;
8759         char mvname[MOVE_LEN];
8760         u64 nodes; // [DM]
8761         char plyext;
8762         int ignore = FALSE;
8763         int prefixHint = FALSE;
8764         mvname[0] = NULLCHAR;
8765
8766         switch (gameMode) {
8767           case MachinePlaysBlack:
8768           case IcsPlayingBlack:
8769             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8770             break;
8771           case MachinePlaysWhite:
8772           case IcsPlayingWhite:
8773             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8774             break;
8775           case AnalyzeMode:
8776           case AnalyzeFile:
8777             break;
8778           case IcsObserving: /* [DM] icsEngineAnalyze */
8779             if (!appData.icsEngineAnalyze) ignore = TRUE;
8780             break;
8781           case TwoMachinesPlay:
8782             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8783                 ignore = TRUE;
8784             }
8785             break;
8786           default:
8787             ignore = TRUE;
8788             break;
8789         }
8790
8791         if (!ignore) {
8792             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8793             buf1[0] = NULLCHAR;
8794             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8795                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8796
8797                 if (plyext != ' ' && plyext != '\t') {
8798                     time *= 100;
8799                 }
8800
8801                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8802                 if( cps->scoreIsAbsolute &&
8803                     ( gameMode == MachinePlaysBlack ||
8804                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8805                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8806                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8807                      !WhiteOnMove(currentMove)
8808                     ) )
8809                 {
8810                     curscore = -curscore;
8811                 }
8812
8813                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8814
8815                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8816                         char buf[MSG_SIZ];
8817                         FILE *f;
8818                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8819                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8820                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8821                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8822                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8823                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8824                                 fclose(f);
8825                         } else DisplayError(_("failed writing PV"), 0);
8826                 }
8827
8828                 tempStats.depth = plylev;
8829                 tempStats.nodes = nodes;
8830                 tempStats.time = time;
8831                 tempStats.score = curscore;
8832                 tempStats.got_only_move = 0;
8833
8834                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8835                         int ticklen;
8836
8837                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8838                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8839                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8840                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8841                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8842                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8843                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8844                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8845                 }
8846
8847                 /* Buffer overflow protection */
8848                 if (pv[0] != NULLCHAR) {
8849                     if (strlen(pv) >= sizeof(tempStats.movelist)
8850                         && appData.debugMode) {
8851                         fprintf(debugFP,
8852                                 "PV is too long; using the first %u bytes.\n",
8853                                 (unsigned) sizeof(tempStats.movelist) - 1);
8854                     }
8855
8856                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8857                 } else {
8858                     sprintf(tempStats.movelist, " no PV\n");
8859                 }
8860
8861                 if (tempStats.seen_stat) {
8862                     tempStats.ok_to_send = 1;
8863                 }
8864
8865                 if (strchr(tempStats.movelist, '(') != NULL) {
8866                     tempStats.line_is_book = 1;
8867                     tempStats.nr_moves = 0;
8868                     tempStats.moves_left = 0;
8869                 } else {
8870                     tempStats.line_is_book = 0;
8871                 }
8872
8873                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8874                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8875
8876                 SendProgramStatsToFrontend( cps, &tempStats );
8877
8878                 /*
8879                     [AS] Protect the thinkOutput buffer from overflow... this
8880                     is only useful if buf1 hasn't overflowed first!
8881                 */
8882                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8883                          plylev,
8884                          (gameMode == TwoMachinesPlay ?
8885                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8886                          ((double) curscore) / 100.0,
8887                          prefixHint ? lastHint : "",
8888                          prefixHint ? " " : "" );
8889
8890                 if( buf1[0] != NULLCHAR ) {
8891                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8892
8893                     if( strlen(pv) > max_len ) {
8894                         if( appData.debugMode) {
8895                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8896                         }
8897                         pv[max_len+1] = '\0';
8898                     }
8899
8900                     strcat( thinkOutput, pv);
8901                 }
8902
8903                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8904                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8905                     DisplayMove(currentMove - 1);
8906                 }
8907                 return;
8908
8909             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8910                 /* crafty (9.25+) says "(only move) <move>"
8911                  * if there is only 1 legal move
8912                  */
8913                 sscanf(p, "(only move) %s", buf1);
8914                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8915                 sprintf(programStats.movelist, "%s (only move)", buf1);
8916                 programStats.depth = 1;
8917                 programStats.nr_moves = 1;
8918                 programStats.moves_left = 1;
8919                 programStats.nodes = 1;
8920                 programStats.time = 1;
8921                 programStats.got_only_move = 1;
8922
8923                 /* Not really, but we also use this member to
8924                    mean "line isn't going to change" (Crafty
8925                    isn't searching, so stats won't change) */
8926                 programStats.line_is_book = 1;
8927
8928                 SendProgramStatsToFrontend( cps, &programStats );
8929
8930                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8931                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8932                     DisplayMove(currentMove - 1);
8933                 }
8934                 return;
8935             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8936                               &time, &nodes, &plylev, &mvleft,
8937                               &mvtot, mvname) >= 5) {
8938                 /* The stat01: line is from Crafty (9.29+) in response
8939                    to the "." command */
8940                 programStats.seen_stat = 1;
8941                 cps->maybeThinking = TRUE;
8942
8943                 if (programStats.got_only_move || !appData.periodicUpdates)
8944                   return;
8945
8946                 programStats.depth = plylev;
8947                 programStats.time = time;
8948                 programStats.nodes = nodes;
8949                 programStats.moves_left = mvleft;
8950                 programStats.nr_moves = mvtot;
8951                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8952                 programStats.ok_to_send = 1;
8953                 programStats.movelist[0] = '\0';
8954
8955                 SendProgramStatsToFrontend( cps, &programStats );
8956
8957                 return;
8958
8959             } else if (strncmp(message,"++",2) == 0) {
8960                 /* Crafty 9.29+ outputs this */
8961                 programStats.got_fail = 2;
8962                 return;
8963
8964             } else if (strncmp(message,"--",2) == 0) {
8965                 /* Crafty 9.29+ outputs this */
8966                 programStats.got_fail = 1;
8967                 return;
8968
8969             } else if (thinkOutput[0] != NULLCHAR &&
8970                        strncmp(message, "    ", 4) == 0) {
8971                 unsigned message_len;
8972
8973                 p = message;
8974                 while (*p && *p == ' ') p++;
8975
8976                 message_len = strlen( p );
8977
8978                 /* [AS] Avoid buffer overflow */
8979                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8980                     strcat(thinkOutput, " ");
8981                     strcat(thinkOutput, p);
8982                 }
8983
8984                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8985                     strcat(programStats.movelist, " ");
8986                     strcat(programStats.movelist, p);
8987                 }
8988
8989                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8990                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8991                     DisplayMove(currentMove - 1);
8992                 }
8993                 return;
8994             }
8995         }
8996         else {
8997             buf1[0] = NULLCHAR;
8998
8999             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9000                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9001             {
9002                 ChessProgramStats cpstats;
9003
9004                 if (plyext != ' ' && plyext != '\t') {
9005                     time *= 100;
9006                 }
9007
9008                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9009                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9010                     curscore = -curscore;
9011                 }
9012
9013                 cpstats.depth = plylev;
9014                 cpstats.nodes = nodes;
9015                 cpstats.time = time;
9016                 cpstats.score = curscore;
9017                 cpstats.got_only_move = 0;
9018                 cpstats.movelist[0] = '\0';
9019
9020                 if (buf1[0] != NULLCHAR) {
9021                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9022                 }
9023
9024                 cpstats.ok_to_send = 0;
9025                 cpstats.line_is_book = 0;
9026                 cpstats.nr_moves = 0;
9027                 cpstats.moves_left = 0;
9028
9029                 SendProgramStatsToFrontend( cps, &cpstats );
9030             }
9031         }
9032     }
9033 }
9034
9035
9036 /* Parse a game score from the character string "game", and
9037    record it as the history of the current game.  The game
9038    score is NOT assumed to start from the standard position.
9039    The display is not updated in any way.
9040    */
9041 void
9042 ParseGameHistory (char *game)
9043 {
9044     ChessMove moveType;
9045     int fromX, fromY, toX, toY, boardIndex;
9046     char promoChar;
9047     char *p, *q;
9048     char buf[MSG_SIZ];
9049
9050     if (appData.debugMode)
9051       fprintf(debugFP, "Parsing game history: %s\n", game);
9052
9053     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9054     gameInfo.site = StrSave(appData.icsHost);
9055     gameInfo.date = PGNDate();
9056     gameInfo.round = StrSave("-");
9057
9058     /* Parse out names of players */
9059     while (*game == ' ') game++;
9060     p = buf;
9061     while (*game != ' ') *p++ = *game++;
9062     *p = NULLCHAR;
9063     gameInfo.white = StrSave(buf);
9064     while (*game == ' ') game++;
9065     p = buf;
9066     while (*game != ' ' && *game != '\n') *p++ = *game++;
9067     *p = NULLCHAR;
9068     gameInfo.black = StrSave(buf);
9069
9070     /* Parse moves */
9071     boardIndex = blackPlaysFirst ? 1 : 0;
9072     yynewstr(game);
9073     for (;;) {
9074         yyboardindex = boardIndex;
9075         moveType = (ChessMove) Myylex();
9076         switch (moveType) {
9077           case IllegalMove:             /* maybe suicide chess, etc. */
9078   if (appData.debugMode) {
9079     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9080     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9081     setbuf(debugFP, NULL);
9082   }
9083           case WhitePromotion:
9084           case BlackPromotion:
9085           case WhiteNonPromotion:
9086           case BlackNonPromotion:
9087           case NormalMove:
9088           case WhiteCapturesEnPassant:
9089           case BlackCapturesEnPassant:
9090           case WhiteKingSideCastle:
9091           case WhiteQueenSideCastle:
9092           case BlackKingSideCastle:
9093           case BlackQueenSideCastle:
9094           case WhiteKingSideCastleWild:
9095           case WhiteQueenSideCastleWild:
9096           case BlackKingSideCastleWild:
9097           case BlackQueenSideCastleWild:
9098           /* PUSH Fabien */
9099           case WhiteHSideCastleFR:
9100           case WhiteASideCastleFR:
9101           case BlackHSideCastleFR:
9102           case BlackASideCastleFR:
9103           /* POP Fabien */
9104             fromX = currentMoveString[0] - AAA;
9105             fromY = currentMoveString[1] - ONE;
9106             toX = currentMoveString[2] - AAA;
9107             toY = currentMoveString[3] - ONE;
9108             promoChar = currentMoveString[4];
9109             break;
9110           case WhiteDrop:
9111           case BlackDrop:
9112             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9113             fromX = moveType == WhiteDrop ?
9114               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9115             (int) CharToPiece(ToLower(currentMoveString[0]));
9116             fromY = DROP_RANK;
9117             toX = currentMoveString[2] - AAA;
9118             toY = currentMoveString[3] - ONE;
9119             promoChar = NULLCHAR;
9120             break;
9121           case AmbiguousMove:
9122             /* bug? */
9123             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9124   if (appData.debugMode) {
9125     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9126     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9127     setbuf(debugFP, NULL);
9128   }
9129             DisplayError(buf, 0);
9130             return;
9131           case ImpossibleMove:
9132             /* bug? */
9133             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9134   if (appData.debugMode) {
9135     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9136     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9137     setbuf(debugFP, NULL);
9138   }
9139             DisplayError(buf, 0);
9140             return;
9141           case EndOfFile:
9142             if (boardIndex < backwardMostMove) {
9143                 /* Oops, gap.  How did that happen? */
9144                 DisplayError(_("Gap in move list"), 0);
9145                 return;
9146             }
9147             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9148             if (boardIndex > forwardMostMove) {
9149                 forwardMostMove = boardIndex;
9150             }
9151             return;
9152           case ElapsedTime:
9153             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9154                 strcat(parseList[boardIndex-1], " ");
9155                 strcat(parseList[boardIndex-1], yy_text);
9156             }
9157             continue;
9158           case Comment:
9159           case PGNTag:
9160           case NAG:
9161           default:
9162             /* ignore */
9163             continue;
9164           case WhiteWins:
9165           case BlackWins:
9166           case GameIsDrawn:
9167           case GameUnfinished:
9168             if (gameMode == IcsExamining) {
9169                 if (boardIndex < backwardMostMove) {
9170                     /* Oops, gap.  How did that happen? */
9171                     return;
9172                 }
9173                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9174                 return;
9175             }
9176             gameInfo.result = moveType;
9177             p = strchr(yy_text, '{');
9178             if (p == NULL) p = strchr(yy_text, '(');
9179             if (p == NULL) {
9180                 p = yy_text;
9181                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9182             } else {
9183                 q = strchr(p, *p == '{' ? '}' : ')');
9184                 if (q != NULL) *q = NULLCHAR;
9185                 p++;
9186             }
9187             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9188             gameInfo.resultDetails = StrSave(p);
9189             continue;
9190         }
9191         if (boardIndex >= forwardMostMove &&
9192             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9193             backwardMostMove = blackPlaysFirst ? 1 : 0;
9194             return;
9195         }
9196         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9197                                  fromY, fromX, toY, toX, promoChar,
9198                                  parseList[boardIndex]);
9199         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9200         /* currentMoveString is set as a side-effect of yylex */
9201         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9202         strcat(moveList[boardIndex], "\n");
9203         boardIndex++;
9204         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9205         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9206           case MT_NONE:
9207           case MT_STALEMATE:
9208           default:
9209             break;
9210           case MT_CHECK:
9211             if(gameInfo.variant != VariantShogi)
9212                 strcat(parseList[boardIndex - 1], "+");
9213             break;
9214           case MT_CHECKMATE:
9215           case MT_STAINMATE:
9216             strcat(parseList[boardIndex - 1], "#");
9217             break;
9218         }
9219     }
9220 }
9221
9222
9223 /* Apply a move to the given board  */
9224 void
9225 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9226 {
9227   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9228   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9229
9230     /* [HGM] compute & store e.p. status and castling rights for new position */
9231     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9232
9233       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9234       oldEP = (signed char)board[EP_STATUS];
9235       board[EP_STATUS] = EP_NONE;
9236
9237   if (fromY == DROP_RANK) {
9238         /* must be first */
9239         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9240             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9241             return;
9242         }
9243         piece = board[toY][toX] = (ChessSquare) fromX;
9244   } else {
9245       int i;
9246
9247       if( board[toY][toX] != EmptySquare )
9248            board[EP_STATUS] = EP_CAPTURE;
9249
9250       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9251            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9252                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9253       } else
9254       if( board[fromY][fromX] == WhitePawn ) {
9255            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9256                board[EP_STATUS] = EP_PAWN_MOVE;
9257            if( toY-fromY==2) {
9258                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9259                         gameInfo.variant != VariantBerolina || toX < fromX)
9260                       board[EP_STATUS] = toX | berolina;
9261                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9262                         gameInfo.variant != VariantBerolina || toX > fromX)
9263                       board[EP_STATUS] = toX;
9264            }
9265       } else
9266       if( board[fromY][fromX] == BlackPawn ) {
9267            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9268                board[EP_STATUS] = EP_PAWN_MOVE;
9269            if( toY-fromY== -2) {
9270                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9271                         gameInfo.variant != VariantBerolina || toX < fromX)
9272                       board[EP_STATUS] = toX | berolina;
9273                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9274                         gameInfo.variant != VariantBerolina || toX > fromX)
9275                       board[EP_STATUS] = toX;
9276            }
9277        }
9278
9279        for(i=0; i<nrCastlingRights; i++) {
9280            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9281               board[CASTLING][i] == toX   && castlingRank[i] == toY
9282              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9283        }
9284
9285      if (fromX == toX && fromY == toY) return;
9286
9287      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9288      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9289      if(gameInfo.variant == VariantKnightmate)
9290          king += (int) WhiteUnicorn - (int) WhiteKing;
9291
9292     /* Code added by Tord: */
9293     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9294     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9295         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9296       board[fromY][fromX] = EmptySquare;
9297       board[toY][toX] = EmptySquare;
9298       if((toX > fromX) != (piece == WhiteRook)) {
9299         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9300       } else {
9301         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9302       }
9303     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9304                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9305       board[fromY][fromX] = EmptySquare;
9306       board[toY][toX] = EmptySquare;
9307       if((toX > fromX) != (piece == BlackRook)) {
9308         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9309       } else {
9310         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9311       }
9312     /* End of code added by Tord */
9313
9314     } else if (board[fromY][fromX] == king
9315         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9316         && toY == fromY && toX > fromX+1) {
9317         board[fromY][fromX] = EmptySquare;
9318         board[toY][toX] = king;
9319         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9320         board[fromY][BOARD_RGHT-1] = EmptySquare;
9321     } else if (board[fromY][fromX] == king
9322         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9323                && toY == fromY && toX < fromX-1) {
9324         board[fromY][fromX] = EmptySquare;
9325         board[toY][toX] = king;
9326         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9327         board[fromY][BOARD_LEFT] = EmptySquare;
9328     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9329                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9330                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9331                ) {
9332         /* white pawn promotion */
9333         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9334         if(gameInfo.variant==VariantBughouse ||
9335            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9336             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9337         board[fromY][fromX] = EmptySquare;
9338     } else if ((fromY >= BOARD_HEIGHT>>1)
9339                && (toX != fromX)
9340                && gameInfo.variant != VariantXiangqi
9341                && gameInfo.variant != VariantBerolina
9342                && (board[fromY][fromX] == WhitePawn)
9343                && (board[toY][toX] == EmptySquare)) {
9344         board[fromY][fromX] = EmptySquare;
9345         board[toY][toX] = WhitePawn;
9346         captured = board[toY - 1][toX];
9347         board[toY - 1][toX] = EmptySquare;
9348     } else if ((fromY == BOARD_HEIGHT-4)
9349                && (toX == fromX)
9350                && gameInfo.variant == VariantBerolina
9351                && (board[fromY][fromX] == WhitePawn)
9352                && (board[toY][toX] == EmptySquare)) {
9353         board[fromY][fromX] = EmptySquare;
9354         board[toY][toX] = WhitePawn;
9355         if(oldEP & EP_BEROLIN_A) {
9356                 captured = board[fromY][fromX-1];
9357                 board[fromY][fromX-1] = EmptySquare;
9358         }else{  captured = board[fromY][fromX+1];
9359                 board[fromY][fromX+1] = EmptySquare;
9360         }
9361     } else if (board[fromY][fromX] == king
9362         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9363                && toY == fromY && toX > fromX+1) {
9364         board[fromY][fromX] = EmptySquare;
9365         board[toY][toX] = king;
9366         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9367         board[fromY][BOARD_RGHT-1] = EmptySquare;
9368     } else if (board[fromY][fromX] == king
9369         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9370                && toY == fromY && toX < fromX-1) {
9371         board[fromY][fromX] = EmptySquare;
9372         board[toY][toX] = king;
9373         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9374         board[fromY][BOARD_LEFT] = EmptySquare;
9375     } else if (fromY == 7 && fromX == 3
9376                && board[fromY][fromX] == BlackKing
9377                && toY == 7 && toX == 5) {
9378         board[fromY][fromX] = EmptySquare;
9379         board[toY][toX] = BlackKing;
9380         board[fromY][7] = EmptySquare;
9381         board[toY][4] = BlackRook;
9382     } else if (fromY == 7 && fromX == 3
9383                && board[fromY][fromX] == BlackKing
9384                && toY == 7 && toX == 1) {
9385         board[fromY][fromX] = EmptySquare;
9386         board[toY][toX] = BlackKing;
9387         board[fromY][0] = EmptySquare;
9388         board[toY][2] = BlackRook;
9389     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9390                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9391                && toY < promoRank && promoChar
9392                ) {
9393         /* black pawn promotion */
9394         board[toY][toX] = CharToPiece(ToLower(promoChar));
9395         if(gameInfo.variant==VariantBughouse ||
9396            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9397             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9398         board[fromY][fromX] = EmptySquare;
9399     } else if ((fromY < BOARD_HEIGHT>>1)
9400                && (toX != fromX)
9401                && gameInfo.variant != VariantXiangqi
9402                && gameInfo.variant != VariantBerolina
9403                && (board[fromY][fromX] == BlackPawn)
9404                && (board[toY][toX] == EmptySquare)) {
9405         board[fromY][fromX] = EmptySquare;
9406         board[toY][toX] = BlackPawn;
9407         captured = board[toY + 1][toX];
9408         board[toY + 1][toX] = EmptySquare;
9409     } else if ((fromY == 3)
9410                && (toX == fromX)
9411                && gameInfo.variant == VariantBerolina
9412                && (board[fromY][fromX] == BlackPawn)
9413                && (board[toY][toX] == EmptySquare)) {
9414         board[fromY][fromX] = EmptySquare;
9415         board[toY][toX] = BlackPawn;
9416         if(oldEP & EP_BEROLIN_A) {
9417                 captured = board[fromY][fromX-1];
9418                 board[fromY][fromX-1] = EmptySquare;
9419         }else{  captured = board[fromY][fromX+1];
9420                 board[fromY][fromX+1] = EmptySquare;
9421         }
9422     } else {
9423         board[toY][toX] = board[fromY][fromX];
9424         board[fromY][fromX] = EmptySquare;
9425     }
9426   }
9427
9428     if (gameInfo.holdingsWidth != 0) {
9429
9430       /* !!A lot more code needs to be written to support holdings  */
9431       /* [HGM] OK, so I have written it. Holdings are stored in the */
9432       /* penultimate board files, so they are automaticlly stored   */
9433       /* in the game history.                                       */
9434       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9435                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9436         /* Delete from holdings, by decreasing count */
9437         /* and erasing image if necessary            */
9438         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9439         if(p < (int) BlackPawn) { /* white drop */
9440              p -= (int)WhitePawn;
9441                  p = PieceToNumber((ChessSquare)p);
9442              if(p >= gameInfo.holdingsSize) p = 0;
9443              if(--board[p][BOARD_WIDTH-2] <= 0)
9444                   board[p][BOARD_WIDTH-1] = EmptySquare;
9445              if((int)board[p][BOARD_WIDTH-2] < 0)
9446                         board[p][BOARD_WIDTH-2] = 0;
9447         } else {                  /* black drop */
9448              p -= (int)BlackPawn;
9449                  p = PieceToNumber((ChessSquare)p);
9450              if(p >= gameInfo.holdingsSize) p = 0;
9451              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9452                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9453              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9454                         board[BOARD_HEIGHT-1-p][1] = 0;
9455         }
9456       }
9457       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9458           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9459         /* [HGM] holdings: Add to holdings, if holdings exist */
9460         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9461                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9462                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9463         }
9464         p = (int) captured;
9465         if (p >= (int) BlackPawn) {
9466           p -= (int)BlackPawn;
9467           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9468                   /* in Shogi restore piece to its original  first */
9469                   captured = (ChessSquare) (DEMOTED captured);
9470                   p = DEMOTED p;
9471           }
9472           p = PieceToNumber((ChessSquare)p);
9473           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9474           board[p][BOARD_WIDTH-2]++;
9475           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9476         } else {
9477           p -= (int)WhitePawn;
9478           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9479                   captured = (ChessSquare) (DEMOTED captured);
9480                   p = DEMOTED p;
9481           }
9482           p = PieceToNumber((ChessSquare)p);
9483           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9484           board[BOARD_HEIGHT-1-p][1]++;
9485           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9486         }
9487       }
9488     } else if (gameInfo.variant == VariantAtomic) {
9489       if (captured != EmptySquare) {
9490         int y, x;
9491         for (y = toY-1; y <= toY+1; y++) {
9492           for (x = toX-1; x <= toX+1; x++) {
9493             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9494                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9495               board[y][x] = EmptySquare;
9496             }
9497           }
9498         }
9499         board[toY][toX] = EmptySquare;
9500       }
9501     }
9502     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9503         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9504     } else
9505     if(promoChar == '+') {
9506         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9507         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9508     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9509         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9510         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9511            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9512         board[toY][toX] = newPiece;
9513     }
9514     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9515                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9516         // [HGM] superchess: take promotion piece out of holdings
9517         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9518         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9519             if(!--board[k][BOARD_WIDTH-2])
9520                 board[k][BOARD_WIDTH-1] = EmptySquare;
9521         } else {
9522             if(!--board[BOARD_HEIGHT-1-k][1])
9523                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9524         }
9525     }
9526
9527 }
9528
9529 /* Updates forwardMostMove */
9530 void
9531 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9532 {
9533 //    forwardMostMove++; // [HGM] bare: moved downstream
9534
9535     (void) CoordsToAlgebraic(boards[forwardMostMove],
9536                              PosFlags(forwardMostMove),
9537                              fromY, fromX, toY, toX, promoChar,
9538                              parseList[forwardMostMove]);
9539
9540     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9541         int timeLeft; static int lastLoadFlag=0; int king, piece;
9542         piece = boards[forwardMostMove][fromY][fromX];
9543         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9544         if(gameInfo.variant == VariantKnightmate)
9545             king += (int) WhiteUnicorn - (int) WhiteKing;
9546         if(forwardMostMove == 0) {
9547             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9548                 fprintf(serverMoves, "%s;", UserName());
9549             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9550                 fprintf(serverMoves, "%s;", second.tidy);
9551             fprintf(serverMoves, "%s;", first.tidy);
9552             if(gameMode == MachinePlaysWhite)
9553                 fprintf(serverMoves, "%s;", UserName());
9554             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9555                 fprintf(serverMoves, "%s;", second.tidy);
9556         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9557         lastLoadFlag = loadFlag;
9558         // print base move
9559         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9560         // print castling suffix
9561         if( toY == fromY && piece == king ) {
9562             if(toX-fromX > 1)
9563                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9564             if(fromX-toX >1)
9565                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9566         }
9567         // e.p. suffix
9568         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9569              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9570              boards[forwardMostMove][toY][toX] == EmptySquare
9571              && fromX != toX && fromY != toY)
9572                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9573         // promotion suffix
9574         if(promoChar != NULLCHAR)
9575                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9576         if(!loadFlag) {
9577                 char buf[MOVE_LEN*2], *p; int len;
9578             fprintf(serverMoves, "/%d/%d",
9579                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9580             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9581             else                      timeLeft = blackTimeRemaining/1000;
9582             fprintf(serverMoves, "/%d", timeLeft);
9583                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9584                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9585                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9586             fprintf(serverMoves, "/%s", buf);
9587         }
9588         fflush(serverMoves);
9589     }
9590
9591     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9592         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9593       return;
9594     }
9595     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9596     if (commentList[forwardMostMove+1] != NULL) {
9597         free(commentList[forwardMostMove+1]);
9598         commentList[forwardMostMove+1] = NULL;
9599     }
9600     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9601     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9602     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9603     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9604     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9605     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9606     adjustedClock = FALSE;
9607     gameInfo.result = GameUnfinished;
9608     if (gameInfo.resultDetails != NULL) {
9609         free(gameInfo.resultDetails);
9610         gameInfo.resultDetails = NULL;
9611     }
9612     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9613                               moveList[forwardMostMove - 1]);
9614     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9615       case MT_NONE:
9616       case MT_STALEMATE:
9617       default:
9618         break;
9619       case MT_CHECK:
9620         if(gameInfo.variant != VariantShogi)
9621             strcat(parseList[forwardMostMove - 1], "+");
9622         break;
9623       case MT_CHECKMATE:
9624       case MT_STAINMATE:
9625         strcat(parseList[forwardMostMove - 1], "#");
9626         break;
9627     }
9628
9629 }
9630
9631 /* Updates currentMove if not pausing */
9632 void
9633 ShowMove (int fromX, int fromY, int toX, int toY)
9634 {
9635     int instant = (gameMode == PlayFromGameFile) ?
9636         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9637     if(appData.noGUI) return;
9638     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9639         if (!instant) {
9640             if (forwardMostMove == currentMove + 1) {
9641                 AnimateMove(boards[forwardMostMove - 1],
9642                             fromX, fromY, toX, toY);
9643             }
9644             if (appData.highlightLastMove) {
9645                 SetHighlights(fromX, fromY, toX, toY);
9646             }
9647         }
9648         currentMove = forwardMostMove;
9649     }
9650
9651     if (instant) return;
9652
9653     DisplayMove(currentMove - 1);
9654     DrawPosition(FALSE, boards[currentMove]);
9655     DisplayBothClocks();
9656     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9657 }
9658
9659 void
9660 SendEgtPath (ChessProgramState *cps)
9661 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9662         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9663
9664         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9665
9666         while(*p) {
9667             char c, *q = name+1, *r, *s;
9668
9669             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9670             while(*p && *p != ',') *q++ = *p++;
9671             *q++ = ':'; *q = 0;
9672             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9673                 strcmp(name, ",nalimov:") == 0 ) {
9674                 // take nalimov path from the menu-changeable option first, if it is defined
9675               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9676                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9677             } else
9678             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9679                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9680                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9681                 s = r = StrStr(s, ":") + 1; // beginning of path info
9682                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9683                 c = *r; *r = 0;             // temporarily null-terminate path info
9684                     *--q = 0;               // strip of trailig ':' from name
9685                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9686                 *r = c;
9687                 SendToProgram(buf,cps);     // send egtbpath command for this format
9688             }
9689             if(*p == ',') p++; // read away comma to position for next format name
9690         }
9691 }
9692
9693 void
9694 InitChessProgram (ChessProgramState *cps, int setup)
9695 /* setup needed to setup FRC opening position */
9696 {
9697     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9698     if (appData.noChessProgram) return;
9699     hintRequested = FALSE;
9700     bookRequested = FALSE;
9701
9702     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9703     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9704     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9705     if(cps->memSize) { /* [HGM] memory */
9706       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9707         SendToProgram(buf, cps);
9708     }
9709     SendEgtPath(cps); /* [HGM] EGT */
9710     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9711       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9712         SendToProgram(buf, cps);
9713     }
9714
9715     SendToProgram(cps->initString, cps);
9716     if (gameInfo.variant != VariantNormal &&
9717         gameInfo.variant != VariantLoadable
9718         /* [HGM] also send variant if board size non-standard */
9719         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9720                                             ) {
9721       char *v = VariantName(gameInfo.variant);
9722       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9723         /* [HGM] in protocol 1 we have to assume all variants valid */
9724         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9725         DisplayFatalError(buf, 0, 1);
9726         return;
9727       }
9728
9729       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9730       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9731       if( gameInfo.variant == VariantXiangqi )
9732            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9733       if( gameInfo.variant == VariantShogi )
9734            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9735       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9736            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9737       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9738           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9739            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9740       if( gameInfo.variant == VariantCourier )
9741            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9742       if( gameInfo.variant == VariantSuper )
9743            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9744       if( gameInfo.variant == VariantGreat )
9745            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9746       if( gameInfo.variant == VariantSChess )
9747            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9748       if( gameInfo.variant == VariantGrand )
9749            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9750
9751       if(overruled) {
9752         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9753                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9754            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9755            if(StrStr(cps->variants, b) == NULL) {
9756                // specific sized variant not known, check if general sizing allowed
9757                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9758                    if(StrStr(cps->variants, "boardsize") == NULL) {
9759                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9760                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9761                        DisplayFatalError(buf, 0, 1);
9762                        return;
9763                    }
9764                    /* [HGM] here we really should compare with the maximum supported board size */
9765                }
9766            }
9767       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9768       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9769       SendToProgram(buf, cps);
9770     }
9771     currentlyInitializedVariant = gameInfo.variant;
9772
9773     /* [HGM] send opening position in FRC to first engine */
9774     if(setup) {
9775           SendToProgram("force\n", cps);
9776           SendBoard(cps, 0);
9777           /* engine is now in force mode! Set flag to wake it up after first move. */
9778           setboardSpoiledMachineBlack = 1;
9779     }
9780
9781     if (cps->sendICS) {
9782       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9783       SendToProgram(buf, cps);
9784     }
9785     cps->maybeThinking = FALSE;
9786     cps->offeredDraw = 0;
9787     if (!appData.icsActive) {
9788         SendTimeControl(cps, movesPerSession, timeControl,
9789                         timeIncrement, appData.searchDepth,
9790                         searchTime);
9791     }
9792     if (appData.showThinking
9793         // [HGM] thinking: four options require thinking output to be sent
9794         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9795                                 ) {
9796         SendToProgram("post\n", cps);
9797     }
9798     SendToProgram("hard\n", cps);
9799     if (!appData.ponderNextMove) {
9800         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9801            it without being sure what state we are in first.  "hard"
9802            is not a toggle, so that one is OK.
9803          */
9804         SendToProgram("easy\n", cps);
9805     }
9806     if (cps->usePing) {
9807       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9808       SendToProgram(buf, cps);
9809     }
9810     cps->initDone = TRUE;
9811     ClearEngineOutputPane(cps == &second);
9812 }
9813
9814
9815 void
9816 StartChessProgram (ChessProgramState *cps)
9817 {
9818     char buf[MSG_SIZ];
9819     int err;
9820
9821     if (appData.noChessProgram) return;
9822     cps->initDone = FALSE;
9823
9824     if (strcmp(cps->host, "localhost") == 0) {
9825         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9826     } else if (*appData.remoteShell == NULLCHAR) {
9827         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9828     } else {
9829         if (*appData.remoteUser == NULLCHAR) {
9830           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9831                     cps->program);
9832         } else {
9833           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9834                     cps->host, appData.remoteUser, cps->program);
9835         }
9836         err = StartChildProcess(buf, "", &cps->pr);
9837     }
9838
9839     if (err != 0) {
9840       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9841         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9842         if(cps != &first) return;
9843         appData.noChessProgram = TRUE;
9844         ThawUI();
9845         SetNCPMode();
9846 //      DisplayFatalError(buf, err, 1);
9847 //      cps->pr = NoProc;
9848 //      cps->isr = NULL;
9849         return;
9850     }
9851
9852     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9853     if (cps->protocolVersion > 1) {
9854       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9855       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9856       cps->comboCnt = 0;  //                and values of combo boxes
9857       SendToProgram(buf, cps);
9858     } else {
9859       SendToProgram("xboard\n", cps);
9860     }
9861 }
9862
9863 void
9864 TwoMachinesEventIfReady P((void))
9865 {
9866   static int curMess = 0;
9867   if (first.lastPing != first.lastPong) {
9868     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9869     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9870     return;
9871   }
9872   if (second.lastPing != second.lastPong) {
9873     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9874     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9875     return;
9876   }
9877   DisplayMessage("", ""); curMess = 0;
9878   ThawUI();
9879   TwoMachinesEvent();
9880 }
9881
9882 char *
9883 MakeName (char *template)
9884 {
9885     time_t clock;
9886     struct tm *tm;
9887     static char buf[MSG_SIZ];
9888     char *p = buf;
9889     int i;
9890
9891     clock = time((time_t *)NULL);
9892     tm = localtime(&clock);
9893
9894     while(*p++ = *template++) if(p[-1] == '%') {
9895         switch(*template++) {
9896           case 0:   *p = 0; return buf;
9897           case 'Y': i = tm->tm_year+1900; break;
9898           case 'y': i = tm->tm_year-100; break;
9899           case 'M': i = tm->tm_mon+1; break;
9900           case 'd': i = tm->tm_mday; break;
9901           case 'h': i = tm->tm_hour; break;
9902           case 'm': i = tm->tm_min; break;
9903           case 's': i = tm->tm_sec; break;
9904           default:  i = 0;
9905         }
9906         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9907     }
9908     return buf;
9909 }
9910
9911 int
9912 CountPlayers (char *p)
9913 {
9914     int n = 0;
9915     while(p = strchr(p, '\n')) p++, n++; // count participants
9916     return n;
9917 }
9918
9919 FILE *
9920 WriteTourneyFile (char *results, FILE *f)
9921 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9922     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9923     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9924         // create a file with tournament description
9925         fprintf(f, "-participants {%s}\n", appData.participants);
9926         fprintf(f, "-seedBase %d\n", appData.seedBase);
9927         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9928         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9929         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9930         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9931         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9932         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9933         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9934         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9935         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9936         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9937         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9938         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9939         if(searchTime > 0)
9940                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9941         else {
9942                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9943                 fprintf(f, "-tc %s\n", appData.timeControl);
9944                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9945         }
9946         fprintf(f, "-results \"%s\"\n", results);
9947     }
9948     return f;
9949 }
9950
9951 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9952
9953 void
9954 Substitute (char *participants, int expunge)
9955 {
9956     int i, changed, changes=0, nPlayers=0;
9957     char *p, *q, *r, buf[MSG_SIZ];
9958     if(participants == NULL) return;
9959     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9960     r = p = participants; q = appData.participants;
9961     while(*p && *p == *q) {
9962         if(*p == '\n') r = p+1, nPlayers++;
9963         p++; q++;
9964     }
9965     if(*p) { // difference
9966         while(*p && *p++ != '\n');
9967         while(*q && *q++ != '\n');
9968       changed = nPlayers;
9969         changes = 1 + (strcmp(p, q) != 0);
9970     }
9971     if(changes == 1) { // a single engine mnemonic was changed
9972         q = r; while(*q) nPlayers += (*q++ == '\n');
9973         p = buf; while(*r && (*p = *r++) != '\n') p++;
9974         *p = NULLCHAR;
9975         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9976         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9977         if(mnemonic[i]) { // The substitute is valid
9978             FILE *f;
9979             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9980                 flock(fileno(f), LOCK_EX);
9981                 ParseArgsFromFile(f);
9982                 fseek(f, 0, SEEK_SET);
9983                 FREE(appData.participants); appData.participants = participants;
9984                 if(expunge) { // erase results of replaced engine
9985                     int len = strlen(appData.results), w, b, dummy;
9986                     for(i=0; i<len; i++) {
9987                         Pairing(i, nPlayers, &w, &b, &dummy);
9988                         if((w == changed || b == changed) && appData.results[i] == '*') {
9989                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9990                             fclose(f);
9991                             return;
9992                         }
9993                     }
9994                     for(i=0; i<len; i++) {
9995                         Pairing(i, nPlayers, &w, &b, &dummy);
9996                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9997                     }
9998                 }
9999                 WriteTourneyFile(appData.results, f);
10000                 fclose(f); // release lock
10001                 return;
10002             }
10003         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10004     }
10005     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10006     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10007     free(participants);
10008     return;
10009 }
10010
10011 int
10012 CreateTourney (char *name)
10013 {
10014         FILE *f;
10015         if(matchMode && strcmp(name, appData.tourneyFile)) {
10016              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10017         }
10018         if(name[0] == NULLCHAR) {
10019             if(appData.participants[0])
10020                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10021             return 0;
10022         }
10023         f = fopen(name, "r");
10024         if(f) { // file exists
10025             ASSIGN(appData.tourneyFile, name);
10026             ParseArgsFromFile(f); // parse it
10027         } else {
10028             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10029             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10030                 DisplayError(_("Not enough participants"), 0);
10031                 return 0;
10032             }
10033             ASSIGN(appData.tourneyFile, name);
10034             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10035             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10036         }
10037         fclose(f);
10038         appData.noChessProgram = FALSE;
10039         appData.clockMode = TRUE;
10040         SetGNUMode();
10041         return 1;
10042 }
10043
10044 int
10045 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10046 {
10047     char buf[MSG_SIZ], *p, *q;
10048     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10049     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10050     skip = !all && group[0]; // if group requested, we start in skip mode
10051     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10052         p = names; q = buf; header = 0;
10053         while(*p && *p != '\n') *q++ = *p++;
10054         *q = 0;
10055         if(*p == '\n') p++;
10056         if(buf[0] == '#') {
10057             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10058             depth++; // we must be entering a new group
10059             if(all) continue; // suppress printing group headers when complete list requested
10060             header = 1;
10061             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10062         }
10063         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10064         if(engineList[i]) free(engineList[i]);
10065         engineList[i] = strdup(buf);
10066         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10067         if(engineMnemonic[i]) free(engineMnemonic[i]);
10068         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10069             strcat(buf, " (");
10070             sscanf(q + 8, "%s", buf + strlen(buf));
10071             strcat(buf, ")");
10072         }
10073         engineMnemonic[i] = strdup(buf);
10074         i++;
10075     }
10076     engineList[i] = engineMnemonic[i] = NULL;
10077     return i;
10078 }
10079
10080 // following implemented as macro to avoid type limitations
10081 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10082
10083 void
10084 SwapEngines (int n)
10085 {   // swap settings for first engine and other engine (so far only some selected options)
10086     int h;
10087     char *p;
10088     if(n == 0) return;
10089     SWAP(directory, p)
10090     SWAP(chessProgram, p)
10091     SWAP(isUCI, h)
10092     SWAP(hasOwnBookUCI, h)
10093     SWAP(protocolVersion, h)
10094     SWAP(reuse, h)
10095     SWAP(scoreIsAbsolute, h)
10096     SWAP(timeOdds, h)
10097     SWAP(logo, p)
10098     SWAP(pgnName, p)
10099     SWAP(pvSAN, h)
10100     SWAP(engOptions, p)
10101     SWAP(engInitString, p)
10102     SWAP(computerString, p)
10103     SWAP(features, p)
10104     SWAP(fenOverride, p)
10105     SWAP(NPS, h)
10106     SWAP(accumulateTC, h)
10107     SWAP(host, p)
10108 }
10109
10110 int
10111 SetPlayer (int player, char *p)
10112 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10113     int i;
10114     char buf[MSG_SIZ], *engineName;
10115     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10116     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10117     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10118     if(mnemonic[i]) {
10119         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10120         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10121         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10122         ParseArgsFromString(buf);
10123     }
10124     free(engineName);
10125     return i;
10126 }
10127
10128 char *recentEngines;
10129
10130 void
10131 RecentEngineEvent (int nr)
10132 {
10133     int n;
10134 //    SwapEngines(1); // bump first to second
10135 //    ReplaceEngine(&second, 1); // and load it there
10136     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10137     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10138     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10139         ReplaceEngine(&first, 0);
10140         FloatToFront(&appData.recentEngineList, command[n]);
10141     }
10142 }
10143
10144 int
10145 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10146 {   // determine players from game number
10147     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10148
10149     if(appData.tourneyType == 0) {
10150         roundsPerCycle = (nPlayers - 1) | 1;
10151         pairingsPerRound = nPlayers / 2;
10152     } else if(appData.tourneyType > 0) {
10153         roundsPerCycle = nPlayers - appData.tourneyType;
10154         pairingsPerRound = appData.tourneyType;
10155     }
10156     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10157     gamesPerCycle = gamesPerRound * roundsPerCycle;
10158     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10159     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10160     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10161     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10162     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10163     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10164
10165     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10166     if(appData.roundSync) *syncInterval = gamesPerRound;
10167
10168     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10169
10170     if(appData.tourneyType == 0) {
10171         if(curPairing == (nPlayers-1)/2 ) {
10172             *whitePlayer = curRound;
10173             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10174         } else {
10175             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10176             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10177             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10178             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10179         }
10180     } else if(appData.tourneyType > 1) {
10181         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10182         *whitePlayer = curRound + appData.tourneyType;
10183     } else if(appData.tourneyType > 0) {
10184         *whitePlayer = curPairing;
10185         *blackPlayer = curRound + appData.tourneyType;
10186     }
10187
10188     // take care of white/black alternation per round. 
10189     // For cycles and games this is already taken care of by default, derived from matchGame!
10190     return curRound & 1;
10191 }
10192
10193 int
10194 NextTourneyGame (int nr, int *swapColors)
10195 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10196     char *p, *q;
10197     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10198     FILE *tf;
10199     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10200     tf = fopen(appData.tourneyFile, "r");
10201     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10202     ParseArgsFromFile(tf); fclose(tf);
10203     InitTimeControls(); // TC might be altered from tourney file
10204
10205     nPlayers = CountPlayers(appData.participants); // count participants
10206     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10207     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10208
10209     if(syncInterval) {
10210         p = q = appData.results;
10211         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10212         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10213             DisplayMessage(_("Waiting for other game(s)"),"");
10214             waitingForGame = TRUE;
10215             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10216             return 0;
10217         }
10218         waitingForGame = FALSE;
10219     }
10220
10221     if(appData.tourneyType < 0) {
10222         if(nr>=0 && !pairingReceived) {
10223             char buf[1<<16];
10224             if(pairing.pr == NoProc) {
10225                 if(!appData.pairingEngine[0]) {
10226                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10227                     return 0;
10228                 }
10229                 StartChessProgram(&pairing); // starts the pairing engine
10230             }
10231             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10232             SendToProgram(buf, &pairing);
10233             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10234             SendToProgram(buf, &pairing);
10235             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10236         }
10237         pairingReceived = 0;                              // ... so we continue here 
10238         *swapColors = 0;
10239         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10240         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10241         matchGame = 1; roundNr = nr / syncInterval + 1;
10242     }
10243
10244     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10245
10246     // redefine engines, engine dir, etc.
10247     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10248     if(first.pr == NoProc) {
10249       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10250       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10251     }
10252     if(second.pr == NoProc) {
10253       SwapEngines(1);
10254       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10255       SwapEngines(1);         // and make that valid for second engine by swapping
10256       InitEngine(&second, 1);
10257     }
10258     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10259     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10260     return 1;
10261 }
10262
10263 void
10264 NextMatchGame ()
10265 {   // performs game initialization that does not invoke engines, and then tries to start the game
10266     int res, firstWhite, swapColors = 0;
10267     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10268     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
10269         char buf[MSG_SIZ];
10270         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10271         if(strcmp(buf, currentDebugFile)) { // name has changed
10272             FILE *f = fopen(buf, "w");
10273             if(f) { // if opening the new file failed, just keep using the old one
10274                 ASSIGN(currentDebugFile, buf);
10275                 fclose(debugFP);
10276                 debugFP = f;
10277             }
10278             if(appData.serverFileName) {
10279                 if(serverFP) fclose(serverFP);
10280                 serverFP = fopen(appData.serverFileName, "w");
10281                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10282                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10283             }
10284         }
10285     }
10286     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10287     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10288     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10289     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10290     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10291     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10292     Reset(FALSE, first.pr != NoProc);
10293     res = LoadGameOrPosition(matchGame); // setup game
10294     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10295     if(!res) return; // abort when bad game/pos file
10296     TwoMachinesEvent();
10297 }
10298
10299 void
10300 UserAdjudicationEvent (int result)
10301 {
10302     ChessMove gameResult = GameIsDrawn;
10303
10304     if( result > 0 ) {
10305         gameResult = WhiteWins;
10306     }
10307     else if( result < 0 ) {
10308         gameResult = BlackWins;
10309     }
10310
10311     if( gameMode == TwoMachinesPlay ) {
10312         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10313     }
10314 }
10315
10316
10317 // [HGM] save: calculate checksum of game to make games easily identifiable
10318 int
10319 StringCheckSum (char *s)
10320 {
10321         int i = 0;
10322         if(s==NULL) return 0;
10323         while(*s) i = i*259 + *s++;
10324         return i;
10325 }
10326
10327 int
10328 GameCheckSum ()
10329 {
10330         int i, sum=0;
10331         for(i=backwardMostMove; i<forwardMostMove; i++) {
10332                 sum += pvInfoList[i].depth;
10333                 sum += StringCheckSum(parseList[i]);
10334                 sum += StringCheckSum(commentList[i]);
10335                 sum *= 261;
10336         }
10337         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10338         return sum + StringCheckSum(commentList[i]);
10339 } // end of save patch
10340
10341 void
10342 GameEnds (ChessMove result, char *resultDetails, int whosays)
10343 {
10344     GameMode nextGameMode;
10345     int isIcsGame;
10346     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10347
10348     if(endingGame) return; /* [HGM] crash: forbid recursion */
10349     endingGame = 1;
10350     if(twoBoards) { // [HGM] dual: switch back to one board
10351         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10352         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10353     }
10354     if (appData.debugMode) {
10355       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10356               result, resultDetails ? resultDetails : "(null)", whosays);
10357     }
10358
10359     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10360
10361     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10362         /* If we are playing on ICS, the server decides when the
10363            game is over, but the engine can offer to draw, claim
10364            a draw, or resign.
10365          */
10366 #if ZIPPY
10367         if (appData.zippyPlay && first.initDone) {
10368             if (result == GameIsDrawn) {
10369                 /* In case draw still needs to be claimed */
10370                 SendToICS(ics_prefix);
10371                 SendToICS("draw\n");
10372             } else if (StrCaseStr(resultDetails, "resign")) {
10373                 SendToICS(ics_prefix);
10374                 SendToICS("resign\n");
10375             }
10376         }
10377 #endif
10378         endingGame = 0; /* [HGM] crash */
10379         return;
10380     }
10381
10382     /* If we're loading the game from a file, stop */
10383     if (whosays == GE_FILE) {
10384       (void) StopLoadGameTimer();
10385       gameFileFP = NULL;
10386     }
10387
10388     /* Cancel draw offers */
10389     first.offeredDraw = second.offeredDraw = 0;
10390
10391     /* If this is an ICS game, only ICS can really say it's done;
10392        if not, anyone can. */
10393     isIcsGame = (gameMode == IcsPlayingWhite ||
10394                  gameMode == IcsPlayingBlack ||
10395                  gameMode == IcsObserving    ||
10396                  gameMode == IcsExamining);
10397
10398     if (!isIcsGame || whosays == GE_ICS) {
10399         /* OK -- not an ICS game, or ICS said it was done */
10400         StopClocks();
10401         if (!isIcsGame && !appData.noChessProgram)
10402           SetUserThinkingEnables();
10403
10404         /* [HGM] if a machine claims the game end we verify this claim */
10405         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10406             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10407                 char claimer;
10408                 ChessMove trueResult = (ChessMove) -1;
10409
10410                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10411                                             first.twoMachinesColor[0] :
10412                                             second.twoMachinesColor[0] ;
10413
10414                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10415                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10416                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10417                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10418                 } else
10419                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10420                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10421                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10422                 } else
10423                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10424                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10425                 }
10426
10427                 // now verify win claims, but not in drop games, as we don't understand those yet
10428                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10429                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10430                     (result == WhiteWins && claimer == 'w' ||
10431                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10432                       if (appData.debugMode) {
10433                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10434                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10435                       }
10436                       if(result != trueResult) {
10437                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10438                               result = claimer == 'w' ? BlackWins : WhiteWins;
10439                               resultDetails = buf;
10440                       }
10441                 } else
10442                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10443                     && (forwardMostMove <= backwardMostMove ||
10444                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10445                         (claimer=='b')==(forwardMostMove&1))
10446                                                                                   ) {
10447                       /* [HGM] verify: draws that were not flagged are false claims */
10448                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10449                       result = claimer == 'w' ? BlackWins : WhiteWins;
10450                       resultDetails = buf;
10451                 }
10452                 /* (Claiming a loss is accepted no questions asked!) */
10453             }
10454             /* [HGM] bare: don't allow bare King to win */
10455             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10456                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10457                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10458                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10459                && result != GameIsDrawn)
10460             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10461                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10462                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10463                         if(p >= 0 && p <= (int)WhiteKing) k++;
10464                 }
10465                 if (appData.debugMode) {
10466                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10467                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10468                 }
10469                 if(k <= 1) {
10470                         result = GameIsDrawn;
10471                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10472                         resultDetails = buf;
10473                 }
10474             }
10475         }
10476
10477
10478         if(serverMoves != NULL && !loadFlag) { char c = '=';
10479             if(result==WhiteWins) c = '+';
10480             if(result==BlackWins) c = '-';
10481             if(resultDetails != NULL)
10482                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10483         }
10484         if (resultDetails != NULL) {
10485             gameInfo.result = result;
10486             gameInfo.resultDetails = StrSave(resultDetails);
10487
10488             /* display last move only if game was not loaded from file */
10489             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10490                 DisplayMove(currentMove - 1);
10491
10492             if (forwardMostMove != 0) {
10493                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10494                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10495                                                                 ) {
10496                     if (*appData.saveGameFile != NULLCHAR) {
10497                         SaveGameToFile(appData.saveGameFile, TRUE);
10498                     } else if (appData.autoSaveGames) {
10499                         AutoSaveGame();
10500                     }
10501                     if (*appData.savePositionFile != NULLCHAR) {
10502                         SavePositionToFile(appData.savePositionFile);
10503                     }
10504                 }
10505             }
10506
10507             /* Tell program how game ended in case it is learning */
10508             /* [HGM] Moved this to after saving the PGN, just in case */
10509             /* engine died and we got here through time loss. In that */
10510             /* case we will get a fatal error writing the pipe, which */
10511             /* would otherwise lose us the PGN.                       */
10512             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10513             /* output during GameEnds should never be fatal anymore   */
10514             if (gameMode == MachinePlaysWhite ||
10515                 gameMode == MachinePlaysBlack ||
10516                 gameMode == TwoMachinesPlay ||
10517                 gameMode == IcsPlayingWhite ||
10518                 gameMode == IcsPlayingBlack ||
10519                 gameMode == BeginningOfGame) {
10520                 char buf[MSG_SIZ];
10521                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10522                         resultDetails);
10523                 if (first.pr != NoProc) {
10524                     SendToProgram(buf, &first);
10525                 }
10526                 if (second.pr != NoProc &&
10527                     gameMode == TwoMachinesPlay) {
10528                     SendToProgram(buf, &second);
10529                 }
10530             }
10531         }
10532
10533         if (appData.icsActive) {
10534             if (appData.quietPlay &&
10535                 (gameMode == IcsPlayingWhite ||
10536                  gameMode == IcsPlayingBlack)) {
10537                 SendToICS(ics_prefix);
10538                 SendToICS("set shout 1\n");
10539             }
10540             nextGameMode = IcsIdle;
10541             ics_user_moved = FALSE;
10542             /* clean up premove.  It's ugly when the game has ended and the
10543              * premove highlights are still on the board.
10544              */
10545             if (gotPremove) {
10546               gotPremove = FALSE;
10547               ClearPremoveHighlights();
10548               DrawPosition(FALSE, boards[currentMove]);
10549             }
10550             if (whosays == GE_ICS) {
10551                 switch (result) {
10552                 case WhiteWins:
10553                     if (gameMode == IcsPlayingWhite)
10554                         PlayIcsWinSound();
10555                     else if(gameMode == IcsPlayingBlack)
10556                         PlayIcsLossSound();
10557                     break;
10558                 case BlackWins:
10559                     if (gameMode == IcsPlayingBlack)
10560                         PlayIcsWinSound();
10561                     else if(gameMode == IcsPlayingWhite)
10562                         PlayIcsLossSound();
10563                     break;
10564                 case GameIsDrawn:
10565                     PlayIcsDrawSound();
10566                     break;
10567                 default:
10568                     PlayIcsUnfinishedSound();
10569                 }
10570             }
10571         } else if (gameMode == EditGame ||
10572                    gameMode == PlayFromGameFile ||
10573                    gameMode == AnalyzeMode ||
10574                    gameMode == AnalyzeFile) {
10575             nextGameMode = gameMode;
10576         } else {
10577             nextGameMode = EndOfGame;
10578         }
10579         pausing = FALSE;
10580         ModeHighlight();
10581     } else {
10582         nextGameMode = gameMode;
10583     }
10584
10585     if (appData.noChessProgram) {
10586         gameMode = nextGameMode;
10587         ModeHighlight();
10588         endingGame = 0; /* [HGM] crash */
10589         return;
10590     }
10591
10592     if (first.reuse) {
10593         /* Put first chess program into idle state */
10594         if (first.pr != NoProc &&
10595             (gameMode == MachinePlaysWhite ||
10596              gameMode == MachinePlaysBlack ||
10597              gameMode == TwoMachinesPlay ||
10598              gameMode == IcsPlayingWhite ||
10599              gameMode == IcsPlayingBlack ||
10600              gameMode == BeginningOfGame)) {
10601             SendToProgram("force\n", &first);
10602             if (first.usePing) {
10603               char buf[MSG_SIZ];
10604               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10605               SendToProgram(buf, &first);
10606             }
10607         }
10608     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10609         /* Kill off first chess program */
10610         if (first.isr != NULL)
10611           RemoveInputSource(first.isr);
10612         first.isr = NULL;
10613
10614         if (first.pr != NoProc) {
10615             ExitAnalyzeMode();
10616             DoSleep( appData.delayBeforeQuit );
10617             SendToProgram("quit\n", &first);
10618             DoSleep( appData.delayAfterQuit );
10619             DestroyChildProcess(first.pr, first.useSigterm);
10620         }
10621         first.pr = NoProc;
10622     }
10623     if (second.reuse) {
10624         /* Put second chess program into idle state */
10625         if (second.pr != NoProc &&
10626             gameMode == TwoMachinesPlay) {
10627             SendToProgram("force\n", &second);
10628             if (second.usePing) {
10629               char buf[MSG_SIZ];
10630               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10631               SendToProgram(buf, &second);
10632             }
10633         }
10634     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10635         /* Kill off second chess program */
10636         if (second.isr != NULL)
10637           RemoveInputSource(second.isr);
10638         second.isr = NULL;
10639
10640         if (second.pr != NoProc) {
10641             DoSleep( appData.delayBeforeQuit );
10642             SendToProgram("quit\n", &second);
10643             DoSleep( appData.delayAfterQuit );
10644             DestroyChildProcess(second.pr, second.useSigterm);
10645         }
10646         second.pr = NoProc;
10647     }
10648
10649     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10650         char resChar = '=';
10651         switch (result) {
10652         case WhiteWins:
10653           resChar = '+';
10654           if (first.twoMachinesColor[0] == 'w') {
10655             first.matchWins++;
10656           } else {
10657             second.matchWins++;
10658           }
10659           break;
10660         case BlackWins:
10661           resChar = '-';
10662           if (first.twoMachinesColor[0] == 'b') {
10663             first.matchWins++;
10664           } else {
10665             second.matchWins++;
10666           }
10667           break;
10668         case GameUnfinished:
10669           resChar = ' ';
10670         default:
10671           break;
10672         }
10673
10674         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10675         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10676             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10677             ReserveGame(nextGame, resChar); // sets nextGame
10678             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10679             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10680         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10681
10682         if (nextGame <= appData.matchGames && !abortMatch) {
10683             gameMode = nextGameMode;
10684             matchGame = nextGame; // this will be overruled in tourney mode!
10685             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10686             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10687             endingGame = 0; /* [HGM] crash */
10688             return;
10689         } else {
10690             gameMode = nextGameMode;
10691             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10692                      first.tidy, second.tidy,
10693                      first.matchWins, second.matchWins,
10694                      appData.matchGames - (first.matchWins + second.matchWins));
10695             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10696             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10697             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10698             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10699                 first.twoMachinesColor = "black\n";
10700                 second.twoMachinesColor = "white\n";
10701             } else {
10702                 first.twoMachinesColor = "white\n";
10703                 second.twoMachinesColor = "black\n";
10704             }
10705         }
10706     }
10707     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10708         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10709       ExitAnalyzeMode();
10710     gameMode = nextGameMode;
10711     ModeHighlight();
10712     endingGame = 0;  /* [HGM] crash */
10713     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10714         if(matchMode == TRUE) { // match through command line: exit with or without popup
10715             if(ranking) {
10716                 ToNrEvent(forwardMostMove);
10717                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10718                 else ExitEvent(0);
10719             } else DisplayFatalError(buf, 0, 0);
10720         } else { // match through menu; just stop, with or without popup
10721             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10722             ModeHighlight();
10723             if(ranking){
10724                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10725             } else DisplayNote(buf);
10726       }
10727       if(ranking) free(ranking);
10728     }
10729 }
10730
10731 /* Assumes program was just initialized (initString sent).
10732    Leaves program in force mode. */
10733 void
10734 FeedMovesToProgram (ChessProgramState *cps, int upto)
10735 {
10736     int i;
10737
10738     if (appData.debugMode)
10739       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10740               startedFromSetupPosition ? "position and " : "",
10741               backwardMostMove, upto, cps->which);
10742     if(currentlyInitializedVariant != gameInfo.variant) {
10743       char buf[MSG_SIZ];
10744         // [HGM] variantswitch: make engine aware of new variant
10745         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10746                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10747         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10748         SendToProgram(buf, cps);
10749         currentlyInitializedVariant = gameInfo.variant;
10750     }
10751     SendToProgram("force\n", cps);
10752     if (startedFromSetupPosition) {
10753         SendBoard(cps, backwardMostMove);
10754     if (appData.debugMode) {
10755         fprintf(debugFP, "feedMoves\n");
10756     }
10757     }
10758     for (i = backwardMostMove; i < upto; i++) {
10759         SendMoveToProgram(i, cps);
10760     }
10761 }
10762
10763
10764 int
10765 ResurrectChessProgram ()
10766 {
10767      /* The chess program may have exited.
10768         If so, restart it and feed it all the moves made so far. */
10769     static int doInit = 0;
10770
10771     if (appData.noChessProgram) return 1;
10772
10773     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10774         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10775         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10776         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10777     } else {
10778         if (first.pr != NoProc) return 1;
10779         StartChessProgram(&first);
10780     }
10781     InitChessProgram(&first, FALSE);
10782     FeedMovesToProgram(&first, currentMove);
10783
10784     if (!first.sendTime) {
10785         /* can't tell gnuchess what its clock should read,
10786            so we bow to its notion. */
10787         ResetClocks();
10788         timeRemaining[0][currentMove] = whiteTimeRemaining;
10789         timeRemaining[1][currentMove] = blackTimeRemaining;
10790     }
10791
10792     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10793                 appData.icsEngineAnalyze) && first.analysisSupport) {
10794       SendToProgram("analyze\n", &first);
10795       first.analyzing = TRUE;
10796     }
10797     return 1;
10798 }
10799
10800 /*
10801  * Button procedures
10802  */
10803 void
10804 Reset (int redraw, int init)
10805 {
10806     int i;
10807
10808     if (appData.debugMode) {
10809         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10810                 redraw, init, gameMode);
10811     }
10812     CleanupTail(); // [HGM] vari: delete any stored variations
10813     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10814     pausing = pauseExamInvalid = FALSE;
10815     startedFromSetupPosition = blackPlaysFirst = FALSE;
10816     firstMove = TRUE;
10817     whiteFlag = blackFlag = FALSE;
10818     userOfferedDraw = FALSE;
10819     hintRequested = bookRequested = FALSE;
10820     first.maybeThinking = FALSE;
10821     second.maybeThinking = FALSE;
10822     first.bookSuspend = FALSE; // [HGM] book
10823     second.bookSuspend = FALSE;
10824     thinkOutput[0] = NULLCHAR;
10825     lastHint[0] = NULLCHAR;
10826     ClearGameInfo(&gameInfo);
10827     gameInfo.variant = StringToVariant(appData.variant);
10828     ics_user_moved = ics_clock_paused = FALSE;
10829     ics_getting_history = H_FALSE;
10830     ics_gamenum = -1;
10831     white_holding[0] = black_holding[0] = NULLCHAR;
10832     ClearProgramStats();
10833     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10834
10835     ResetFrontEnd();
10836     ClearHighlights();
10837     flipView = appData.flipView;
10838     ClearPremoveHighlights();
10839     gotPremove = FALSE;
10840     alarmSounded = FALSE;
10841
10842     GameEnds(EndOfFile, NULL, GE_PLAYER);
10843     if(appData.serverMovesName != NULL) {
10844         /* [HGM] prepare to make moves file for broadcasting */
10845         clock_t t = clock();
10846         if(serverMoves != NULL) fclose(serverMoves);
10847         serverMoves = fopen(appData.serverMovesName, "r");
10848         if(serverMoves != NULL) {
10849             fclose(serverMoves);
10850             /* delay 15 sec before overwriting, so all clients can see end */
10851             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10852         }
10853         serverMoves = fopen(appData.serverMovesName, "w");
10854     }
10855
10856     ExitAnalyzeMode();
10857     gameMode = BeginningOfGame;
10858     ModeHighlight();
10859     if(appData.icsActive) gameInfo.variant = VariantNormal;
10860     currentMove = forwardMostMove = backwardMostMove = 0;
10861     MarkTargetSquares(1);
10862     InitPosition(redraw);
10863     for (i = 0; i < MAX_MOVES; i++) {
10864         if (commentList[i] != NULL) {
10865             free(commentList[i]);
10866             commentList[i] = NULL;
10867         }
10868     }
10869     ResetClocks();
10870     timeRemaining[0][0] = whiteTimeRemaining;
10871     timeRemaining[1][0] = blackTimeRemaining;
10872
10873     if (first.pr == NoProc) {
10874         StartChessProgram(&first);
10875     }
10876     if (init) {
10877             InitChessProgram(&first, startedFromSetupPosition);
10878     }
10879     DisplayTitle("");
10880     DisplayMessage("", "");
10881     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10882     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10883     ClearMap();        // [HGM] exclude: invalidate map
10884 }
10885
10886 void
10887 AutoPlayGameLoop ()
10888 {
10889     for (;;) {
10890         if (!AutoPlayOneMove())
10891           return;
10892         if (matchMode || appData.timeDelay == 0)
10893           continue;
10894         if (appData.timeDelay < 0)
10895           return;
10896         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10897         break;
10898     }
10899 }
10900
10901
10902 int
10903 AutoPlayOneMove ()
10904 {
10905     int fromX, fromY, toX, toY;
10906
10907     if (appData.debugMode) {
10908       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10909     }
10910
10911     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10912       return FALSE;
10913
10914     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10915       pvInfoList[currentMove].depth = programStats.depth;
10916       pvInfoList[currentMove].score = programStats.score;
10917       pvInfoList[currentMove].time  = 0;
10918       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10919     }
10920
10921     if (currentMove >= forwardMostMove) {
10922       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10923 //      gameMode = EndOfGame;
10924 //      ModeHighlight();
10925
10926       /* [AS] Clear current move marker at the end of a game */
10927       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10928
10929       return FALSE;
10930     }
10931
10932     toX = moveList[currentMove][2] - AAA;
10933     toY = moveList[currentMove][3] - ONE;
10934
10935     if (moveList[currentMove][1] == '@') {
10936         if (appData.highlightLastMove) {
10937             SetHighlights(-1, -1, toX, toY);
10938         }
10939     } else {
10940         fromX = moveList[currentMove][0] - AAA;
10941         fromY = moveList[currentMove][1] - ONE;
10942
10943         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10944
10945         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10946
10947         if (appData.highlightLastMove) {
10948             SetHighlights(fromX, fromY, toX, toY);
10949         }
10950     }
10951     DisplayMove(currentMove);
10952     SendMoveToProgram(currentMove++, &first);
10953     DisplayBothClocks();
10954     DrawPosition(FALSE, boards[currentMove]);
10955     // [HGM] PV info: always display, routine tests if empty
10956     DisplayComment(currentMove - 1, commentList[currentMove]);
10957     return TRUE;
10958 }
10959
10960
10961 int
10962 LoadGameOneMove (ChessMove readAhead)
10963 {
10964     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10965     char promoChar = NULLCHAR;
10966     ChessMove moveType;
10967     char move[MSG_SIZ];
10968     char *p, *q;
10969
10970     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10971         gameMode != AnalyzeMode && gameMode != Training) {
10972         gameFileFP = NULL;
10973         return FALSE;
10974     }
10975
10976     yyboardindex = forwardMostMove;
10977     if (readAhead != EndOfFile) {
10978       moveType = readAhead;
10979     } else {
10980       if (gameFileFP == NULL)
10981           return FALSE;
10982       moveType = (ChessMove) Myylex();
10983     }
10984
10985     done = FALSE;
10986     switch (moveType) {
10987       case Comment:
10988         if (appData.debugMode)
10989           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10990         p = yy_text;
10991
10992         /* append the comment but don't display it */
10993         AppendComment(currentMove, p, FALSE);
10994         return TRUE;
10995
10996       case WhiteCapturesEnPassant:
10997       case BlackCapturesEnPassant:
10998       case WhitePromotion:
10999       case BlackPromotion:
11000       case WhiteNonPromotion:
11001       case BlackNonPromotion:
11002       case NormalMove:
11003       case WhiteKingSideCastle:
11004       case WhiteQueenSideCastle:
11005       case BlackKingSideCastle:
11006       case BlackQueenSideCastle:
11007       case WhiteKingSideCastleWild:
11008       case WhiteQueenSideCastleWild:
11009       case BlackKingSideCastleWild:
11010       case BlackQueenSideCastleWild:
11011       /* PUSH Fabien */
11012       case WhiteHSideCastleFR:
11013       case WhiteASideCastleFR:
11014       case BlackHSideCastleFR:
11015       case BlackASideCastleFR:
11016       /* POP Fabien */
11017         if (appData.debugMode)
11018           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11019         fromX = currentMoveString[0] - AAA;
11020         fromY = currentMoveString[1] - ONE;
11021         toX = currentMoveString[2] - AAA;
11022         toY = currentMoveString[3] - ONE;
11023         promoChar = currentMoveString[4];
11024         break;
11025
11026       case WhiteDrop:
11027       case BlackDrop:
11028         if (appData.debugMode)
11029           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11030         fromX = moveType == WhiteDrop ?
11031           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11032         (int) CharToPiece(ToLower(currentMoveString[0]));
11033         fromY = DROP_RANK;
11034         toX = currentMoveString[2] - AAA;
11035         toY = currentMoveString[3] - ONE;
11036         break;
11037
11038       case WhiteWins:
11039       case BlackWins:
11040       case GameIsDrawn:
11041       case GameUnfinished:
11042         if (appData.debugMode)
11043           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11044         p = strchr(yy_text, '{');
11045         if (p == NULL) p = strchr(yy_text, '(');
11046         if (p == NULL) {
11047             p = yy_text;
11048             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11049         } else {
11050             q = strchr(p, *p == '{' ? '}' : ')');
11051             if (q != NULL) *q = NULLCHAR;
11052             p++;
11053         }
11054         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11055         GameEnds(moveType, p, GE_FILE);
11056         done = TRUE;
11057         if (cmailMsgLoaded) {
11058             ClearHighlights();
11059             flipView = WhiteOnMove(currentMove);
11060             if (moveType == GameUnfinished) flipView = !flipView;
11061             if (appData.debugMode)
11062               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11063         }
11064         break;
11065
11066       case EndOfFile:
11067         if (appData.debugMode)
11068           fprintf(debugFP, "Parser hit end of file\n");
11069         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11070           case MT_NONE:
11071           case MT_CHECK:
11072             break;
11073           case MT_CHECKMATE:
11074           case MT_STAINMATE:
11075             if (WhiteOnMove(currentMove)) {
11076                 GameEnds(BlackWins, "Black mates", GE_FILE);
11077             } else {
11078                 GameEnds(WhiteWins, "White mates", GE_FILE);
11079             }
11080             break;
11081           case MT_STALEMATE:
11082             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11083             break;
11084         }
11085         done = TRUE;
11086         break;
11087
11088       case MoveNumberOne:
11089         if (lastLoadGameStart == GNUChessGame) {
11090             /* GNUChessGames have numbers, but they aren't move numbers */
11091             if (appData.debugMode)
11092               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11093                       yy_text, (int) moveType);
11094             return LoadGameOneMove(EndOfFile); /* tail recursion */
11095         }
11096         /* else fall thru */
11097
11098       case XBoardGame:
11099       case GNUChessGame:
11100       case PGNTag:
11101         /* Reached start of next game in file */
11102         if (appData.debugMode)
11103           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11104         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11105           case MT_NONE:
11106           case MT_CHECK:
11107             break;
11108           case MT_CHECKMATE:
11109           case MT_STAINMATE:
11110             if (WhiteOnMove(currentMove)) {
11111                 GameEnds(BlackWins, "Black mates", GE_FILE);
11112             } else {
11113                 GameEnds(WhiteWins, "White mates", GE_FILE);
11114             }
11115             break;
11116           case MT_STALEMATE:
11117             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11118             break;
11119         }
11120         done = TRUE;
11121         break;
11122
11123       case PositionDiagram:     /* should not happen; ignore */
11124       case ElapsedTime:         /* ignore */
11125       case NAG:                 /* ignore */
11126         if (appData.debugMode)
11127           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11128                   yy_text, (int) moveType);
11129         return LoadGameOneMove(EndOfFile); /* tail recursion */
11130
11131       case IllegalMove:
11132         if (appData.testLegality) {
11133             if (appData.debugMode)
11134               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11135             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11136                     (forwardMostMove / 2) + 1,
11137                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11138             DisplayError(move, 0);
11139             done = TRUE;
11140         } else {
11141             if (appData.debugMode)
11142               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11143                       yy_text, currentMoveString);
11144             fromX = currentMoveString[0] - AAA;
11145             fromY = currentMoveString[1] - ONE;
11146             toX = currentMoveString[2] - AAA;
11147             toY = currentMoveString[3] - ONE;
11148             promoChar = currentMoveString[4];
11149         }
11150         break;
11151
11152       case AmbiguousMove:
11153         if (appData.debugMode)
11154           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11155         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11156                 (forwardMostMove / 2) + 1,
11157                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11158         DisplayError(move, 0);
11159         done = TRUE;
11160         break;
11161
11162       default:
11163       case ImpossibleMove:
11164         if (appData.debugMode)
11165           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11166         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11167                 (forwardMostMove / 2) + 1,
11168                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11169         DisplayError(move, 0);
11170         done = TRUE;
11171         break;
11172     }
11173
11174     if (done) {
11175         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11176             DrawPosition(FALSE, boards[currentMove]);
11177             DisplayBothClocks();
11178             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11179               DisplayComment(currentMove - 1, commentList[currentMove]);
11180         }
11181         (void) StopLoadGameTimer();
11182         gameFileFP = NULL;
11183         cmailOldMove = forwardMostMove;
11184         return FALSE;
11185     } else {
11186         /* currentMoveString is set as a side-effect of yylex */
11187
11188         thinkOutput[0] = NULLCHAR;
11189         MakeMove(fromX, fromY, toX, toY, promoChar);
11190         currentMove = forwardMostMove;
11191         return TRUE;
11192     }
11193 }
11194
11195 /* Load the nth game from the given file */
11196 int
11197 LoadGameFromFile (char *filename, int n, char *title, int useList)
11198 {
11199     FILE *f;
11200     char buf[MSG_SIZ];
11201
11202     if (strcmp(filename, "-") == 0) {
11203         f = stdin;
11204         title = "stdin";
11205     } else {
11206         f = fopen(filename, "rb");
11207         if (f == NULL) {
11208           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11209             DisplayError(buf, errno);
11210             return FALSE;
11211         }
11212     }
11213     if (fseek(f, 0, 0) == -1) {
11214         /* f is not seekable; probably a pipe */
11215         useList = FALSE;
11216     }
11217     if (useList && n == 0) {
11218         int error = GameListBuild(f);
11219         if (error) {
11220             DisplayError(_("Cannot build game list"), error);
11221         } else if (!ListEmpty(&gameList) &&
11222                    ((ListGame *) gameList.tailPred)->number > 1) {
11223             GameListPopUp(f, title);
11224             return TRUE;
11225         }
11226         GameListDestroy();
11227         n = 1;
11228     }
11229     if (n == 0) n = 1;
11230     return LoadGame(f, n, title, FALSE);
11231 }
11232
11233
11234 void
11235 MakeRegisteredMove ()
11236 {
11237     int fromX, fromY, toX, toY;
11238     char promoChar;
11239     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11240         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11241           case CMAIL_MOVE:
11242           case CMAIL_DRAW:
11243             if (appData.debugMode)
11244               fprintf(debugFP, "Restoring %s for game %d\n",
11245                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11246
11247             thinkOutput[0] = NULLCHAR;
11248             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11249             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11250             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11251             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11252             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11253             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11254             MakeMove(fromX, fromY, toX, toY, promoChar);
11255             ShowMove(fromX, fromY, toX, toY);
11256
11257             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11258               case MT_NONE:
11259               case MT_CHECK:
11260                 break;
11261
11262               case MT_CHECKMATE:
11263               case MT_STAINMATE:
11264                 if (WhiteOnMove(currentMove)) {
11265                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11266                 } else {
11267                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11268                 }
11269                 break;
11270
11271               case MT_STALEMATE:
11272                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11273                 break;
11274             }
11275
11276             break;
11277
11278           case CMAIL_RESIGN:
11279             if (WhiteOnMove(currentMove)) {
11280                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11281             } else {
11282                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11283             }
11284             break;
11285
11286           case CMAIL_ACCEPT:
11287             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11288             break;
11289
11290           default:
11291             break;
11292         }
11293     }
11294
11295     return;
11296 }
11297
11298 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11299 int
11300 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11301 {
11302     int retVal;
11303
11304     if (gameNumber > nCmailGames) {
11305         DisplayError(_("No more games in this message"), 0);
11306         return FALSE;
11307     }
11308     if (f == lastLoadGameFP) {
11309         int offset = gameNumber - lastLoadGameNumber;
11310         if (offset == 0) {
11311             cmailMsg[0] = NULLCHAR;
11312             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11313                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11314                 nCmailMovesRegistered--;
11315             }
11316             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11317             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11318                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11319             }
11320         } else {
11321             if (! RegisterMove()) return FALSE;
11322         }
11323     }
11324
11325     retVal = LoadGame(f, gameNumber, title, useList);
11326
11327     /* Make move registered during previous look at this game, if any */
11328     MakeRegisteredMove();
11329
11330     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11331         commentList[currentMove]
11332           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11333         DisplayComment(currentMove - 1, commentList[currentMove]);
11334     }
11335
11336     return retVal;
11337 }
11338
11339 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11340 int
11341 ReloadGame (int offset)
11342 {
11343     int gameNumber = lastLoadGameNumber + offset;
11344     if (lastLoadGameFP == NULL) {
11345         DisplayError(_("No game has been loaded yet"), 0);
11346         return FALSE;
11347     }
11348     if (gameNumber <= 0) {
11349         DisplayError(_("Can't back up any further"), 0);
11350         return FALSE;
11351     }
11352     if (cmailMsgLoaded) {
11353         return CmailLoadGame(lastLoadGameFP, gameNumber,
11354                              lastLoadGameTitle, lastLoadGameUseList);
11355     } else {
11356         return LoadGame(lastLoadGameFP, gameNumber,
11357                         lastLoadGameTitle, lastLoadGameUseList);
11358     }
11359 }
11360
11361 int keys[EmptySquare+1];
11362
11363 int
11364 PositionMatches (Board b1, Board b2)
11365 {
11366     int r, f, sum=0;
11367     switch(appData.searchMode) {
11368         case 1: return CompareWithRights(b1, b2);
11369         case 2:
11370             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11371                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11372             }
11373             return TRUE;
11374         case 3:
11375             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11376               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11377                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11378             }
11379             return sum==0;
11380         case 4:
11381             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11382                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11383             }
11384             return sum==0;
11385     }
11386     return TRUE;
11387 }
11388
11389 #define Q_PROMO  4
11390 #define Q_EP     3
11391 #define Q_BCASTL 2
11392 #define Q_WCASTL 1
11393
11394 int pieceList[256], quickBoard[256];
11395 ChessSquare pieceType[256] = { EmptySquare };
11396 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11397 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11398 int soughtTotal, turn;
11399 Boolean epOK, flipSearch;
11400
11401 typedef struct {
11402     unsigned char piece, to;
11403 } Move;
11404
11405 #define DSIZE (250000)
11406
11407 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11408 Move *moveDatabase = initialSpace;
11409 unsigned int movePtr, dataSize = DSIZE;
11410
11411 int
11412 MakePieceList (Board board, int *counts)
11413 {
11414     int r, f, n=Q_PROMO, total=0;
11415     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11416     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11417         int sq = f + (r<<4);
11418         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11419             quickBoard[sq] = ++n;
11420             pieceList[n] = sq;
11421             pieceType[n] = board[r][f];
11422             counts[board[r][f]]++;
11423             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11424             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11425             total++;
11426         }
11427     }
11428     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11429     return total;
11430 }
11431
11432 void
11433 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11434 {
11435     int sq = fromX + (fromY<<4);
11436     int piece = quickBoard[sq];
11437     quickBoard[sq] = 0;
11438     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11439     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11440         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11441         moveDatabase[movePtr++].piece = Q_WCASTL;
11442         quickBoard[sq] = piece;
11443         piece = quickBoard[from]; quickBoard[from] = 0;
11444         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11445     } else
11446     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11447         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11448         moveDatabase[movePtr++].piece = Q_BCASTL;
11449         quickBoard[sq] = piece;
11450         piece = quickBoard[from]; quickBoard[from] = 0;
11451         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11452     } else
11453     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11454         quickBoard[(fromY<<4)+toX] = 0;
11455         moveDatabase[movePtr].piece = Q_EP;
11456         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11457         moveDatabase[movePtr].to = sq;
11458     } else
11459     if(promoPiece != pieceType[piece]) {
11460         moveDatabase[movePtr++].piece = Q_PROMO;
11461         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11462     }
11463     moveDatabase[movePtr].piece = piece;
11464     quickBoard[sq] = piece;
11465     movePtr++;
11466 }
11467
11468 int
11469 PackGame (Board board)
11470 {
11471     Move *newSpace = NULL;
11472     moveDatabase[movePtr].piece = 0; // terminate previous game
11473     if(movePtr > dataSize) {
11474         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11475         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11476         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11477         if(newSpace) {
11478             int i;
11479             Move *p = moveDatabase, *q = newSpace;
11480             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11481             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11482             moveDatabase = newSpace;
11483         } else { // calloc failed, we must be out of memory. Too bad...
11484             dataSize = 0; // prevent calloc events for all subsequent games
11485             return 0;     // and signal this one isn't cached
11486         }
11487     }
11488     movePtr++;
11489     MakePieceList(board, counts);
11490     return movePtr;
11491 }
11492
11493 int
11494 QuickCompare (Board board, int *minCounts, int *maxCounts)
11495 {   // compare according to search mode
11496     int r, f;
11497     switch(appData.searchMode)
11498     {
11499       case 1: // exact position match
11500         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11501         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11502             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11503         }
11504         break;
11505       case 2: // can have extra material on empty squares
11506         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11507             if(board[r][f] == EmptySquare) continue;
11508             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11509         }
11510         break;
11511       case 3: // material with exact Pawn structure
11512         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11513             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11514             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11515         } // fall through to material comparison
11516       case 4: // exact material
11517         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11518         break;
11519       case 6: // material range with given imbalance
11520         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11521         // fall through to range comparison
11522       case 5: // material range
11523         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11524     }
11525     return TRUE;
11526 }
11527
11528 int
11529 QuickScan (Board board, Move *move)
11530 {   // reconstruct game,and compare all positions in it
11531     int cnt=0, stretch=0, total = MakePieceList(board, counts), delayedKing = -1;
11532     do {
11533         int piece = move->piece;
11534         int to = move->to, from = pieceList[piece];
11535         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11536           if(!piece) return -1;
11537           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11538             piece = (++move)->piece;
11539             from = pieceList[piece];
11540             counts[pieceType[piece]]--;
11541             pieceType[piece] = (ChessSquare) move->to;
11542             counts[move->to]++;
11543           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11544             counts[pieceType[quickBoard[to]]]--;
11545             quickBoard[to] = 0; total--;
11546             move++;
11547             continue;
11548           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11549             int rook;
11550             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11551             from  = pieceList[piece]; // so this must be King
11552             quickBoard[from] = 0;
11553             pieceList[piece] = to;
11554             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11555             quickBoard[from] = 0; // rook
11556             quickBoard[to] = piece;
11557             to = move->to; piece = move->piece;
11558             goto aftercastle;
11559           }
11560         }
11561         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11562         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11563         quickBoard[from] = 0;
11564       aftercastle:
11565         quickBoard[to] = piece;
11566         pieceList[piece] = to;
11567         cnt++; turn ^= 3;
11568         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11569            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11570            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11571                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11572           ) {
11573             static int lastCounts[EmptySquare+1];
11574             int i;
11575             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11576             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11577         } else stretch = 0;
11578         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11579         move++; delayedKing = -1;
11580     } while(1);
11581 }
11582
11583 void
11584 InitSearch ()
11585 {
11586     int r, f;
11587     flipSearch = FALSE;
11588     CopyBoard(soughtBoard, boards[currentMove]);
11589     soughtTotal = MakePieceList(soughtBoard, maxSought);
11590     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11591     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11592     CopyBoard(reverseBoard, boards[currentMove]);
11593     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11594         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11595         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11596         reverseBoard[r][f] = piece;
11597     }
11598     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11599     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11600     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11601                  || (boards[currentMove][CASTLING][2] == NoRights || 
11602                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11603                  && (boards[currentMove][CASTLING][5] == NoRights || 
11604                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11605       ) {
11606         flipSearch = TRUE;
11607         CopyBoard(flipBoard, soughtBoard);
11608         CopyBoard(rotateBoard, reverseBoard);
11609         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11610             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11611             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11612         }
11613     }
11614     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11615     if(appData.searchMode >= 5) {
11616         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11617         MakePieceList(soughtBoard, minSought);
11618         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11619     }
11620     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11621         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11622 }
11623
11624 GameInfo dummyInfo;
11625
11626 int
11627 GameContainsPosition (FILE *f, ListGame *lg)
11628 {
11629     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11630     int fromX, fromY, toX, toY;
11631     char promoChar;
11632     static int initDone=FALSE;
11633
11634     // weed out games based on numerical tag comparison
11635     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11636     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11637     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11638     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11639     if(!initDone) {
11640         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11641         initDone = TRUE;
11642     }
11643     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11644     else CopyBoard(boards[scratch], initialPosition); // default start position
11645     if(lg->moves) {
11646         turn = btm + 1;
11647         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11648         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11649     }
11650     if(btm) plyNr++;
11651     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11652     fseek(f, lg->offset, 0);
11653     yynewfile(f);
11654     while(1) {
11655         yyboardindex = scratch;
11656         quickFlag = plyNr+1;
11657         next = Myylex();
11658         quickFlag = 0;
11659         switch(next) {
11660             case PGNTag:
11661                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11662             default:
11663                 continue;
11664
11665             case XBoardGame:
11666             case GNUChessGame:
11667                 if(plyNr) return -1; // after we have seen moves, this is for new game
11668               continue;
11669
11670             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11671             case ImpossibleMove:
11672             case WhiteWins: // game ends here with these four
11673             case BlackWins:
11674             case GameIsDrawn:
11675             case GameUnfinished:
11676                 return -1;
11677
11678             case IllegalMove:
11679                 if(appData.testLegality) return -1;
11680             case WhiteCapturesEnPassant:
11681             case BlackCapturesEnPassant:
11682             case WhitePromotion:
11683             case BlackPromotion:
11684             case WhiteNonPromotion:
11685             case BlackNonPromotion:
11686             case NormalMove:
11687             case WhiteKingSideCastle:
11688             case WhiteQueenSideCastle:
11689             case BlackKingSideCastle:
11690             case BlackQueenSideCastle:
11691             case WhiteKingSideCastleWild:
11692             case WhiteQueenSideCastleWild:
11693             case BlackKingSideCastleWild:
11694             case BlackQueenSideCastleWild:
11695             case WhiteHSideCastleFR:
11696             case WhiteASideCastleFR:
11697             case BlackHSideCastleFR:
11698             case BlackASideCastleFR:
11699                 fromX = currentMoveString[0] - AAA;
11700                 fromY = currentMoveString[1] - ONE;
11701                 toX = currentMoveString[2] - AAA;
11702                 toY = currentMoveString[3] - ONE;
11703                 promoChar = currentMoveString[4];
11704                 break;
11705             case WhiteDrop:
11706             case BlackDrop:
11707                 fromX = next == WhiteDrop ?
11708                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11709                   (int) CharToPiece(ToLower(currentMoveString[0]));
11710                 fromY = DROP_RANK;
11711                 toX = currentMoveString[2] - AAA;
11712                 toY = currentMoveString[3] - ONE;
11713                 promoChar = 0;
11714                 break;
11715         }
11716         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11717         plyNr++;
11718         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11719         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11720         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11721         if(appData.findMirror) {
11722             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11723             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11724         }
11725     }
11726 }
11727
11728 /* Load the nth game from open file f */
11729 int
11730 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11731 {
11732     ChessMove cm;
11733     char buf[MSG_SIZ];
11734     int gn = gameNumber;
11735     ListGame *lg = NULL;
11736     int numPGNTags = 0;
11737     int err, pos = -1;
11738     GameMode oldGameMode;
11739     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11740
11741     if (appData.debugMode)
11742         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11743
11744     if (gameMode == Training )
11745         SetTrainingModeOff();
11746
11747     oldGameMode = gameMode;
11748     if (gameMode != BeginningOfGame) {
11749       Reset(FALSE, TRUE);
11750     }
11751
11752     gameFileFP = f;
11753     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11754         fclose(lastLoadGameFP);
11755     }
11756
11757     if (useList) {
11758         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11759
11760         if (lg) {
11761             fseek(f, lg->offset, 0);
11762             GameListHighlight(gameNumber);
11763             pos = lg->position;
11764             gn = 1;
11765         }
11766         else {
11767             DisplayError(_("Game number out of range"), 0);
11768             return FALSE;
11769         }
11770     } else {
11771         GameListDestroy();
11772         if (fseek(f, 0, 0) == -1) {
11773             if (f == lastLoadGameFP ?
11774                 gameNumber == lastLoadGameNumber + 1 :
11775                 gameNumber == 1) {
11776                 gn = 1;
11777             } else {
11778                 DisplayError(_("Can't seek on game file"), 0);
11779                 return FALSE;
11780             }
11781         }
11782     }
11783     lastLoadGameFP = f;
11784     lastLoadGameNumber = gameNumber;
11785     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11786     lastLoadGameUseList = useList;
11787
11788     yynewfile(f);
11789
11790     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11791       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11792                 lg->gameInfo.black);
11793             DisplayTitle(buf);
11794     } else if (*title != NULLCHAR) {
11795         if (gameNumber > 1) {
11796           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11797             DisplayTitle(buf);
11798         } else {
11799             DisplayTitle(title);
11800         }
11801     }
11802
11803     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11804         gameMode = PlayFromGameFile;
11805         ModeHighlight();
11806     }
11807
11808     currentMove = forwardMostMove = backwardMostMove = 0;
11809     CopyBoard(boards[0], initialPosition);
11810     StopClocks();
11811
11812     /*
11813      * Skip the first gn-1 games in the file.
11814      * Also skip over anything that precedes an identifiable
11815      * start of game marker, to avoid being confused by
11816      * garbage at the start of the file.  Currently
11817      * recognized start of game markers are the move number "1",
11818      * the pattern "gnuchess .* game", the pattern
11819      * "^[#;%] [^ ]* game file", and a PGN tag block.
11820      * A game that starts with one of the latter two patterns
11821      * will also have a move number 1, possibly
11822      * following a position diagram.
11823      * 5-4-02: Let's try being more lenient and allowing a game to
11824      * start with an unnumbered move.  Does that break anything?
11825      */
11826     cm = lastLoadGameStart = EndOfFile;
11827     while (gn > 0) {
11828         yyboardindex = forwardMostMove;
11829         cm = (ChessMove) Myylex();
11830         switch (cm) {
11831           case EndOfFile:
11832             if (cmailMsgLoaded) {
11833                 nCmailGames = CMAIL_MAX_GAMES - gn;
11834             } else {
11835                 Reset(TRUE, TRUE);
11836                 DisplayError(_("Game not found in file"), 0);
11837             }
11838             return FALSE;
11839
11840           case GNUChessGame:
11841           case XBoardGame:
11842             gn--;
11843             lastLoadGameStart = cm;
11844             break;
11845
11846           case MoveNumberOne:
11847             switch (lastLoadGameStart) {
11848               case GNUChessGame:
11849               case XBoardGame:
11850               case PGNTag:
11851                 break;
11852               case MoveNumberOne:
11853               case EndOfFile:
11854                 gn--;           /* count this game */
11855                 lastLoadGameStart = cm;
11856                 break;
11857               default:
11858                 /* impossible */
11859                 break;
11860             }
11861             break;
11862
11863           case PGNTag:
11864             switch (lastLoadGameStart) {
11865               case GNUChessGame:
11866               case PGNTag:
11867               case MoveNumberOne:
11868               case EndOfFile:
11869                 gn--;           /* count this game */
11870                 lastLoadGameStart = cm;
11871                 break;
11872               case XBoardGame:
11873                 lastLoadGameStart = cm; /* game counted already */
11874                 break;
11875               default:
11876                 /* impossible */
11877                 break;
11878             }
11879             if (gn > 0) {
11880                 do {
11881                     yyboardindex = forwardMostMove;
11882                     cm = (ChessMove) Myylex();
11883                 } while (cm == PGNTag || cm == Comment);
11884             }
11885             break;
11886
11887           case WhiteWins:
11888           case BlackWins:
11889           case GameIsDrawn:
11890             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11891                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11892                     != CMAIL_OLD_RESULT) {
11893                     nCmailResults ++ ;
11894                     cmailResult[  CMAIL_MAX_GAMES
11895                                 - gn - 1] = CMAIL_OLD_RESULT;
11896                 }
11897             }
11898             break;
11899
11900           case NormalMove:
11901             /* Only a NormalMove can be at the start of a game
11902              * without a position diagram. */
11903             if (lastLoadGameStart == EndOfFile ) {
11904               gn--;
11905               lastLoadGameStart = MoveNumberOne;
11906             }
11907             break;
11908
11909           default:
11910             break;
11911         }
11912     }
11913
11914     if (appData.debugMode)
11915       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11916
11917     if (cm == XBoardGame) {
11918         /* Skip any header junk before position diagram and/or move 1 */
11919         for (;;) {
11920             yyboardindex = forwardMostMove;
11921             cm = (ChessMove) Myylex();
11922
11923             if (cm == EndOfFile ||
11924                 cm == GNUChessGame || cm == XBoardGame) {
11925                 /* Empty game; pretend end-of-file and handle later */
11926                 cm = EndOfFile;
11927                 break;
11928             }
11929
11930             if (cm == MoveNumberOne || cm == PositionDiagram ||
11931                 cm == PGNTag || cm == Comment)
11932               break;
11933         }
11934     } else if (cm == GNUChessGame) {
11935         if (gameInfo.event != NULL) {
11936             free(gameInfo.event);
11937         }
11938         gameInfo.event = StrSave(yy_text);
11939     }
11940
11941     startedFromSetupPosition = FALSE;
11942     while (cm == PGNTag) {
11943         if (appData.debugMode)
11944           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11945         err = ParsePGNTag(yy_text, &gameInfo);
11946         if (!err) numPGNTags++;
11947
11948         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11949         if(gameInfo.variant != oldVariant) {
11950             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11951             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11952             InitPosition(TRUE);
11953             oldVariant = gameInfo.variant;
11954             if (appData.debugMode)
11955               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11956         }
11957
11958
11959         if (gameInfo.fen != NULL) {
11960           Board initial_position;
11961           startedFromSetupPosition = TRUE;
11962           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11963             Reset(TRUE, TRUE);
11964             DisplayError(_("Bad FEN position in file"), 0);
11965             return FALSE;
11966           }
11967           CopyBoard(boards[0], initial_position);
11968           if (blackPlaysFirst) {
11969             currentMove = forwardMostMove = backwardMostMove = 1;
11970             CopyBoard(boards[1], initial_position);
11971             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11972             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11973             timeRemaining[0][1] = whiteTimeRemaining;
11974             timeRemaining[1][1] = blackTimeRemaining;
11975             if (commentList[0] != NULL) {
11976               commentList[1] = commentList[0];
11977               commentList[0] = NULL;
11978             }
11979           } else {
11980             currentMove = forwardMostMove = backwardMostMove = 0;
11981           }
11982           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11983           {   int i;
11984               initialRulePlies = FENrulePlies;
11985               for( i=0; i< nrCastlingRights; i++ )
11986                   initialRights[i] = initial_position[CASTLING][i];
11987           }
11988           yyboardindex = forwardMostMove;
11989           free(gameInfo.fen);
11990           gameInfo.fen = NULL;
11991         }
11992
11993         yyboardindex = forwardMostMove;
11994         cm = (ChessMove) Myylex();
11995
11996         /* Handle comments interspersed among the tags */
11997         while (cm == Comment) {
11998             char *p;
11999             if (appData.debugMode)
12000               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12001             p = yy_text;
12002             AppendComment(currentMove, p, FALSE);
12003             yyboardindex = forwardMostMove;
12004             cm = (ChessMove) Myylex();
12005         }
12006     }
12007
12008     /* don't rely on existence of Event tag since if game was
12009      * pasted from clipboard the Event tag may not exist
12010      */
12011     if (numPGNTags > 0){
12012         char *tags;
12013         if (gameInfo.variant == VariantNormal) {
12014           VariantClass v = StringToVariant(gameInfo.event);
12015           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12016           if(v < VariantShogi) gameInfo.variant = v;
12017         }
12018         if (!matchMode) {
12019           if( appData.autoDisplayTags ) {
12020             tags = PGNTags(&gameInfo);
12021             TagsPopUp(tags, CmailMsg());
12022             free(tags);
12023           }
12024         }
12025     } else {
12026         /* Make something up, but don't display it now */
12027         SetGameInfo();
12028         TagsPopDown();
12029     }
12030
12031     if (cm == PositionDiagram) {
12032         int i, j;
12033         char *p;
12034         Board initial_position;
12035
12036         if (appData.debugMode)
12037           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12038
12039         if (!startedFromSetupPosition) {
12040             p = yy_text;
12041             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12042               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12043                 switch (*p) {
12044                   case '{':
12045                   case '[':
12046                   case '-':
12047                   case ' ':
12048                   case '\t':
12049                   case '\n':
12050                   case '\r':
12051                     break;
12052                   default:
12053                     initial_position[i][j++] = CharToPiece(*p);
12054                     break;
12055                 }
12056             while (*p == ' ' || *p == '\t' ||
12057                    *p == '\n' || *p == '\r') p++;
12058
12059             if (strncmp(p, "black", strlen("black"))==0)
12060               blackPlaysFirst = TRUE;
12061             else
12062               blackPlaysFirst = FALSE;
12063             startedFromSetupPosition = TRUE;
12064
12065             CopyBoard(boards[0], initial_position);
12066             if (blackPlaysFirst) {
12067                 currentMove = forwardMostMove = backwardMostMove = 1;
12068                 CopyBoard(boards[1], initial_position);
12069                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12070                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12071                 timeRemaining[0][1] = whiteTimeRemaining;
12072                 timeRemaining[1][1] = blackTimeRemaining;
12073                 if (commentList[0] != NULL) {
12074                     commentList[1] = commentList[0];
12075                     commentList[0] = NULL;
12076                 }
12077             } else {
12078                 currentMove = forwardMostMove = backwardMostMove = 0;
12079             }
12080         }
12081         yyboardindex = forwardMostMove;
12082         cm = (ChessMove) Myylex();
12083     }
12084
12085     if (first.pr == NoProc) {
12086         StartChessProgram(&first);
12087     }
12088     InitChessProgram(&first, FALSE);
12089     SendToProgram("force\n", &first);
12090     if (startedFromSetupPosition) {
12091         SendBoard(&first, forwardMostMove);
12092     if (appData.debugMode) {
12093         fprintf(debugFP, "Load Game\n");
12094     }
12095         DisplayBothClocks();
12096     }
12097
12098     /* [HGM] server: flag to write setup moves in broadcast file as one */
12099     loadFlag = appData.suppressLoadMoves;
12100
12101     while (cm == Comment) {
12102         char *p;
12103         if (appData.debugMode)
12104           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12105         p = yy_text;
12106         AppendComment(currentMove, p, FALSE);
12107         yyboardindex = forwardMostMove;
12108         cm = (ChessMove) Myylex();
12109     }
12110
12111     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12112         cm == WhiteWins || cm == BlackWins ||
12113         cm == GameIsDrawn || cm == GameUnfinished) {
12114         DisplayMessage("", _("No moves in game"));
12115         if (cmailMsgLoaded) {
12116             if (appData.debugMode)
12117               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12118             ClearHighlights();
12119             flipView = FALSE;
12120         }
12121         DrawPosition(FALSE, boards[currentMove]);
12122         DisplayBothClocks();
12123         gameMode = EditGame;
12124         ModeHighlight();
12125         gameFileFP = NULL;
12126         cmailOldMove = 0;
12127         return TRUE;
12128     }
12129
12130     // [HGM] PV info: routine tests if comment empty
12131     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12132         DisplayComment(currentMove - 1, commentList[currentMove]);
12133     }
12134     if (!matchMode && appData.timeDelay != 0)
12135       DrawPosition(FALSE, boards[currentMove]);
12136
12137     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12138       programStats.ok_to_send = 1;
12139     }
12140
12141     /* if the first token after the PGN tags is a move
12142      * and not move number 1, retrieve it from the parser
12143      */
12144     if (cm != MoveNumberOne)
12145         LoadGameOneMove(cm);
12146
12147     /* load the remaining moves from the file */
12148     while (LoadGameOneMove(EndOfFile)) {
12149       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12150       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12151     }
12152
12153     /* rewind to the start of the game */
12154     currentMove = backwardMostMove;
12155
12156     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12157
12158     if (oldGameMode == AnalyzeFile ||
12159         oldGameMode == AnalyzeMode) {
12160       AnalyzeFileEvent();
12161     }
12162
12163     if (!matchMode && pos > 0) {
12164         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12165     } else
12166     if (matchMode || appData.timeDelay == 0) {
12167       ToEndEvent();
12168     } else if (appData.timeDelay > 0) {
12169       AutoPlayGameLoop();
12170     }
12171
12172     if (appData.debugMode)
12173         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12174
12175     loadFlag = 0; /* [HGM] true game starts */
12176     return TRUE;
12177 }
12178
12179 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12180 int
12181 ReloadPosition (int offset)
12182 {
12183     int positionNumber = lastLoadPositionNumber + offset;
12184     if (lastLoadPositionFP == NULL) {
12185         DisplayError(_("No position has been loaded yet"), 0);
12186         return FALSE;
12187     }
12188     if (positionNumber <= 0) {
12189         DisplayError(_("Can't back up any further"), 0);
12190         return FALSE;
12191     }
12192     return LoadPosition(lastLoadPositionFP, positionNumber,
12193                         lastLoadPositionTitle);
12194 }
12195
12196 /* Load the nth position from the given file */
12197 int
12198 LoadPositionFromFile (char *filename, int n, char *title)
12199 {
12200     FILE *f;
12201     char buf[MSG_SIZ];
12202
12203     if (strcmp(filename, "-") == 0) {
12204         return LoadPosition(stdin, n, "stdin");
12205     } else {
12206         f = fopen(filename, "rb");
12207         if (f == NULL) {
12208             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12209             DisplayError(buf, errno);
12210             return FALSE;
12211         } else {
12212             return LoadPosition(f, n, title);
12213         }
12214     }
12215 }
12216
12217 /* Load the nth position from the given open file, and close it */
12218 int
12219 LoadPosition (FILE *f, int positionNumber, char *title)
12220 {
12221     char *p, line[MSG_SIZ];
12222     Board initial_position;
12223     int i, j, fenMode, pn;
12224
12225     if (gameMode == Training )
12226         SetTrainingModeOff();
12227
12228     if (gameMode != BeginningOfGame) {
12229         Reset(FALSE, TRUE);
12230     }
12231     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12232         fclose(lastLoadPositionFP);
12233     }
12234     if (positionNumber == 0) positionNumber = 1;
12235     lastLoadPositionFP = f;
12236     lastLoadPositionNumber = positionNumber;
12237     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12238     if (first.pr == NoProc && !appData.noChessProgram) {
12239       StartChessProgram(&first);
12240       InitChessProgram(&first, FALSE);
12241     }
12242     pn = positionNumber;
12243     if (positionNumber < 0) {
12244         /* Negative position number means to seek to that byte offset */
12245         if (fseek(f, -positionNumber, 0) == -1) {
12246             DisplayError(_("Can't seek on position file"), 0);
12247             return FALSE;
12248         };
12249         pn = 1;
12250     } else {
12251         if (fseek(f, 0, 0) == -1) {
12252             if (f == lastLoadPositionFP ?
12253                 positionNumber == lastLoadPositionNumber + 1 :
12254                 positionNumber == 1) {
12255                 pn = 1;
12256             } else {
12257                 DisplayError(_("Can't seek on position file"), 0);
12258                 return FALSE;
12259             }
12260         }
12261     }
12262     /* See if this file is FEN or old-style xboard */
12263     if (fgets(line, MSG_SIZ, f) == NULL) {
12264         DisplayError(_("Position not found in file"), 0);
12265         return FALSE;
12266     }
12267     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12268     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12269
12270     if (pn >= 2) {
12271         if (fenMode || line[0] == '#') pn--;
12272         while (pn > 0) {
12273             /* skip positions before number pn */
12274             if (fgets(line, MSG_SIZ, f) == NULL) {
12275                 Reset(TRUE, TRUE);
12276                 DisplayError(_("Position not found in file"), 0);
12277                 return FALSE;
12278             }
12279             if (fenMode || line[0] == '#') pn--;
12280         }
12281     }
12282
12283     if (fenMode) {
12284         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12285             DisplayError(_("Bad FEN position in file"), 0);
12286             return FALSE;
12287         }
12288     } else {
12289         (void) fgets(line, MSG_SIZ, f);
12290         (void) fgets(line, MSG_SIZ, f);
12291
12292         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12293             (void) fgets(line, MSG_SIZ, f);
12294             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12295                 if (*p == ' ')
12296                   continue;
12297                 initial_position[i][j++] = CharToPiece(*p);
12298             }
12299         }
12300
12301         blackPlaysFirst = FALSE;
12302         if (!feof(f)) {
12303             (void) fgets(line, MSG_SIZ, f);
12304             if (strncmp(line, "black", strlen("black"))==0)
12305               blackPlaysFirst = TRUE;
12306         }
12307     }
12308     startedFromSetupPosition = TRUE;
12309
12310     CopyBoard(boards[0], initial_position);
12311     if (blackPlaysFirst) {
12312         currentMove = forwardMostMove = backwardMostMove = 1;
12313         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12314         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12315         CopyBoard(boards[1], initial_position);
12316         DisplayMessage("", _("Black to play"));
12317     } else {
12318         currentMove = forwardMostMove = backwardMostMove = 0;
12319         DisplayMessage("", _("White to play"));
12320     }
12321     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12322     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12323         SendToProgram("force\n", &first);
12324         SendBoard(&first, forwardMostMove);
12325     }
12326     if (appData.debugMode) {
12327 int i, j;
12328   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12329   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12330         fprintf(debugFP, "Load Position\n");
12331     }
12332
12333     if (positionNumber > 1) {
12334       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12335         DisplayTitle(line);
12336     } else {
12337         DisplayTitle(title);
12338     }
12339     gameMode = EditGame;
12340     ModeHighlight();
12341     ResetClocks();
12342     timeRemaining[0][1] = whiteTimeRemaining;
12343     timeRemaining[1][1] = blackTimeRemaining;
12344     DrawPosition(FALSE, boards[currentMove]);
12345
12346     return TRUE;
12347 }
12348
12349
12350 void
12351 CopyPlayerNameIntoFileName (char **dest, char *src)
12352 {
12353     while (*src != NULLCHAR && *src != ',') {
12354         if (*src == ' ') {
12355             *(*dest)++ = '_';
12356             src++;
12357         } else {
12358             *(*dest)++ = *src++;
12359         }
12360     }
12361 }
12362
12363 char *
12364 DefaultFileName (char *ext)
12365 {
12366     static char def[MSG_SIZ];
12367     char *p;
12368
12369     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12370         p = def;
12371         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12372         *p++ = '-';
12373         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12374         *p++ = '.';
12375         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12376     } else {
12377         def[0] = NULLCHAR;
12378     }
12379     return def;
12380 }
12381
12382 /* Save the current game to the given file */
12383 int
12384 SaveGameToFile (char *filename, int append)
12385 {
12386     FILE *f;
12387     char buf[MSG_SIZ];
12388     int result, i, t,tot=0;
12389
12390     if (strcmp(filename, "-") == 0) {
12391         return SaveGame(stdout, 0, NULL);
12392     } else {
12393         for(i=0; i<10; i++) { // upto 10 tries
12394              f = fopen(filename, append ? "a" : "w");
12395              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12396              if(f || errno != 13) break;
12397              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12398              tot += t;
12399         }
12400         if (f == NULL) {
12401             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12402             DisplayError(buf, errno);
12403             return FALSE;
12404         } else {
12405             safeStrCpy(buf, lastMsg, MSG_SIZ);
12406             DisplayMessage(_("Waiting for access to save file"), "");
12407             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12408             DisplayMessage(_("Saving game"), "");
12409             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12410             result = SaveGame(f, 0, NULL);
12411             DisplayMessage(buf, "");
12412             return result;
12413         }
12414     }
12415 }
12416
12417 char *
12418 SavePart (char *str)
12419 {
12420     static char buf[MSG_SIZ];
12421     char *p;
12422
12423     p = strchr(str, ' ');
12424     if (p == NULL) return str;
12425     strncpy(buf, str, p - str);
12426     buf[p - str] = NULLCHAR;
12427     return buf;
12428 }
12429
12430 #define PGN_MAX_LINE 75
12431
12432 #define PGN_SIDE_WHITE  0
12433 #define PGN_SIDE_BLACK  1
12434
12435 static int
12436 FindFirstMoveOutOfBook (int side)
12437 {
12438     int result = -1;
12439
12440     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12441         int index = backwardMostMove;
12442         int has_book_hit = 0;
12443
12444         if( (index % 2) != side ) {
12445             index++;
12446         }
12447
12448         while( index < forwardMostMove ) {
12449             /* Check to see if engine is in book */
12450             int depth = pvInfoList[index].depth;
12451             int score = pvInfoList[index].score;
12452             int in_book = 0;
12453
12454             if( depth <= 2 ) {
12455                 in_book = 1;
12456             }
12457             else if( score == 0 && depth == 63 ) {
12458                 in_book = 1; /* Zappa */
12459             }
12460             else if( score == 2 && depth == 99 ) {
12461                 in_book = 1; /* Abrok */
12462             }
12463
12464             has_book_hit += in_book;
12465
12466             if( ! in_book ) {
12467                 result = index;
12468
12469                 break;
12470             }
12471
12472             index += 2;
12473         }
12474     }
12475
12476     return result;
12477 }
12478
12479 void
12480 GetOutOfBookInfo (char * buf)
12481 {
12482     int oob[2];
12483     int i;
12484     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12485
12486     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12487     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12488
12489     *buf = '\0';
12490
12491     if( oob[0] >= 0 || oob[1] >= 0 ) {
12492         for( i=0; i<2; i++ ) {
12493             int idx = oob[i];
12494
12495             if( idx >= 0 ) {
12496                 if( i > 0 && oob[0] >= 0 ) {
12497                     strcat( buf, "   " );
12498                 }
12499
12500                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12501                 sprintf( buf+strlen(buf), "%s%.2f",
12502                     pvInfoList[idx].score >= 0 ? "+" : "",
12503                     pvInfoList[idx].score / 100.0 );
12504             }
12505         }
12506     }
12507 }
12508
12509 /* Save game in PGN style and close the file */
12510 int
12511 SaveGamePGN (FILE *f)
12512 {
12513     int i, offset, linelen, newblock;
12514     time_t tm;
12515 //    char *movetext;
12516     char numtext[32];
12517     int movelen, numlen, blank;
12518     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12519
12520     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12521
12522     tm = time((time_t *) NULL);
12523
12524     PrintPGNTags(f, &gameInfo);
12525
12526     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12527
12528     if (backwardMostMove > 0 || startedFromSetupPosition) {
12529         char *fen = PositionToFEN(backwardMostMove, NULL);
12530         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12531         fprintf(f, "\n{--------------\n");
12532         PrintPosition(f, backwardMostMove);
12533         fprintf(f, "--------------}\n");
12534         free(fen);
12535     }
12536     else {
12537         /* [AS] Out of book annotation */
12538         if( appData.saveOutOfBookInfo ) {
12539             char buf[64];
12540
12541             GetOutOfBookInfo( buf );
12542
12543             if( buf[0] != '\0' ) {
12544                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12545             }
12546         }
12547
12548         fprintf(f, "\n");
12549     }
12550
12551     i = backwardMostMove;
12552     linelen = 0;
12553     newblock = TRUE;
12554
12555     while (i < forwardMostMove) {
12556         /* Print comments preceding this move */
12557         if (commentList[i] != NULL) {
12558             if (linelen > 0) fprintf(f, "\n");
12559             fprintf(f, "%s", commentList[i]);
12560             linelen = 0;
12561             newblock = TRUE;
12562         }
12563
12564         /* Format move number */
12565         if ((i % 2) == 0)
12566           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12567         else
12568           if (newblock)
12569             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12570           else
12571             numtext[0] = NULLCHAR;
12572
12573         numlen = strlen(numtext);
12574         newblock = FALSE;
12575
12576         /* Print move number */
12577         blank = linelen > 0 && numlen > 0;
12578         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12579             fprintf(f, "\n");
12580             linelen = 0;
12581             blank = 0;
12582         }
12583         if (blank) {
12584             fprintf(f, " ");
12585             linelen++;
12586         }
12587         fprintf(f, "%s", numtext);
12588         linelen += numlen;
12589
12590         /* Get move */
12591         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12592         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12593
12594         /* Print move */
12595         blank = linelen > 0 && movelen > 0;
12596         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12597             fprintf(f, "\n");
12598             linelen = 0;
12599             blank = 0;
12600         }
12601         if (blank) {
12602             fprintf(f, " ");
12603             linelen++;
12604         }
12605         fprintf(f, "%s", move_buffer);
12606         linelen += movelen;
12607
12608         /* [AS] Add PV info if present */
12609         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12610             /* [HGM] add time */
12611             char buf[MSG_SIZ]; int seconds;
12612
12613             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12614
12615             if( seconds <= 0)
12616               buf[0] = 0;
12617             else
12618               if( seconds < 30 )
12619                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12620               else
12621                 {
12622                   seconds = (seconds + 4)/10; // round to full seconds
12623                   if( seconds < 60 )
12624                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12625                   else
12626                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12627                 }
12628
12629             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12630                       pvInfoList[i].score >= 0 ? "+" : "",
12631                       pvInfoList[i].score / 100.0,
12632                       pvInfoList[i].depth,
12633                       buf );
12634
12635             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12636
12637             /* Print score/depth */
12638             blank = linelen > 0 && movelen > 0;
12639             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12640                 fprintf(f, "\n");
12641                 linelen = 0;
12642                 blank = 0;
12643             }
12644             if (blank) {
12645                 fprintf(f, " ");
12646                 linelen++;
12647             }
12648             fprintf(f, "%s", move_buffer);
12649             linelen += movelen;
12650         }
12651
12652         i++;
12653     }
12654
12655     /* Start a new line */
12656     if (linelen > 0) fprintf(f, "\n");
12657
12658     /* Print comments after last move */
12659     if (commentList[i] != NULL) {
12660         fprintf(f, "%s\n", commentList[i]);
12661     }
12662
12663     /* Print result */
12664     if (gameInfo.resultDetails != NULL &&
12665         gameInfo.resultDetails[0] != NULLCHAR) {
12666         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12667                 PGNResult(gameInfo.result));
12668     } else {
12669         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12670     }
12671
12672     fclose(f);
12673     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12674     return TRUE;
12675 }
12676
12677 /* Save game in old style and close the file */
12678 int
12679 SaveGameOldStyle (FILE *f)
12680 {
12681     int i, offset;
12682     time_t tm;
12683
12684     tm = time((time_t *) NULL);
12685
12686     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12687     PrintOpponents(f);
12688
12689     if (backwardMostMove > 0 || startedFromSetupPosition) {
12690         fprintf(f, "\n[--------------\n");
12691         PrintPosition(f, backwardMostMove);
12692         fprintf(f, "--------------]\n");
12693     } else {
12694         fprintf(f, "\n");
12695     }
12696
12697     i = backwardMostMove;
12698     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12699
12700     while (i < forwardMostMove) {
12701         if (commentList[i] != NULL) {
12702             fprintf(f, "[%s]\n", commentList[i]);
12703         }
12704
12705         if ((i % 2) == 1) {
12706             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12707             i++;
12708         } else {
12709             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12710             i++;
12711             if (commentList[i] != NULL) {
12712                 fprintf(f, "\n");
12713                 continue;
12714             }
12715             if (i >= forwardMostMove) {
12716                 fprintf(f, "\n");
12717                 break;
12718             }
12719             fprintf(f, "%s\n", parseList[i]);
12720             i++;
12721         }
12722     }
12723
12724     if (commentList[i] != NULL) {
12725         fprintf(f, "[%s]\n", commentList[i]);
12726     }
12727
12728     /* This isn't really the old style, but it's close enough */
12729     if (gameInfo.resultDetails != NULL &&
12730         gameInfo.resultDetails[0] != NULLCHAR) {
12731         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12732                 gameInfo.resultDetails);
12733     } else {
12734         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12735     }
12736
12737     fclose(f);
12738     return TRUE;
12739 }
12740
12741 /* Save the current game to open file f and close the file */
12742 int
12743 SaveGame (FILE *f, int dummy, char *dummy2)
12744 {
12745     if (gameMode == EditPosition) EditPositionDone(TRUE);
12746     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12747     if (appData.oldSaveStyle)
12748       return SaveGameOldStyle(f);
12749     else
12750       return SaveGamePGN(f);
12751 }
12752
12753 /* Save the current position to the given file */
12754 int
12755 SavePositionToFile (char *filename)
12756 {
12757     FILE *f;
12758     char buf[MSG_SIZ];
12759
12760     if (strcmp(filename, "-") == 0) {
12761         return SavePosition(stdout, 0, NULL);
12762     } else {
12763         f = fopen(filename, "a");
12764         if (f == NULL) {
12765             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12766             DisplayError(buf, errno);
12767             return FALSE;
12768         } else {
12769             safeStrCpy(buf, lastMsg, MSG_SIZ);
12770             DisplayMessage(_("Waiting for access to save file"), "");
12771             flock(fileno(f), LOCK_EX); // [HGM] lock
12772             DisplayMessage(_("Saving position"), "");
12773             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12774             SavePosition(f, 0, NULL);
12775             DisplayMessage(buf, "");
12776             return TRUE;
12777         }
12778     }
12779 }
12780
12781 /* Save the current position to the given open file and close the file */
12782 int
12783 SavePosition (FILE *f, int dummy, char *dummy2)
12784 {
12785     time_t tm;
12786     char *fen;
12787
12788     if (gameMode == EditPosition) EditPositionDone(TRUE);
12789     if (appData.oldSaveStyle) {
12790         tm = time((time_t *) NULL);
12791
12792         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12793         PrintOpponents(f);
12794         fprintf(f, "[--------------\n");
12795         PrintPosition(f, currentMove);
12796         fprintf(f, "--------------]\n");
12797     } else {
12798         fen = PositionToFEN(currentMove, NULL);
12799         fprintf(f, "%s\n", fen);
12800         free(fen);
12801     }
12802     fclose(f);
12803     return TRUE;
12804 }
12805
12806 void
12807 ReloadCmailMsgEvent (int unregister)
12808 {
12809 #if !WIN32
12810     static char *inFilename = NULL;
12811     static char *outFilename;
12812     int i;
12813     struct stat inbuf, outbuf;
12814     int status;
12815
12816     /* Any registered moves are unregistered if unregister is set, */
12817     /* i.e. invoked by the signal handler */
12818     if (unregister) {
12819         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12820             cmailMoveRegistered[i] = FALSE;
12821             if (cmailCommentList[i] != NULL) {
12822                 free(cmailCommentList[i]);
12823                 cmailCommentList[i] = NULL;
12824             }
12825         }
12826         nCmailMovesRegistered = 0;
12827     }
12828
12829     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12830         cmailResult[i] = CMAIL_NOT_RESULT;
12831     }
12832     nCmailResults = 0;
12833
12834     if (inFilename == NULL) {
12835         /* Because the filenames are static they only get malloced once  */
12836         /* and they never get freed                                      */
12837         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12838         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12839
12840         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12841         sprintf(outFilename, "%s.out", appData.cmailGameName);
12842     }
12843
12844     status = stat(outFilename, &outbuf);
12845     if (status < 0) {
12846         cmailMailedMove = FALSE;
12847     } else {
12848         status = stat(inFilename, &inbuf);
12849         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12850     }
12851
12852     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12853        counts the games, notes how each one terminated, etc.
12854
12855        It would be nice to remove this kludge and instead gather all
12856        the information while building the game list.  (And to keep it
12857        in the game list nodes instead of having a bunch of fixed-size
12858        parallel arrays.)  Note this will require getting each game's
12859        termination from the PGN tags, as the game list builder does
12860        not process the game moves.  --mann
12861        */
12862     cmailMsgLoaded = TRUE;
12863     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12864
12865     /* Load first game in the file or popup game menu */
12866     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12867
12868 #endif /* !WIN32 */
12869     return;
12870 }
12871
12872 int
12873 RegisterMove ()
12874 {
12875     FILE *f;
12876     char string[MSG_SIZ];
12877
12878     if (   cmailMailedMove
12879         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12880         return TRUE;            /* Allow free viewing  */
12881     }
12882
12883     /* Unregister move to ensure that we don't leave RegisterMove        */
12884     /* with the move registered when the conditions for registering no   */
12885     /* longer hold                                                       */
12886     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12887         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12888         nCmailMovesRegistered --;
12889
12890         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12891           {
12892               free(cmailCommentList[lastLoadGameNumber - 1]);
12893               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12894           }
12895     }
12896
12897     if (cmailOldMove == -1) {
12898         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12899         return FALSE;
12900     }
12901
12902     if (currentMove > cmailOldMove + 1) {
12903         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12904         return FALSE;
12905     }
12906
12907     if (currentMove < cmailOldMove) {
12908         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12909         return FALSE;
12910     }
12911
12912     if (forwardMostMove > currentMove) {
12913         /* Silently truncate extra moves */
12914         TruncateGame();
12915     }
12916
12917     if (   (currentMove == cmailOldMove + 1)
12918         || (   (currentMove == cmailOldMove)
12919             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12920                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12921         if (gameInfo.result != GameUnfinished) {
12922             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12923         }
12924
12925         if (commentList[currentMove] != NULL) {
12926             cmailCommentList[lastLoadGameNumber - 1]
12927               = StrSave(commentList[currentMove]);
12928         }
12929         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12930
12931         if (appData.debugMode)
12932           fprintf(debugFP, "Saving %s for game %d\n",
12933                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12934
12935         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12936
12937         f = fopen(string, "w");
12938         if (appData.oldSaveStyle) {
12939             SaveGameOldStyle(f); /* also closes the file */
12940
12941             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12942             f = fopen(string, "w");
12943             SavePosition(f, 0, NULL); /* also closes the file */
12944         } else {
12945             fprintf(f, "{--------------\n");
12946             PrintPosition(f, currentMove);
12947             fprintf(f, "--------------}\n\n");
12948
12949             SaveGame(f, 0, NULL); /* also closes the file*/
12950         }
12951
12952         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12953         nCmailMovesRegistered ++;
12954     } else if (nCmailGames == 1) {
12955         DisplayError(_("You have not made a move yet"), 0);
12956         return FALSE;
12957     }
12958
12959     return TRUE;
12960 }
12961
12962 void
12963 MailMoveEvent ()
12964 {
12965 #if !WIN32
12966     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12967     FILE *commandOutput;
12968     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12969     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12970     int nBuffers;
12971     int i;
12972     int archived;
12973     char *arcDir;
12974
12975     if (! cmailMsgLoaded) {
12976         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12977         return;
12978     }
12979
12980     if (nCmailGames == nCmailResults) {
12981         DisplayError(_("No unfinished games"), 0);
12982         return;
12983     }
12984
12985 #if CMAIL_PROHIBIT_REMAIL
12986     if (cmailMailedMove) {
12987       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);
12988         DisplayError(msg, 0);
12989         return;
12990     }
12991 #endif
12992
12993     if (! (cmailMailedMove || RegisterMove())) return;
12994
12995     if (   cmailMailedMove
12996         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12997       snprintf(string, MSG_SIZ, partCommandString,
12998                appData.debugMode ? " -v" : "", appData.cmailGameName);
12999         commandOutput = popen(string, "r");
13000
13001         if (commandOutput == NULL) {
13002             DisplayError(_("Failed to invoke cmail"), 0);
13003         } else {
13004             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13005                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13006             }
13007             if (nBuffers > 1) {
13008                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13009                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13010                 nBytes = MSG_SIZ - 1;
13011             } else {
13012                 (void) memcpy(msg, buffer, nBytes);
13013             }
13014             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13015
13016             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13017                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13018
13019                 archived = TRUE;
13020                 for (i = 0; i < nCmailGames; i ++) {
13021                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13022                         archived = FALSE;
13023                     }
13024                 }
13025                 if (   archived
13026                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13027                         != NULL)) {
13028                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13029                            arcDir,
13030                            appData.cmailGameName,
13031                            gameInfo.date);
13032                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13033                     cmailMsgLoaded = FALSE;
13034                 }
13035             }
13036
13037             DisplayInformation(msg);
13038             pclose(commandOutput);
13039         }
13040     } else {
13041         if ((*cmailMsg) != '\0') {
13042             DisplayInformation(cmailMsg);
13043         }
13044     }
13045
13046     return;
13047 #endif /* !WIN32 */
13048 }
13049
13050 char *
13051 CmailMsg ()
13052 {
13053 #if WIN32
13054     return NULL;
13055 #else
13056     int  prependComma = 0;
13057     char number[5];
13058     char string[MSG_SIZ];       /* Space for game-list */
13059     int  i;
13060
13061     if (!cmailMsgLoaded) return "";
13062
13063     if (cmailMailedMove) {
13064       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13065     } else {
13066         /* Create a list of games left */
13067       snprintf(string, MSG_SIZ, "[");
13068         for (i = 0; i < nCmailGames; i ++) {
13069             if (! (   cmailMoveRegistered[i]
13070                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13071                 if (prependComma) {
13072                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13073                 } else {
13074                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13075                     prependComma = 1;
13076                 }
13077
13078                 strcat(string, number);
13079             }
13080         }
13081         strcat(string, "]");
13082
13083         if (nCmailMovesRegistered + nCmailResults == 0) {
13084             switch (nCmailGames) {
13085               case 1:
13086                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13087                 break;
13088
13089               case 2:
13090                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13091                 break;
13092
13093               default:
13094                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13095                          nCmailGames);
13096                 break;
13097             }
13098         } else {
13099             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13100               case 1:
13101                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13102                          string);
13103                 break;
13104
13105               case 0:
13106                 if (nCmailResults == nCmailGames) {
13107                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13108                 } else {
13109                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13110                 }
13111                 break;
13112
13113               default:
13114                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13115                          string);
13116             }
13117         }
13118     }
13119     return cmailMsg;
13120 #endif /* WIN32 */
13121 }
13122
13123 void
13124 ResetGameEvent ()
13125 {
13126     if (gameMode == Training)
13127       SetTrainingModeOff();
13128
13129     Reset(TRUE, TRUE);
13130     cmailMsgLoaded = FALSE;
13131     if (appData.icsActive) {
13132       SendToICS(ics_prefix);
13133       SendToICS("refresh\n");
13134     }
13135 }
13136
13137 void
13138 ExitEvent (int status)
13139 {
13140     exiting++;
13141     if (exiting > 2) {
13142       /* Give up on clean exit */
13143       exit(status);
13144     }
13145     if (exiting > 1) {
13146       /* Keep trying for clean exit */
13147       return;
13148     }
13149
13150     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13151
13152     if (telnetISR != NULL) {
13153       RemoveInputSource(telnetISR);
13154     }
13155     if (icsPR != NoProc) {
13156       DestroyChildProcess(icsPR, TRUE);
13157     }
13158
13159     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13160     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13161
13162     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13163     /* make sure this other one finishes before killing it!                  */
13164     if(endingGame) { int count = 0;
13165         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13166         while(endingGame && count++ < 10) DoSleep(1);
13167         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13168     }
13169
13170     /* Kill off chess programs */
13171     if (first.pr != NoProc) {
13172         ExitAnalyzeMode();
13173
13174         DoSleep( appData.delayBeforeQuit );
13175         SendToProgram("quit\n", &first);
13176         DoSleep( appData.delayAfterQuit );
13177         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13178     }
13179     if (second.pr != NoProc) {
13180         DoSleep( appData.delayBeforeQuit );
13181         SendToProgram("quit\n", &second);
13182         DoSleep( appData.delayAfterQuit );
13183         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13184     }
13185     if (first.isr != NULL) {
13186         RemoveInputSource(first.isr);
13187     }
13188     if (second.isr != NULL) {
13189         RemoveInputSource(second.isr);
13190     }
13191
13192     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13193     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13194
13195     ShutDownFrontEnd();
13196     exit(status);
13197 }
13198
13199 void
13200 PauseEvent ()
13201 {
13202     if (appData.debugMode)
13203         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13204     if (pausing) {
13205         pausing = FALSE;
13206         ModeHighlight();
13207         if (gameMode == MachinePlaysWhite ||
13208             gameMode == MachinePlaysBlack) {
13209             StartClocks();
13210         } else {
13211             DisplayBothClocks();
13212         }
13213         if (gameMode == PlayFromGameFile) {
13214             if (appData.timeDelay >= 0)
13215                 AutoPlayGameLoop();
13216         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13217             Reset(FALSE, TRUE);
13218             SendToICS(ics_prefix);
13219             SendToICS("refresh\n");
13220         } else if (currentMove < forwardMostMove) {
13221             ForwardInner(forwardMostMove);
13222         }
13223         pauseExamInvalid = FALSE;
13224     } else {
13225         switch (gameMode) {
13226           default:
13227             return;
13228           case IcsExamining:
13229             pauseExamForwardMostMove = forwardMostMove;
13230             pauseExamInvalid = FALSE;
13231             /* fall through */
13232           case IcsObserving:
13233           case IcsPlayingWhite:
13234           case IcsPlayingBlack:
13235             pausing = TRUE;
13236             ModeHighlight();
13237             return;
13238           case PlayFromGameFile:
13239             (void) StopLoadGameTimer();
13240             pausing = TRUE;
13241             ModeHighlight();
13242             break;
13243           case BeginningOfGame:
13244             if (appData.icsActive) return;
13245             /* else fall through */
13246           case MachinePlaysWhite:
13247           case MachinePlaysBlack:
13248           case TwoMachinesPlay:
13249             if (forwardMostMove == 0)
13250               return;           /* don't pause if no one has moved */
13251             if ((gameMode == MachinePlaysWhite &&
13252                  !WhiteOnMove(forwardMostMove)) ||
13253                 (gameMode == MachinePlaysBlack &&
13254                  WhiteOnMove(forwardMostMove))) {
13255                 StopClocks();
13256             }
13257             pausing = TRUE;
13258             ModeHighlight();
13259             break;
13260         }
13261     }
13262 }
13263
13264 void
13265 EditCommentEvent ()
13266 {
13267     char title[MSG_SIZ];
13268
13269     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13270       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13271     } else {
13272       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13273                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13274                parseList[currentMove - 1]);
13275     }
13276
13277     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13278 }
13279
13280
13281 void
13282 EditTagsEvent ()
13283 {
13284     char *tags = PGNTags(&gameInfo);
13285     bookUp = FALSE;
13286     EditTagsPopUp(tags, NULL);
13287     free(tags);
13288 }
13289
13290 void
13291 AnalyzeModeEvent ()
13292 {
13293     if (appData.noChessProgram || gameMode == AnalyzeMode)
13294       return;
13295
13296     if (gameMode != AnalyzeFile) {
13297         if (!appData.icsEngineAnalyze) {
13298                EditGameEvent();
13299                if (gameMode != EditGame) return;
13300         }
13301         ResurrectChessProgram();
13302         SendToProgram("analyze\n", &first);
13303         first.analyzing = TRUE;
13304         /*first.maybeThinking = TRUE;*/
13305         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13306         EngineOutputPopUp();
13307     }
13308     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13309     pausing = FALSE;
13310     ModeHighlight();
13311     SetGameInfo();
13312
13313     StartAnalysisClock();
13314     GetTimeMark(&lastNodeCountTime);
13315     lastNodeCount = 0;
13316 }
13317
13318 void
13319 AnalyzeFileEvent ()
13320 {
13321     if (appData.noChessProgram || gameMode == AnalyzeFile)
13322       return;
13323
13324     if (gameMode != AnalyzeMode) {
13325         EditGameEvent();
13326         if (gameMode != EditGame) return;
13327         ResurrectChessProgram();
13328         SendToProgram("analyze\n", &first);
13329         first.analyzing = TRUE;
13330         /*first.maybeThinking = TRUE;*/
13331         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13332         EngineOutputPopUp();
13333     }
13334     gameMode = AnalyzeFile;
13335     pausing = FALSE;
13336     ModeHighlight();
13337     SetGameInfo();
13338
13339     StartAnalysisClock();
13340     GetTimeMark(&lastNodeCountTime);
13341     lastNodeCount = 0;
13342     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13343 }
13344
13345 void
13346 MachineWhiteEvent ()
13347 {
13348     char buf[MSG_SIZ];
13349     char *bookHit = NULL;
13350
13351     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13352       return;
13353
13354
13355     if (gameMode == PlayFromGameFile ||
13356         gameMode == TwoMachinesPlay  ||
13357         gameMode == Training         ||
13358         gameMode == AnalyzeMode      ||
13359         gameMode == EndOfGame)
13360         EditGameEvent();
13361
13362     if (gameMode == EditPosition)
13363         EditPositionDone(TRUE);
13364
13365     if (!WhiteOnMove(currentMove)) {
13366         DisplayError(_("It is not White's turn"), 0);
13367         return;
13368     }
13369
13370     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13371       ExitAnalyzeMode();
13372
13373     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13374         gameMode == AnalyzeFile)
13375         TruncateGame();
13376
13377     ResurrectChessProgram();    /* in case it isn't running */
13378     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13379         gameMode = MachinePlaysWhite;
13380         ResetClocks();
13381     } else
13382     gameMode = MachinePlaysWhite;
13383     pausing = FALSE;
13384     ModeHighlight();
13385     SetGameInfo();
13386     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13387     DisplayTitle(buf);
13388     if (first.sendName) {
13389       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13390       SendToProgram(buf, &first);
13391     }
13392     if (first.sendTime) {
13393       if (first.useColors) {
13394         SendToProgram("black\n", &first); /*gnu kludge*/
13395       }
13396       SendTimeRemaining(&first, TRUE);
13397     }
13398     if (first.useColors) {
13399       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13400     }
13401     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13402     SetMachineThinkingEnables();
13403     first.maybeThinking = TRUE;
13404     StartClocks();
13405     firstMove = FALSE;
13406
13407     if (appData.autoFlipView && !flipView) {
13408       flipView = !flipView;
13409       DrawPosition(FALSE, NULL);
13410       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13411     }
13412
13413     if(bookHit) { // [HGM] book: simulate book reply
13414         static char bookMove[MSG_SIZ]; // a bit generous?
13415
13416         programStats.nodes = programStats.depth = programStats.time =
13417         programStats.score = programStats.got_only_move = 0;
13418         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13419
13420         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13421         strcat(bookMove, bookHit);
13422         HandleMachineMove(bookMove, &first);
13423     }
13424 }
13425
13426 void
13427 MachineBlackEvent ()
13428 {
13429   char buf[MSG_SIZ];
13430   char *bookHit = NULL;
13431
13432     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13433         return;
13434
13435
13436     if (gameMode == PlayFromGameFile ||
13437         gameMode == TwoMachinesPlay  ||
13438         gameMode == Training         ||
13439         gameMode == AnalyzeMode      ||
13440         gameMode == EndOfGame)
13441         EditGameEvent();
13442
13443     if (gameMode == EditPosition)
13444         EditPositionDone(TRUE);
13445
13446     if (WhiteOnMove(currentMove)) {
13447         DisplayError(_("It is not Black's turn"), 0);
13448         return;
13449     }
13450
13451     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13452       ExitAnalyzeMode();
13453
13454     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13455         gameMode == AnalyzeFile)
13456         TruncateGame();
13457
13458     ResurrectChessProgram();    /* in case it isn't running */
13459     gameMode = MachinePlaysBlack;
13460     pausing = FALSE;
13461     ModeHighlight();
13462     SetGameInfo();
13463     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13464     DisplayTitle(buf);
13465     if (first.sendName) {
13466       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13467       SendToProgram(buf, &first);
13468     }
13469     if (first.sendTime) {
13470       if (first.useColors) {
13471         SendToProgram("white\n", &first); /*gnu kludge*/
13472       }
13473       SendTimeRemaining(&first, FALSE);
13474     }
13475     if (first.useColors) {
13476       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13477     }
13478     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13479     SetMachineThinkingEnables();
13480     first.maybeThinking = TRUE;
13481     StartClocks();
13482
13483     if (appData.autoFlipView && flipView) {
13484       flipView = !flipView;
13485       DrawPosition(FALSE, NULL);
13486       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13487     }
13488     if(bookHit) { // [HGM] book: simulate book reply
13489         static char bookMove[MSG_SIZ]; // a bit generous?
13490
13491         programStats.nodes = programStats.depth = programStats.time =
13492         programStats.score = programStats.got_only_move = 0;
13493         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13494
13495         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13496         strcat(bookMove, bookHit);
13497         HandleMachineMove(bookMove, &first);
13498     }
13499 }
13500
13501
13502 void
13503 DisplayTwoMachinesTitle ()
13504 {
13505     char buf[MSG_SIZ];
13506     if (appData.matchGames > 0) {
13507         if(appData.tourneyFile[0]) {
13508           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13509                    gameInfo.white, _("vs."), gameInfo.black,
13510                    nextGame+1, appData.matchGames+1,
13511                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13512         } else 
13513         if (first.twoMachinesColor[0] == 'w') {
13514           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13515                    gameInfo.white, _("vs."),  gameInfo.black,
13516                    first.matchWins, second.matchWins,
13517                    matchGame - 1 - (first.matchWins + second.matchWins));
13518         } else {
13519           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13520                    gameInfo.white, _("vs."), gameInfo.black,
13521                    second.matchWins, first.matchWins,
13522                    matchGame - 1 - (first.matchWins + second.matchWins));
13523         }
13524     } else {
13525       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13526     }
13527     DisplayTitle(buf);
13528 }
13529
13530 void
13531 SettingsMenuIfReady ()
13532 {
13533   if (second.lastPing != second.lastPong) {
13534     DisplayMessage("", _("Waiting for second chess program"));
13535     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13536     return;
13537   }
13538   ThawUI();
13539   DisplayMessage("", "");
13540   SettingsPopUp(&second);
13541 }
13542
13543 int
13544 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13545 {
13546     char buf[MSG_SIZ];
13547     if (cps->pr == NoProc) {
13548         StartChessProgram(cps);
13549         if (cps->protocolVersion == 1) {
13550           retry();
13551         } else {
13552           /* kludge: allow timeout for initial "feature" command */
13553           FreezeUI();
13554           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13555           DisplayMessage("", buf);
13556           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13557         }
13558         return 1;
13559     }
13560     return 0;
13561 }
13562
13563 void
13564 TwoMachinesEvent P((void))
13565 {
13566     int i;
13567     char buf[MSG_SIZ];
13568     ChessProgramState *onmove;
13569     char *bookHit = NULL;
13570     static int stalling = 0;
13571     TimeMark now;
13572     long wait;
13573
13574     if (appData.noChessProgram) return;
13575
13576     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13577         DisplayError("second engine does not play this", 0);
13578         return;
13579     }
13580
13581     switch (gameMode) {
13582       case TwoMachinesPlay:
13583         return;
13584       case MachinePlaysWhite:
13585       case MachinePlaysBlack:
13586         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13587             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13588             return;
13589         }
13590         /* fall through */
13591       case BeginningOfGame:
13592       case PlayFromGameFile:
13593       case EndOfGame:
13594         EditGameEvent();
13595         if (gameMode != EditGame) return;
13596         break;
13597       case EditPosition:
13598         EditPositionDone(TRUE);
13599         break;
13600       case AnalyzeMode:
13601       case AnalyzeFile:
13602         ExitAnalyzeMode();
13603         break;
13604       case EditGame:
13605       default:
13606         break;
13607     }
13608
13609 //    forwardMostMove = currentMove;
13610     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13611
13612     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13613
13614     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13615     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13616       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13617       return;
13618     }
13619     if(!stalling) {
13620       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13621       SendToProgram("force\n", &second);
13622       stalling = 1;
13623       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13624       return;
13625     }
13626     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13627     if(appData.matchPause>10000 || appData.matchPause<10)
13628                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13629     wait = SubtractTimeMarks(&now, &pauseStart);
13630     if(wait < appData.matchPause) {
13631         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13632         return;
13633     }
13634     // we are now committed to starting the game
13635     stalling = 0;
13636     DisplayMessage("", "");
13637     if (startedFromSetupPosition) {
13638         SendBoard(&second, backwardMostMove);
13639     if (appData.debugMode) {
13640         fprintf(debugFP, "Two Machines\n");
13641     }
13642     }
13643     for (i = backwardMostMove; i < forwardMostMove; i++) {
13644         SendMoveToProgram(i, &second);
13645     }
13646
13647     gameMode = TwoMachinesPlay;
13648     pausing = FALSE;
13649     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13650     SetGameInfo();
13651     DisplayTwoMachinesTitle();
13652     firstMove = TRUE;
13653     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13654         onmove = &first;
13655     } else {
13656         onmove = &second;
13657     }
13658     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13659     SendToProgram(first.computerString, &first);
13660     if (first.sendName) {
13661       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13662       SendToProgram(buf, &first);
13663     }
13664     SendToProgram(second.computerString, &second);
13665     if (second.sendName) {
13666       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13667       SendToProgram(buf, &second);
13668     }
13669
13670     ResetClocks();
13671     if (!first.sendTime || !second.sendTime) {
13672         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13673         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13674     }
13675     if (onmove->sendTime) {
13676       if (onmove->useColors) {
13677         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13678       }
13679       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13680     }
13681     if (onmove->useColors) {
13682       SendToProgram(onmove->twoMachinesColor, onmove);
13683     }
13684     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13685 //    SendToProgram("go\n", onmove);
13686     onmove->maybeThinking = TRUE;
13687     SetMachineThinkingEnables();
13688
13689     StartClocks();
13690
13691     if(bookHit) { // [HGM] book: simulate book reply
13692         static char bookMove[MSG_SIZ]; // a bit generous?
13693
13694         programStats.nodes = programStats.depth = programStats.time =
13695         programStats.score = programStats.got_only_move = 0;
13696         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13697
13698         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13699         strcat(bookMove, bookHit);
13700         savedMessage = bookMove; // args for deferred call
13701         savedState = onmove;
13702         ScheduleDelayedEvent(DeferredBookMove, 1);
13703     }
13704 }
13705
13706 void
13707 TrainingEvent ()
13708 {
13709     if (gameMode == Training) {
13710       SetTrainingModeOff();
13711       gameMode = PlayFromGameFile;
13712       DisplayMessage("", _("Training mode off"));
13713     } else {
13714       gameMode = Training;
13715       animateTraining = appData.animate;
13716
13717       /* make sure we are not already at the end of the game */
13718       if (currentMove < forwardMostMove) {
13719         SetTrainingModeOn();
13720         DisplayMessage("", _("Training mode on"));
13721       } else {
13722         gameMode = PlayFromGameFile;
13723         DisplayError(_("Already at end of game"), 0);
13724       }
13725     }
13726     ModeHighlight();
13727 }
13728
13729 void
13730 IcsClientEvent ()
13731 {
13732     if (!appData.icsActive) return;
13733     switch (gameMode) {
13734       case IcsPlayingWhite:
13735       case IcsPlayingBlack:
13736       case IcsObserving:
13737       case IcsIdle:
13738       case BeginningOfGame:
13739       case IcsExamining:
13740         return;
13741
13742       case EditGame:
13743         break;
13744
13745       case EditPosition:
13746         EditPositionDone(TRUE);
13747         break;
13748
13749       case AnalyzeMode:
13750       case AnalyzeFile:
13751         ExitAnalyzeMode();
13752         break;
13753
13754       default:
13755         EditGameEvent();
13756         break;
13757     }
13758
13759     gameMode = IcsIdle;
13760     ModeHighlight();
13761     return;
13762 }
13763
13764 void
13765 EditGameEvent ()
13766 {
13767     int i;
13768
13769     switch (gameMode) {
13770       case Training:
13771         SetTrainingModeOff();
13772         break;
13773       case MachinePlaysWhite:
13774       case MachinePlaysBlack:
13775       case BeginningOfGame:
13776         SendToProgram("force\n", &first);
13777         SetUserThinkingEnables();
13778         break;
13779       case PlayFromGameFile:
13780         (void) StopLoadGameTimer();
13781         if (gameFileFP != NULL) {
13782             gameFileFP = NULL;
13783         }
13784         break;
13785       case EditPosition:
13786         EditPositionDone(TRUE);
13787         break;
13788       case AnalyzeMode:
13789       case AnalyzeFile:
13790         ExitAnalyzeMode();
13791         SendToProgram("force\n", &first);
13792         break;
13793       case TwoMachinesPlay:
13794         GameEnds(EndOfFile, NULL, GE_PLAYER);
13795         ResurrectChessProgram();
13796         SetUserThinkingEnables();
13797         break;
13798       case EndOfGame:
13799         ResurrectChessProgram();
13800         break;
13801       case IcsPlayingBlack:
13802       case IcsPlayingWhite:
13803         DisplayError(_("Warning: You are still playing a game"), 0);
13804         break;
13805       case IcsObserving:
13806         DisplayError(_("Warning: You are still observing a game"), 0);
13807         break;
13808       case IcsExamining:
13809         DisplayError(_("Warning: You are still examining a game"), 0);
13810         break;
13811       case IcsIdle:
13812         break;
13813       case EditGame:
13814       default:
13815         return;
13816     }
13817
13818     pausing = FALSE;
13819     StopClocks();
13820     first.offeredDraw = second.offeredDraw = 0;
13821
13822     if (gameMode == PlayFromGameFile) {
13823         whiteTimeRemaining = timeRemaining[0][currentMove];
13824         blackTimeRemaining = timeRemaining[1][currentMove];
13825         DisplayTitle("");
13826     }
13827
13828     if (gameMode == MachinePlaysWhite ||
13829         gameMode == MachinePlaysBlack ||
13830         gameMode == TwoMachinesPlay ||
13831         gameMode == EndOfGame) {
13832         i = forwardMostMove;
13833         while (i > currentMove) {
13834             SendToProgram("undo\n", &first);
13835             i--;
13836         }
13837         if(!adjustedClock) {
13838         whiteTimeRemaining = timeRemaining[0][currentMove];
13839         blackTimeRemaining = timeRemaining[1][currentMove];
13840         DisplayBothClocks();
13841         }
13842         if (whiteFlag || blackFlag) {
13843             whiteFlag = blackFlag = 0;
13844         }
13845         DisplayTitle("");
13846     }
13847
13848     gameMode = EditGame;
13849     ModeHighlight();
13850     SetGameInfo();
13851 }
13852
13853
13854 void
13855 EditPositionEvent ()
13856 {
13857     if (gameMode == EditPosition) {
13858         EditGameEvent();
13859         return;
13860     }
13861
13862     EditGameEvent();
13863     if (gameMode != EditGame) return;
13864
13865     gameMode = EditPosition;
13866     ModeHighlight();
13867     SetGameInfo();
13868     if (currentMove > 0)
13869       CopyBoard(boards[0], boards[currentMove]);
13870
13871     blackPlaysFirst = !WhiteOnMove(currentMove);
13872     ResetClocks();
13873     currentMove = forwardMostMove = backwardMostMove = 0;
13874     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13875     DisplayMove(-1);
13876     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13877 }
13878
13879 void
13880 ExitAnalyzeMode ()
13881 {
13882     /* [DM] icsEngineAnalyze - possible call from other functions */
13883     if (appData.icsEngineAnalyze) {
13884         appData.icsEngineAnalyze = FALSE;
13885
13886         DisplayMessage("",_("Close ICS engine analyze..."));
13887     }
13888     if (first.analysisSupport && first.analyzing) {
13889       SendToProgram("exit\n", &first);
13890       first.analyzing = FALSE;
13891     }
13892     thinkOutput[0] = NULLCHAR;
13893 }
13894
13895 void
13896 EditPositionDone (Boolean fakeRights)
13897 {
13898     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13899
13900     startedFromSetupPosition = TRUE;
13901     InitChessProgram(&first, FALSE);
13902     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13903       boards[0][EP_STATUS] = EP_NONE;
13904       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13905     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13906         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13907         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13908       } else boards[0][CASTLING][2] = NoRights;
13909     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13910         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13911         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13912       } else boards[0][CASTLING][5] = NoRights;
13913     }
13914     SendToProgram("force\n", &first);
13915     if (blackPlaysFirst) {
13916         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13917         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13918         currentMove = forwardMostMove = backwardMostMove = 1;
13919         CopyBoard(boards[1], boards[0]);
13920     } else {
13921         currentMove = forwardMostMove = backwardMostMove = 0;
13922     }
13923     SendBoard(&first, forwardMostMove);
13924     if (appData.debugMode) {
13925         fprintf(debugFP, "EditPosDone\n");
13926     }
13927     DisplayTitle("");
13928     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13929     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13930     gameMode = EditGame;
13931     ModeHighlight();
13932     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13933     ClearHighlights(); /* [AS] */
13934 }
13935
13936 /* Pause for `ms' milliseconds */
13937 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13938 void
13939 TimeDelay (long ms)
13940 {
13941     TimeMark m1, m2;
13942
13943     GetTimeMark(&m1);
13944     do {
13945         GetTimeMark(&m2);
13946     } while (SubtractTimeMarks(&m2, &m1) < ms);
13947 }
13948
13949 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13950 void
13951 SendMultiLineToICS (char *buf)
13952 {
13953     char temp[MSG_SIZ+1], *p;
13954     int len;
13955
13956     len = strlen(buf);
13957     if (len > MSG_SIZ)
13958       len = MSG_SIZ;
13959
13960     strncpy(temp, buf, len);
13961     temp[len] = 0;
13962
13963     p = temp;
13964     while (*p) {
13965         if (*p == '\n' || *p == '\r')
13966           *p = ' ';
13967         ++p;
13968     }
13969
13970     strcat(temp, "\n");
13971     SendToICS(temp);
13972     SendToPlayer(temp, strlen(temp));
13973 }
13974
13975 void
13976 SetWhiteToPlayEvent ()
13977 {
13978     if (gameMode == EditPosition) {
13979         blackPlaysFirst = FALSE;
13980         DisplayBothClocks();    /* works because currentMove is 0 */
13981     } else if (gameMode == IcsExamining) {
13982         SendToICS(ics_prefix);
13983         SendToICS("tomove white\n");
13984     }
13985 }
13986
13987 void
13988 SetBlackToPlayEvent ()
13989 {
13990     if (gameMode == EditPosition) {
13991         blackPlaysFirst = TRUE;
13992         currentMove = 1;        /* kludge */
13993         DisplayBothClocks();
13994         currentMove = 0;
13995     } else if (gameMode == IcsExamining) {
13996         SendToICS(ics_prefix);
13997         SendToICS("tomove black\n");
13998     }
13999 }
14000
14001 void
14002 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14003 {
14004     char buf[MSG_SIZ];
14005     ChessSquare piece = boards[0][y][x];
14006
14007     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14008
14009     switch (selection) {
14010       case ClearBoard:
14011         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14012             SendToICS(ics_prefix);
14013             SendToICS("bsetup clear\n");
14014         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14015             SendToICS(ics_prefix);
14016             SendToICS("clearboard\n");
14017         } else {
14018             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14019                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14020                 for (y = 0; y < BOARD_HEIGHT; y++) {
14021                     if (gameMode == IcsExamining) {
14022                         if (boards[currentMove][y][x] != EmptySquare) {
14023                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14024                                     AAA + x, ONE + y);
14025                             SendToICS(buf);
14026                         }
14027                     } else {
14028                         boards[0][y][x] = p;
14029                     }
14030                 }
14031             }
14032         }
14033         if (gameMode == EditPosition) {
14034             DrawPosition(FALSE, boards[0]);
14035         }
14036         break;
14037
14038       case WhitePlay:
14039         SetWhiteToPlayEvent();
14040         break;
14041
14042       case BlackPlay:
14043         SetBlackToPlayEvent();
14044         break;
14045
14046       case EmptySquare:
14047         if (gameMode == IcsExamining) {
14048             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14049             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14050             SendToICS(buf);
14051         } else {
14052             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14053                 if(x == BOARD_LEFT-2) {
14054                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14055                     boards[0][y][1] = 0;
14056                 } else
14057                 if(x == BOARD_RGHT+1) {
14058                     if(y >= gameInfo.holdingsSize) break;
14059                     boards[0][y][BOARD_WIDTH-2] = 0;
14060                 } else break;
14061             }
14062             boards[0][y][x] = EmptySquare;
14063             DrawPosition(FALSE, boards[0]);
14064         }
14065         break;
14066
14067       case PromotePiece:
14068         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14069            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14070             selection = (ChessSquare) (PROMOTED piece);
14071         } else if(piece == EmptySquare) selection = WhiteSilver;
14072         else selection = (ChessSquare)((int)piece - 1);
14073         goto defaultlabel;
14074
14075       case DemotePiece:
14076         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14077            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14078             selection = (ChessSquare) (DEMOTED piece);
14079         } else if(piece == EmptySquare) selection = BlackSilver;
14080         else selection = (ChessSquare)((int)piece + 1);
14081         goto defaultlabel;
14082
14083       case WhiteQueen:
14084       case BlackQueen:
14085         if(gameInfo.variant == VariantShatranj ||
14086            gameInfo.variant == VariantXiangqi  ||
14087            gameInfo.variant == VariantCourier  ||
14088            gameInfo.variant == VariantMakruk     )
14089             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14090         goto defaultlabel;
14091
14092       case WhiteKing:
14093       case BlackKing:
14094         if(gameInfo.variant == VariantXiangqi)
14095             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14096         if(gameInfo.variant == VariantKnightmate)
14097             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14098       default:
14099         defaultlabel:
14100         if (gameMode == IcsExamining) {
14101             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14102             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14103                      PieceToChar(selection), AAA + x, ONE + y);
14104             SendToICS(buf);
14105         } else {
14106             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14107                 int n;
14108                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14109                     n = PieceToNumber(selection - BlackPawn);
14110                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14111                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14112                     boards[0][BOARD_HEIGHT-1-n][1]++;
14113                 } else
14114                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14115                     n = PieceToNumber(selection);
14116                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14117                     boards[0][n][BOARD_WIDTH-1] = selection;
14118                     boards[0][n][BOARD_WIDTH-2]++;
14119                 }
14120             } else
14121             boards[0][y][x] = selection;
14122             DrawPosition(TRUE, boards[0]);
14123             ClearHighlights();
14124             fromX = fromY = -1;
14125         }
14126         break;
14127     }
14128 }
14129
14130
14131 void
14132 DropMenuEvent (ChessSquare selection, int x, int y)
14133 {
14134     ChessMove moveType;
14135
14136     switch (gameMode) {
14137       case IcsPlayingWhite:
14138       case MachinePlaysBlack:
14139         if (!WhiteOnMove(currentMove)) {
14140             DisplayMoveError(_("It is Black's turn"));
14141             return;
14142         }
14143         moveType = WhiteDrop;
14144         break;
14145       case IcsPlayingBlack:
14146       case MachinePlaysWhite:
14147         if (WhiteOnMove(currentMove)) {
14148             DisplayMoveError(_("It is White's turn"));
14149             return;
14150         }
14151         moveType = BlackDrop;
14152         break;
14153       case EditGame:
14154         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14155         break;
14156       default:
14157         return;
14158     }
14159
14160     if (moveType == BlackDrop && selection < BlackPawn) {
14161       selection = (ChessSquare) ((int) selection
14162                                  + (int) BlackPawn - (int) WhitePawn);
14163     }
14164     if (boards[currentMove][y][x] != EmptySquare) {
14165         DisplayMoveError(_("That square is occupied"));
14166         return;
14167     }
14168
14169     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14170 }
14171
14172 void
14173 AcceptEvent ()
14174 {
14175     /* Accept a pending offer of any kind from opponent */
14176
14177     if (appData.icsActive) {
14178         SendToICS(ics_prefix);
14179         SendToICS("accept\n");
14180     } else if (cmailMsgLoaded) {
14181         if (currentMove == cmailOldMove &&
14182             commentList[cmailOldMove] != NULL &&
14183             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14184                    "Black offers a draw" : "White offers a draw")) {
14185             TruncateGame();
14186             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14187             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14188         } else {
14189             DisplayError(_("There is no pending offer on this move"), 0);
14190             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14191         }
14192     } else {
14193         /* Not used for offers from chess program */
14194     }
14195 }
14196
14197 void
14198 DeclineEvent ()
14199 {
14200     /* Decline a pending offer of any kind from opponent */
14201
14202     if (appData.icsActive) {
14203         SendToICS(ics_prefix);
14204         SendToICS("decline\n");
14205     } else if (cmailMsgLoaded) {
14206         if (currentMove == cmailOldMove &&
14207             commentList[cmailOldMove] != NULL &&
14208             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14209                    "Black offers a draw" : "White offers a draw")) {
14210 #ifdef NOTDEF
14211             AppendComment(cmailOldMove, "Draw declined", TRUE);
14212             DisplayComment(cmailOldMove - 1, "Draw declined");
14213 #endif /*NOTDEF*/
14214         } else {
14215             DisplayError(_("There is no pending offer on this move"), 0);
14216         }
14217     } else {
14218         /* Not used for offers from chess program */
14219     }
14220 }
14221
14222 void
14223 RematchEvent ()
14224 {
14225     /* Issue ICS rematch command */
14226     if (appData.icsActive) {
14227         SendToICS(ics_prefix);
14228         SendToICS("rematch\n");
14229     }
14230 }
14231
14232 void
14233 CallFlagEvent ()
14234 {
14235     /* Call your opponent's flag (claim a win on time) */
14236     if (appData.icsActive) {
14237         SendToICS(ics_prefix);
14238         SendToICS("flag\n");
14239     } else {
14240         switch (gameMode) {
14241           default:
14242             return;
14243           case MachinePlaysWhite:
14244             if (whiteFlag) {
14245                 if (blackFlag)
14246                   GameEnds(GameIsDrawn, "Both players ran out of time",
14247                            GE_PLAYER);
14248                 else
14249                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14250             } else {
14251                 DisplayError(_("Your opponent is not out of time"), 0);
14252             }
14253             break;
14254           case MachinePlaysBlack:
14255             if (blackFlag) {
14256                 if (whiteFlag)
14257                   GameEnds(GameIsDrawn, "Both players ran out of time",
14258                            GE_PLAYER);
14259                 else
14260                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14261             } else {
14262                 DisplayError(_("Your opponent is not out of time"), 0);
14263             }
14264             break;
14265         }
14266     }
14267 }
14268
14269 void
14270 ClockClick (int which)
14271 {       // [HGM] code moved to back-end from winboard.c
14272         if(which) { // black clock
14273           if (gameMode == EditPosition || gameMode == IcsExamining) {
14274             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14275             SetBlackToPlayEvent();
14276           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14277           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14278           } else if (shiftKey) {
14279             AdjustClock(which, -1);
14280           } else if (gameMode == IcsPlayingWhite ||
14281                      gameMode == MachinePlaysBlack) {
14282             CallFlagEvent();
14283           }
14284         } else { // white clock
14285           if (gameMode == EditPosition || gameMode == IcsExamining) {
14286             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14287             SetWhiteToPlayEvent();
14288           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14289           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14290           } else if (shiftKey) {
14291             AdjustClock(which, -1);
14292           } else if (gameMode == IcsPlayingBlack ||
14293                    gameMode == MachinePlaysWhite) {
14294             CallFlagEvent();
14295           }
14296         }
14297 }
14298
14299 void
14300 DrawEvent ()
14301 {
14302     /* Offer draw or accept pending draw offer from opponent */
14303
14304     if (appData.icsActive) {
14305         /* Note: tournament rules require draw offers to be
14306            made after you make your move but before you punch
14307            your clock.  Currently ICS doesn't let you do that;
14308            instead, you immediately punch your clock after making
14309            a move, but you can offer a draw at any time. */
14310
14311         SendToICS(ics_prefix);
14312         SendToICS("draw\n");
14313         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14314     } else if (cmailMsgLoaded) {
14315         if (currentMove == cmailOldMove &&
14316             commentList[cmailOldMove] != NULL &&
14317             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14318                    "Black offers a draw" : "White offers a draw")) {
14319             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14320             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14321         } else if (currentMove == cmailOldMove + 1) {
14322             char *offer = WhiteOnMove(cmailOldMove) ?
14323               "White offers a draw" : "Black offers a draw";
14324             AppendComment(currentMove, offer, TRUE);
14325             DisplayComment(currentMove - 1, offer);
14326             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14327         } else {
14328             DisplayError(_("You must make your move before offering a draw"), 0);
14329             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14330         }
14331     } else if (first.offeredDraw) {
14332         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14333     } else {
14334         if (first.sendDrawOffers) {
14335             SendToProgram("draw\n", &first);
14336             userOfferedDraw = TRUE;
14337         }
14338     }
14339 }
14340
14341 void
14342 AdjournEvent ()
14343 {
14344     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14345
14346     if (appData.icsActive) {
14347         SendToICS(ics_prefix);
14348         SendToICS("adjourn\n");
14349     } else {
14350         /* Currently GNU Chess doesn't offer or accept Adjourns */
14351     }
14352 }
14353
14354
14355 void
14356 AbortEvent ()
14357 {
14358     /* Offer Abort or accept pending Abort offer from opponent */
14359
14360     if (appData.icsActive) {
14361         SendToICS(ics_prefix);
14362         SendToICS("abort\n");
14363     } else {
14364         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14365     }
14366 }
14367
14368 void
14369 ResignEvent ()
14370 {
14371     /* Resign.  You can do this even if it's not your turn. */
14372
14373     if (appData.icsActive) {
14374         SendToICS(ics_prefix);
14375         SendToICS("resign\n");
14376     } else {
14377         switch (gameMode) {
14378           case MachinePlaysWhite:
14379             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14380             break;
14381           case MachinePlaysBlack:
14382             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14383             break;
14384           case EditGame:
14385             if (cmailMsgLoaded) {
14386                 TruncateGame();
14387                 if (WhiteOnMove(cmailOldMove)) {
14388                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14389                 } else {
14390                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14391                 }
14392                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14393             }
14394             break;
14395           default:
14396             break;
14397         }
14398     }
14399 }
14400
14401
14402 void
14403 StopObservingEvent ()
14404 {
14405     /* Stop observing current games */
14406     SendToICS(ics_prefix);
14407     SendToICS("unobserve\n");
14408 }
14409
14410 void
14411 StopExaminingEvent ()
14412 {
14413     /* Stop observing current game */
14414     SendToICS(ics_prefix);
14415     SendToICS("unexamine\n");
14416 }
14417
14418 void
14419 ForwardInner (int target)
14420 {
14421     int limit; int oldSeekGraphUp = seekGraphUp;
14422
14423     if (appData.debugMode)
14424         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14425                 target, currentMove, forwardMostMove);
14426
14427     if (gameMode == EditPosition)
14428       return;
14429
14430     seekGraphUp = FALSE;
14431     MarkTargetSquares(1);
14432
14433     if (gameMode == PlayFromGameFile && !pausing)
14434       PauseEvent();
14435
14436     if (gameMode == IcsExamining && pausing)
14437       limit = pauseExamForwardMostMove;
14438     else
14439       limit = forwardMostMove;
14440
14441     if (target > limit) target = limit;
14442
14443     if (target > 0 && moveList[target - 1][0]) {
14444         int fromX, fromY, toX, toY;
14445         toX = moveList[target - 1][2] - AAA;
14446         toY = moveList[target - 1][3] - ONE;
14447         if (moveList[target - 1][1] == '@') {
14448             if (appData.highlightLastMove) {
14449                 SetHighlights(-1, -1, toX, toY);
14450             }
14451         } else {
14452             fromX = moveList[target - 1][0] - AAA;
14453             fromY = moveList[target - 1][1] - ONE;
14454             if (target == currentMove + 1) {
14455                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14456             }
14457             if (appData.highlightLastMove) {
14458                 SetHighlights(fromX, fromY, toX, toY);
14459             }
14460         }
14461     }
14462     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14463         gameMode == Training || gameMode == PlayFromGameFile ||
14464         gameMode == AnalyzeFile) {
14465         while (currentMove < target) {
14466             SendMoveToProgram(currentMove++, &first);
14467         }
14468     } else {
14469         currentMove = target;
14470     }
14471
14472     if (gameMode == EditGame || gameMode == EndOfGame) {
14473         whiteTimeRemaining = timeRemaining[0][currentMove];
14474         blackTimeRemaining = timeRemaining[1][currentMove];
14475     }
14476     DisplayBothClocks();
14477     DisplayMove(currentMove - 1);
14478     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14479     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14480     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14481         DisplayComment(currentMove - 1, commentList[currentMove]);
14482     }
14483     ClearMap(); // [HGM] exclude: invalidate map
14484 }
14485
14486
14487 void
14488 ForwardEvent ()
14489 {
14490     if (gameMode == IcsExamining && !pausing) {
14491         SendToICS(ics_prefix);
14492         SendToICS("forward\n");
14493     } else {
14494         ForwardInner(currentMove + 1);
14495     }
14496 }
14497
14498 void
14499 ToEndEvent ()
14500 {
14501     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14502         /* to optimze, we temporarily turn off analysis mode while we feed
14503          * the remaining moves to the engine. Otherwise we get analysis output
14504          * after each move.
14505          */
14506         if (first.analysisSupport) {
14507           SendToProgram("exit\nforce\n", &first);
14508           first.analyzing = FALSE;
14509         }
14510     }
14511
14512     if (gameMode == IcsExamining && !pausing) {
14513         SendToICS(ics_prefix);
14514         SendToICS("forward 999999\n");
14515     } else {
14516         ForwardInner(forwardMostMove);
14517     }
14518
14519     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14520         /* we have fed all the moves, so reactivate analysis mode */
14521         SendToProgram("analyze\n", &first);
14522         first.analyzing = TRUE;
14523         /*first.maybeThinking = TRUE;*/
14524         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14525     }
14526 }
14527
14528 void
14529 BackwardInner (int target)
14530 {
14531     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14532
14533     if (appData.debugMode)
14534         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14535                 target, currentMove, forwardMostMove);
14536
14537     if (gameMode == EditPosition) return;
14538     seekGraphUp = FALSE;
14539     MarkTargetSquares(1);
14540     if (currentMove <= backwardMostMove) {
14541         ClearHighlights();
14542         DrawPosition(full_redraw, boards[currentMove]);
14543         return;
14544     }
14545     if (gameMode == PlayFromGameFile && !pausing)
14546       PauseEvent();
14547
14548     if (moveList[target][0]) {
14549         int fromX, fromY, toX, toY;
14550         toX = moveList[target][2] - AAA;
14551         toY = moveList[target][3] - ONE;
14552         if (moveList[target][1] == '@') {
14553             if (appData.highlightLastMove) {
14554                 SetHighlights(-1, -1, toX, toY);
14555             }
14556         } else {
14557             fromX = moveList[target][0] - AAA;
14558             fromY = moveList[target][1] - ONE;
14559             if (target == currentMove - 1) {
14560                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14561             }
14562             if (appData.highlightLastMove) {
14563                 SetHighlights(fromX, fromY, toX, toY);
14564             }
14565         }
14566     }
14567     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14568         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14569         while (currentMove > target) {
14570             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14571                 // null move cannot be undone. Reload program with move history before it.
14572                 int i;
14573                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14574                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14575                 }
14576                 SendBoard(&first, i); 
14577                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14578                 break;
14579             }
14580             SendToProgram("undo\n", &first);
14581             currentMove--;
14582         }
14583     } else {
14584         currentMove = target;
14585     }
14586
14587     if (gameMode == EditGame || gameMode == EndOfGame) {
14588         whiteTimeRemaining = timeRemaining[0][currentMove];
14589         blackTimeRemaining = timeRemaining[1][currentMove];
14590     }
14591     DisplayBothClocks();
14592     DisplayMove(currentMove - 1);
14593     DrawPosition(full_redraw, boards[currentMove]);
14594     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14595     // [HGM] PV info: routine tests if comment empty
14596     DisplayComment(currentMove - 1, commentList[currentMove]);
14597     ClearMap(); // [HGM] exclude: invalidate map
14598 }
14599
14600 void
14601 BackwardEvent ()
14602 {
14603     if (gameMode == IcsExamining && !pausing) {
14604         SendToICS(ics_prefix);
14605         SendToICS("backward\n");
14606     } else {
14607         BackwardInner(currentMove - 1);
14608     }
14609 }
14610
14611 void
14612 ToStartEvent ()
14613 {
14614     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14615         /* to optimize, we temporarily turn off analysis mode while we undo
14616          * all the moves. Otherwise we get analysis output after each undo.
14617          */
14618         if (first.analysisSupport) {
14619           SendToProgram("exit\nforce\n", &first);
14620           first.analyzing = FALSE;
14621         }
14622     }
14623
14624     if (gameMode == IcsExamining && !pausing) {
14625         SendToICS(ics_prefix);
14626         SendToICS("backward 999999\n");
14627     } else {
14628         BackwardInner(backwardMostMove);
14629     }
14630
14631     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14632         /* we have fed all the moves, so reactivate analysis mode */
14633         SendToProgram("analyze\n", &first);
14634         first.analyzing = TRUE;
14635         /*first.maybeThinking = TRUE;*/
14636         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14637     }
14638 }
14639
14640 void
14641 ToNrEvent (int to)
14642 {
14643   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14644   if (to >= forwardMostMove) to = forwardMostMove;
14645   if (to <= backwardMostMove) to = backwardMostMove;
14646   if (to < currentMove) {
14647     BackwardInner(to);
14648   } else {
14649     ForwardInner(to);
14650   }
14651 }
14652
14653 void
14654 RevertEvent (Boolean annotate)
14655 {
14656     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14657         return;
14658     }
14659     if (gameMode != IcsExamining) {
14660         DisplayError(_("You are not examining a game"), 0);
14661         return;
14662     }
14663     if (pausing) {
14664         DisplayError(_("You can't revert while pausing"), 0);
14665         return;
14666     }
14667     SendToICS(ics_prefix);
14668     SendToICS("revert\n");
14669 }
14670
14671 void
14672 RetractMoveEvent ()
14673 {
14674     switch (gameMode) {
14675       case MachinePlaysWhite:
14676       case MachinePlaysBlack:
14677         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14678             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14679             return;
14680         }
14681         if (forwardMostMove < 2) return;
14682         currentMove = forwardMostMove = forwardMostMove - 2;
14683         whiteTimeRemaining = timeRemaining[0][currentMove];
14684         blackTimeRemaining = timeRemaining[1][currentMove];
14685         DisplayBothClocks();
14686         DisplayMove(currentMove - 1);
14687         ClearHighlights();/*!! could figure this out*/
14688         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14689         SendToProgram("remove\n", &first);
14690         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14691         break;
14692
14693       case BeginningOfGame:
14694       default:
14695         break;
14696
14697       case IcsPlayingWhite:
14698       case IcsPlayingBlack:
14699         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14700             SendToICS(ics_prefix);
14701             SendToICS("takeback 2\n");
14702         } else {
14703             SendToICS(ics_prefix);
14704             SendToICS("takeback 1\n");
14705         }
14706         break;
14707     }
14708 }
14709
14710 void
14711 MoveNowEvent ()
14712 {
14713     ChessProgramState *cps;
14714
14715     switch (gameMode) {
14716       case MachinePlaysWhite:
14717         if (!WhiteOnMove(forwardMostMove)) {
14718             DisplayError(_("It is your turn"), 0);
14719             return;
14720         }
14721         cps = &first;
14722         break;
14723       case MachinePlaysBlack:
14724         if (WhiteOnMove(forwardMostMove)) {
14725             DisplayError(_("It is your turn"), 0);
14726             return;
14727         }
14728         cps = &first;
14729         break;
14730       case TwoMachinesPlay:
14731         if (WhiteOnMove(forwardMostMove) ==
14732             (first.twoMachinesColor[0] == 'w')) {
14733             cps = &first;
14734         } else {
14735             cps = &second;
14736         }
14737         break;
14738       case BeginningOfGame:
14739       default:
14740         return;
14741     }
14742     SendToProgram("?\n", cps);
14743 }
14744
14745 void
14746 TruncateGameEvent ()
14747 {
14748     EditGameEvent();
14749     if (gameMode != EditGame) return;
14750     TruncateGame();
14751 }
14752
14753 void
14754 TruncateGame ()
14755 {
14756     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14757     if (forwardMostMove > currentMove) {
14758         if (gameInfo.resultDetails != NULL) {
14759             free(gameInfo.resultDetails);
14760             gameInfo.resultDetails = NULL;
14761             gameInfo.result = GameUnfinished;
14762         }
14763         forwardMostMove = currentMove;
14764         HistorySet(parseList, backwardMostMove, forwardMostMove,
14765                    currentMove-1);
14766     }
14767 }
14768
14769 void
14770 HintEvent ()
14771 {
14772     if (appData.noChessProgram) return;
14773     switch (gameMode) {
14774       case MachinePlaysWhite:
14775         if (WhiteOnMove(forwardMostMove)) {
14776             DisplayError(_("Wait until your turn"), 0);
14777             return;
14778         }
14779         break;
14780       case BeginningOfGame:
14781       case MachinePlaysBlack:
14782         if (!WhiteOnMove(forwardMostMove)) {
14783             DisplayError(_("Wait until your turn"), 0);
14784             return;
14785         }
14786         break;
14787       default:
14788         DisplayError(_("No hint available"), 0);
14789         return;
14790     }
14791     SendToProgram("hint\n", &first);
14792     hintRequested = TRUE;
14793 }
14794
14795 void
14796 BookEvent ()
14797 {
14798     if (appData.noChessProgram) return;
14799     switch (gameMode) {
14800       case MachinePlaysWhite:
14801         if (WhiteOnMove(forwardMostMove)) {
14802             DisplayError(_("Wait until your turn"), 0);
14803             return;
14804         }
14805         break;
14806       case BeginningOfGame:
14807       case MachinePlaysBlack:
14808         if (!WhiteOnMove(forwardMostMove)) {
14809             DisplayError(_("Wait until your turn"), 0);
14810             return;
14811         }
14812         break;
14813       case EditPosition:
14814         EditPositionDone(TRUE);
14815         break;
14816       case TwoMachinesPlay:
14817         return;
14818       default:
14819         break;
14820     }
14821     SendToProgram("bk\n", &first);
14822     bookOutput[0] = NULLCHAR;
14823     bookRequested = TRUE;
14824 }
14825
14826 void
14827 AboutGameEvent ()
14828 {
14829     char *tags = PGNTags(&gameInfo);
14830     TagsPopUp(tags, CmailMsg());
14831     free(tags);
14832 }
14833
14834 /* end button procedures */
14835
14836 void
14837 PrintPosition (FILE *fp, int move)
14838 {
14839     int i, j;
14840
14841     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14842         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14843             char c = PieceToChar(boards[move][i][j]);
14844             fputc(c == 'x' ? '.' : c, fp);
14845             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14846         }
14847     }
14848     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14849       fprintf(fp, "white to play\n");
14850     else
14851       fprintf(fp, "black to play\n");
14852 }
14853
14854 void
14855 PrintOpponents (FILE *fp)
14856 {
14857     if (gameInfo.white != NULL) {
14858         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14859     } else {
14860         fprintf(fp, "\n");
14861     }
14862 }
14863
14864 /* Find last component of program's own name, using some heuristics */
14865 void
14866 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14867 {
14868     char *p, *q, c;
14869     int local = (strcmp(host, "localhost") == 0);
14870     while (!local && (p = strchr(prog, ';')) != NULL) {
14871         p++;
14872         while (*p == ' ') p++;
14873         prog = p;
14874     }
14875     if (*prog == '"' || *prog == '\'') {
14876         q = strchr(prog + 1, *prog);
14877     } else {
14878         q = strchr(prog, ' ');
14879     }
14880     if (q == NULL) q = prog + strlen(prog);
14881     p = q;
14882     while (p >= prog && *p != '/' && *p != '\\') p--;
14883     p++;
14884     if(p == prog && *p == '"') p++;
14885     c = *q; *q = 0;
14886     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14887     memcpy(buf, p, q - p);
14888     buf[q - p] = NULLCHAR;
14889     if (!local) {
14890         strcat(buf, "@");
14891         strcat(buf, host);
14892     }
14893 }
14894
14895 char *
14896 TimeControlTagValue ()
14897 {
14898     char buf[MSG_SIZ];
14899     if (!appData.clockMode) {
14900       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14901     } else if (movesPerSession > 0) {
14902       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14903     } else if (timeIncrement == 0) {
14904       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14905     } else {
14906       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14907     }
14908     return StrSave(buf);
14909 }
14910
14911 void
14912 SetGameInfo ()
14913 {
14914     /* This routine is used only for certain modes */
14915     VariantClass v = gameInfo.variant;
14916     ChessMove r = GameUnfinished;
14917     char *p = NULL;
14918
14919     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14920         r = gameInfo.result;
14921         p = gameInfo.resultDetails;
14922         gameInfo.resultDetails = NULL;
14923     }
14924     ClearGameInfo(&gameInfo);
14925     gameInfo.variant = v;
14926
14927     switch (gameMode) {
14928       case MachinePlaysWhite:
14929         gameInfo.event = StrSave( appData.pgnEventHeader );
14930         gameInfo.site = StrSave(HostName());
14931         gameInfo.date = PGNDate();
14932         gameInfo.round = StrSave("-");
14933         gameInfo.white = StrSave(first.tidy);
14934         gameInfo.black = StrSave(UserName());
14935         gameInfo.timeControl = TimeControlTagValue();
14936         break;
14937
14938       case MachinePlaysBlack:
14939         gameInfo.event = StrSave( appData.pgnEventHeader );
14940         gameInfo.site = StrSave(HostName());
14941         gameInfo.date = PGNDate();
14942         gameInfo.round = StrSave("-");
14943         gameInfo.white = StrSave(UserName());
14944         gameInfo.black = StrSave(first.tidy);
14945         gameInfo.timeControl = TimeControlTagValue();
14946         break;
14947
14948       case TwoMachinesPlay:
14949         gameInfo.event = StrSave( appData.pgnEventHeader );
14950         gameInfo.site = StrSave(HostName());
14951         gameInfo.date = PGNDate();
14952         if (roundNr > 0) {
14953             char buf[MSG_SIZ];
14954             snprintf(buf, MSG_SIZ, "%d", roundNr);
14955             gameInfo.round = StrSave(buf);
14956         } else {
14957             gameInfo.round = StrSave("-");
14958         }
14959         if (first.twoMachinesColor[0] == 'w') {
14960             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14961             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14962         } else {
14963             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14964             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14965         }
14966         gameInfo.timeControl = TimeControlTagValue();
14967         break;
14968
14969       case EditGame:
14970         gameInfo.event = StrSave("Edited game");
14971         gameInfo.site = StrSave(HostName());
14972         gameInfo.date = PGNDate();
14973         gameInfo.round = StrSave("-");
14974         gameInfo.white = StrSave("-");
14975         gameInfo.black = StrSave("-");
14976         gameInfo.result = r;
14977         gameInfo.resultDetails = p;
14978         break;
14979
14980       case EditPosition:
14981         gameInfo.event = StrSave("Edited position");
14982         gameInfo.site = StrSave(HostName());
14983         gameInfo.date = PGNDate();
14984         gameInfo.round = StrSave("-");
14985         gameInfo.white = StrSave("-");
14986         gameInfo.black = StrSave("-");
14987         break;
14988
14989       case IcsPlayingWhite:
14990       case IcsPlayingBlack:
14991       case IcsObserving:
14992       case IcsExamining:
14993         break;
14994
14995       case PlayFromGameFile:
14996         gameInfo.event = StrSave("Game from non-PGN file");
14997         gameInfo.site = StrSave(HostName());
14998         gameInfo.date = PGNDate();
14999         gameInfo.round = StrSave("-");
15000         gameInfo.white = StrSave("?");
15001         gameInfo.black = StrSave("?");
15002         break;
15003
15004       default:
15005         break;
15006     }
15007 }
15008
15009 void
15010 ReplaceComment (int index, char *text)
15011 {
15012     int len;
15013     char *p;
15014     float score;
15015
15016     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15017        pvInfoList[index-1].depth == len &&
15018        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15019        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15020     while (*text == '\n') text++;
15021     len = strlen(text);
15022     while (len > 0 && text[len - 1] == '\n') len--;
15023
15024     if (commentList[index] != NULL)
15025       free(commentList[index]);
15026
15027     if (len == 0) {
15028         commentList[index] = NULL;
15029         return;
15030     }
15031   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15032       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15033       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15034     commentList[index] = (char *) malloc(len + 2);
15035     strncpy(commentList[index], text, len);
15036     commentList[index][len] = '\n';
15037     commentList[index][len + 1] = NULLCHAR;
15038   } else {
15039     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15040     char *p;
15041     commentList[index] = (char *) malloc(len + 7);
15042     safeStrCpy(commentList[index], "{\n", 3);
15043     safeStrCpy(commentList[index]+2, text, len+1);
15044     commentList[index][len+2] = NULLCHAR;
15045     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15046     strcat(commentList[index], "\n}\n");
15047   }
15048 }
15049
15050 void
15051 CrushCRs (char *text)
15052 {
15053   char *p = text;
15054   char *q = text;
15055   char ch;
15056
15057   do {
15058     ch = *p++;
15059     if (ch == '\r') continue;
15060     *q++ = ch;
15061   } while (ch != '\0');
15062 }
15063
15064 void
15065 AppendComment (int index, char *text, Boolean addBraces)
15066 /* addBraces  tells if we should add {} */
15067 {
15068     int oldlen, len;
15069     char *old;
15070
15071 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15072     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15073
15074     CrushCRs(text);
15075     while (*text == '\n') text++;
15076     len = strlen(text);
15077     while (len > 0 && text[len - 1] == '\n') len--;
15078     text[len] = NULLCHAR;
15079
15080     if (len == 0) return;
15081
15082     if (commentList[index] != NULL) {
15083       Boolean addClosingBrace = addBraces;
15084         old = commentList[index];
15085         oldlen = strlen(old);
15086         while(commentList[index][oldlen-1] ==  '\n')
15087           commentList[index][--oldlen] = NULLCHAR;
15088         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15089         safeStrCpy(commentList[index], old, oldlen + len + 6);
15090         free(old);
15091         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15092         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15093           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15094           while (*text == '\n') { text++; len--; }
15095           commentList[index][--oldlen] = NULLCHAR;
15096       }
15097         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15098         else          strcat(commentList[index], "\n");
15099         strcat(commentList[index], text);
15100         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15101         else          strcat(commentList[index], "\n");
15102     } else {
15103         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15104         if(addBraces)
15105           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15106         else commentList[index][0] = NULLCHAR;
15107         strcat(commentList[index], text);
15108         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15109         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15110     }
15111 }
15112
15113 static char *
15114 FindStr (char * text, char * sub_text)
15115 {
15116     char * result = strstr( text, sub_text );
15117
15118     if( result != NULL ) {
15119         result += strlen( sub_text );
15120     }
15121
15122     return result;
15123 }
15124
15125 /* [AS] Try to extract PV info from PGN comment */
15126 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15127 char *
15128 GetInfoFromComment (int index, char * text)
15129 {
15130     char * sep = text, *p;
15131
15132     if( text != NULL && index > 0 ) {
15133         int score = 0;
15134         int depth = 0;
15135         int time = -1, sec = 0, deci;
15136         char * s_eval = FindStr( text, "[%eval " );
15137         char * s_emt = FindStr( text, "[%emt " );
15138
15139         if( s_eval != NULL || s_emt != NULL ) {
15140             /* New style */
15141             char delim;
15142
15143             if( s_eval != NULL ) {
15144                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15145                     return text;
15146                 }
15147
15148                 if( delim != ']' ) {
15149                     return text;
15150                 }
15151             }
15152
15153             if( s_emt != NULL ) {
15154             }
15155                 return text;
15156         }
15157         else {
15158             /* We expect something like: [+|-]nnn.nn/dd */
15159             int score_lo = 0;
15160
15161             if(*text != '{') return text; // [HGM] braces: must be normal comment
15162
15163             sep = strchr( text, '/' );
15164             if( sep == NULL || sep < (text+4) ) {
15165                 return text;
15166             }
15167
15168             p = text;
15169             if(p[1] == '(') { // comment starts with PV
15170                p = strchr(p, ')'); // locate end of PV
15171                if(p == NULL || sep < p+5) return text;
15172                // at this point we have something like "{(.*) +0.23/6 ..."
15173                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15174                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15175                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15176             }
15177             time = -1; sec = -1; deci = -1;
15178             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15179                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15180                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15181                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15182                 return text;
15183             }
15184
15185             if( score_lo < 0 || score_lo >= 100 ) {
15186                 return text;
15187             }
15188
15189             if(sec >= 0) time = 600*time + 10*sec; else
15190             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15191
15192             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15193
15194             /* [HGM] PV time: now locate end of PV info */
15195             while( *++sep >= '0' && *sep <= '9'); // strip depth
15196             if(time >= 0)
15197             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15198             if(sec >= 0)
15199             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15200             if(deci >= 0)
15201             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15202             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15203         }
15204
15205         if( depth <= 0 ) {
15206             return text;
15207         }
15208
15209         if( time < 0 ) {
15210             time = -1;
15211         }
15212
15213         pvInfoList[index-1].depth = depth;
15214         pvInfoList[index-1].score = score;
15215         pvInfoList[index-1].time  = 10*time; // centi-sec
15216         if(*sep == '}') *sep = 0; else *--sep = '{';
15217         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15218     }
15219     return sep;
15220 }
15221
15222 void
15223 SendToProgram (char *message, ChessProgramState *cps)
15224 {
15225     int count, outCount, error;
15226     char buf[MSG_SIZ];
15227
15228     if (cps->pr == NoProc) return;
15229     Attention(cps);
15230
15231     if (appData.debugMode) {
15232         TimeMark now;
15233         GetTimeMark(&now);
15234         fprintf(debugFP, "%ld >%-6s: %s",
15235                 SubtractTimeMarks(&now, &programStartTime),
15236                 cps->which, message);
15237         if(serverFP)
15238             fprintf(serverFP, "%ld >%-6s: %s",
15239                 SubtractTimeMarks(&now, &programStartTime),
15240                 cps->which, message), fflush(serverFP);
15241     }
15242
15243     count = strlen(message);
15244     outCount = OutputToProcess(cps->pr, message, count, &error);
15245     if (outCount < count && !exiting
15246                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15247       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15248       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15249         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15250             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15251                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15252                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15253                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15254             } else {
15255                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15256                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15257                 gameInfo.result = res;
15258             }
15259             gameInfo.resultDetails = StrSave(buf);
15260         }
15261         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15262         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15263     }
15264 }
15265
15266 void
15267 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15268 {
15269     char *end_str;
15270     char buf[MSG_SIZ];
15271     ChessProgramState *cps = (ChessProgramState *)closure;
15272
15273     if (isr != cps->isr) return; /* Killed intentionally */
15274     if (count <= 0) {
15275         if (count == 0) {
15276             RemoveInputSource(cps->isr);
15277             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15278                     _(cps->which), cps->program);
15279             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15280             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15281                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15282                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15283                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15284                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15285                 } else {
15286                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15287                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15288                     gameInfo.result = res;
15289                 }
15290                 gameInfo.resultDetails = StrSave(buf);
15291             }
15292             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15293             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15294         } else {
15295             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15296                     _(cps->which), cps->program);
15297             RemoveInputSource(cps->isr);
15298
15299             /* [AS] Program is misbehaving badly... kill it */
15300             if( count == -2 ) {
15301                 DestroyChildProcess( cps->pr, 9 );
15302                 cps->pr = NoProc;
15303             }
15304
15305             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15306         }
15307         return;
15308     }
15309
15310     if ((end_str = strchr(message, '\r')) != NULL)
15311       *end_str = NULLCHAR;
15312     if ((end_str = strchr(message, '\n')) != NULL)
15313       *end_str = NULLCHAR;
15314
15315     if (appData.debugMode) {
15316         TimeMark now; int print = 1;
15317         char *quote = ""; char c; int i;
15318
15319         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15320                 char start = message[0];
15321                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15322                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15323                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15324                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15325                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15326                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15327                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15328                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15329                    sscanf(message, "hint: %c", &c)!=1 && 
15330                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15331                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15332                     print = (appData.engineComments >= 2);
15333                 }
15334                 message[0] = start; // restore original message
15335         }
15336         if(print) {
15337                 GetTimeMark(&now);
15338                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15339                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15340                         quote,
15341                         message);
15342                 if(serverFP)
15343                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15344                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15345                         quote,
15346                         message), fflush(serverFP);
15347         }
15348     }
15349
15350     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15351     if (appData.icsEngineAnalyze) {
15352         if (strstr(message, "whisper") != NULL ||
15353              strstr(message, "kibitz") != NULL ||
15354             strstr(message, "tellics") != NULL) return;
15355     }
15356
15357     HandleMachineMove(message, cps);
15358 }
15359
15360
15361 void
15362 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15363 {
15364     char buf[MSG_SIZ];
15365     int seconds;
15366
15367     if( timeControl_2 > 0 ) {
15368         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15369             tc = timeControl_2;
15370         }
15371     }
15372     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15373     inc /= cps->timeOdds;
15374     st  /= cps->timeOdds;
15375
15376     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15377
15378     if (st > 0) {
15379       /* Set exact time per move, normally using st command */
15380       if (cps->stKludge) {
15381         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15382         seconds = st % 60;
15383         if (seconds == 0) {
15384           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15385         } else {
15386           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15387         }
15388       } else {
15389         snprintf(buf, MSG_SIZ, "st %d\n", st);
15390       }
15391     } else {
15392       /* Set conventional or incremental time control, using level command */
15393       if (seconds == 0) {
15394         /* Note old gnuchess bug -- minutes:seconds used to not work.
15395            Fixed in later versions, but still avoid :seconds
15396            when seconds is 0. */
15397         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15398       } else {
15399         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15400                  seconds, inc/1000.);
15401       }
15402     }
15403     SendToProgram(buf, cps);
15404
15405     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15406     /* Orthogonally, limit search to given depth */
15407     if (sd > 0) {
15408       if (cps->sdKludge) {
15409         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15410       } else {
15411         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15412       }
15413       SendToProgram(buf, cps);
15414     }
15415
15416     if(cps->nps >= 0) { /* [HGM] nps */
15417         if(cps->supportsNPS == FALSE)
15418           cps->nps = -1; // don't use if engine explicitly says not supported!
15419         else {
15420           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15421           SendToProgram(buf, cps);
15422         }
15423     }
15424 }
15425
15426 ChessProgramState *
15427 WhitePlayer ()
15428 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15429 {
15430     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15431        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15432         return &second;
15433     return &first;
15434 }
15435
15436 void
15437 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15438 {
15439     char message[MSG_SIZ];
15440     long time, otime;
15441
15442     /* Note: this routine must be called when the clocks are stopped
15443        or when they have *just* been set or switched; otherwise
15444        it will be off by the time since the current tick started.
15445     */
15446     if (machineWhite) {
15447         time = whiteTimeRemaining / 10;
15448         otime = blackTimeRemaining / 10;
15449     } else {
15450         time = blackTimeRemaining / 10;
15451         otime = whiteTimeRemaining / 10;
15452     }
15453     /* [HGM] translate opponent's time by time-odds factor */
15454     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15455
15456     if (time <= 0) time = 1;
15457     if (otime <= 0) otime = 1;
15458
15459     snprintf(message, MSG_SIZ, "time %ld\n", time);
15460     SendToProgram(message, cps);
15461
15462     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15463     SendToProgram(message, cps);
15464 }
15465
15466 int
15467 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15468 {
15469   char buf[MSG_SIZ];
15470   int len = strlen(name);
15471   int val;
15472
15473   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15474     (*p) += len + 1;
15475     sscanf(*p, "%d", &val);
15476     *loc = (val != 0);
15477     while (**p && **p != ' ')
15478       (*p)++;
15479     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15480     SendToProgram(buf, cps);
15481     return TRUE;
15482   }
15483   return FALSE;
15484 }
15485
15486 int
15487 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15488 {
15489   char buf[MSG_SIZ];
15490   int len = strlen(name);
15491   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15492     (*p) += len + 1;
15493     sscanf(*p, "%d", loc);
15494     while (**p && **p != ' ') (*p)++;
15495     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15496     SendToProgram(buf, cps);
15497     return TRUE;
15498   }
15499   return FALSE;
15500 }
15501
15502 int
15503 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15504 {
15505   char buf[MSG_SIZ];
15506   int len = strlen(name);
15507   if (strncmp((*p), name, len) == 0
15508       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15509     (*p) += len + 2;
15510     sscanf(*p, "%[^\"]", loc);
15511     while (**p && **p != '\"') (*p)++;
15512     if (**p == '\"') (*p)++;
15513     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15514     SendToProgram(buf, cps);
15515     return TRUE;
15516   }
15517   return FALSE;
15518 }
15519
15520 int
15521 ParseOption (Option *opt, ChessProgramState *cps)
15522 // [HGM] options: process the string that defines an engine option, and determine
15523 // name, type, default value, and allowed value range
15524 {
15525         char *p, *q, buf[MSG_SIZ];
15526         int n, min = (-1)<<31, max = 1<<31, def;
15527
15528         if(p = strstr(opt->name, " -spin ")) {
15529             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15530             if(max < min) max = min; // enforce consistency
15531             if(def < min) def = min;
15532             if(def > max) def = max;
15533             opt->value = def;
15534             opt->min = min;
15535             opt->max = max;
15536             opt->type = Spin;
15537         } else if((p = strstr(opt->name, " -slider "))) {
15538             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15539             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15540             if(max < min) max = min; // enforce consistency
15541             if(def < min) def = min;
15542             if(def > max) def = max;
15543             opt->value = def;
15544             opt->min = min;
15545             opt->max = max;
15546             opt->type = Spin; // Slider;
15547         } else if((p = strstr(opt->name, " -string "))) {
15548             opt->textValue = p+9;
15549             opt->type = TextBox;
15550         } else if((p = strstr(opt->name, " -file "))) {
15551             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15552             opt->textValue = p+7;
15553             opt->type = FileName; // FileName;
15554         } else if((p = strstr(opt->name, " -path "))) {
15555             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15556             opt->textValue = p+7;
15557             opt->type = PathName; // PathName;
15558         } else if(p = strstr(opt->name, " -check ")) {
15559             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15560             opt->value = (def != 0);
15561             opt->type = CheckBox;
15562         } else if(p = strstr(opt->name, " -combo ")) {
15563             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15564             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15565             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15566             opt->value = n = 0;
15567             while(q = StrStr(q, " /// ")) {
15568                 n++; *q = 0;    // count choices, and null-terminate each of them
15569                 q += 5;
15570                 if(*q == '*') { // remember default, which is marked with * prefix
15571                     q++;
15572                     opt->value = n;
15573                 }
15574                 cps->comboList[cps->comboCnt++] = q;
15575             }
15576             cps->comboList[cps->comboCnt++] = NULL;
15577             opt->max = n + 1;
15578             opt->type = ComboBox;
15579         } else if(p = strstr(opt->name, " -button")) {
15580             opt->type = Button;
15581         } else if(p = strstr(opt->name, " -save")) {
15582             opt->type = SaveButton;
15583         } else return FALSE;
15584         *p = 0; // terminate option name
15585         // now look if the command-line options define a setting for this engine option.
15586         if(cps->optionSettings && cps->optionSettings[0])
15587             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15588         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15589           snprintf(buf, MSG_SIZ, "option %s", p);
15590                 if(p = strstr(buf, ",")) *p = 0;
15591                 if(q = strchr(buf, '=')) switch(opt->type) {
15592                     case ComboBox:
15593                         for(n=0; n<opt->max; n++)
15594                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15595                         break;
15596                     case TextBox:
15597                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15598                         break;
15599                     case Spin:
15600                     case CheckBox:
15601                         opt->value = atoi(q+1);
15602                     default:
15603                         break;
15604                 }
15605                 strcat(buf, "\n");
15606                 SendToProgram(buf, cps);
15607         }
15608         return TRUE;
15609 }
15610
15611 void
15612 FeatureDone (ChessProgramState *cps, int val)
15613 {
15614   DelayedEventCallback cb = GetDelayedEvent();
15615   if ((cb == InitBackEnd3 && cps == &first) ||
15616       (cb == SettingsMenuIfReady && cps == &second) ||
15617       (cb == LoadEngine) ||
15618       (cb == TwoMachinesEventIfReady)) {
15619     CancelDelayedEvent();
15620     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15621   }
15622   cps->initDone = val;
15623 }
15624
15625 /* Parse feature command from engine */
15626 void
15627 ParseFeatures (char *args, ChessProgramState *cps)
15628 {
15629   char *p = args;
15630   char *q;
15631   int val;
15632   char buf[MSG_SIZ];
15633
15634   for (;;) {
15635     while (*p == ' ') p++;
15636     if (*p == NULLCHAR) return;
15637
15638     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15639     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15640     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15641     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15642     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15643     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15644     if (BoolFeature(&p, "reuse", &val, cps)) {
15645       /* Engine can disable reuse, but can't enable it if user said no */
15646       if (!val) cps->reuse = FALSE;
15647       continue;
15648     }
15649     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15650     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15651       if (gameMode == TwoMachinesPlay) {
15652         DisplayTwoMachinesTitle();
15653       } else {
15654         DisplayTitle("");
15655       }
15656       continue;
15657     }
15658     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15659     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15660     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15661     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15662     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15663     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15664     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15665     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15666     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15667     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15668     if (IntFeature(&p, "done", &val, cps)) {
15669       FeatureDone(cps, val);
15670       continue;
15671     }
15672     /* Added by Tord: */
15673     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15674     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15675     /* End of additions by Tord */
15676
15677     /* [HGM] added features: */
15678     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15679     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15680     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15681     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15682     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15683     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15684     if (StringFeature(&p, "option", buf, cps)) {
15685         FREE(cps->option[cps->nrOptions].name);
15686         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15687         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15688         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15689           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15690             SendToProgram(buf, cps);
15691             continue;
15692         }
15693         if(cps->nrOptions >= MAX_OPTIONS) {
15694             cps->nrOptions--;
15695             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15696             DisplayError(buf, 0);
15697         }
15698         continue;
15699     }
15700     /* End of additions by HGM */
15701
15702     /* unknown feature: complain and skip */
15703     q = p;
15704     while (*q && *q != '=') q++;
15705     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15706     SendToProgram(buf, cps);
15707     p = q;
15708     if (*p == '=') {
15709       p++;
15710       if (*p == '\"') {
15711         p++;
15712         while (*p && *p != '\"') p++;
15713         if (*p == '\"') p++;
15714       } else {
15715         while (*p && *p != ' ') p++;
15716       }
15717     }
15718   }
15719
15720 }
15721
15722 void
15723 PeriodicUpdatesEvent (int newState)
15724 {
15725     if (newState == appData.periodicUpdates)
15726       return;
15727
15728     appData.periodicUpdates=newState;
15729
15730     /* Display type changes, so update it now */
15731 //    DisplayAnalysis();
15732
15733     /* Get the ball rolling again... */
15734     if (newState) {
15735         AnalysisPeriodicEvent(1);
15736         StartAnalysisClock();
15737     }
15738 }
15739
15740 void
15741 PonderNextMoveEvent (int newState)
15742 {
15743     if (newState == appData.ponderNextMove) return;
15744     if (gameMode == EditPosition) EditPositionDone(TRUE);
15745     if (newState) {
15746         SendToProgram("hard\n", &first);
15747         if (gameMode == TwoMachinesPlay) {
15748             SendToProgram("hard\n", &second);
15749         }
15750     } else {
15751         SendToProgram("easy\n", &first);
15752         thinkOutput[0] = NULLCHAR;
15753         if (gameMode == TwoMachinesPlay) {
15754             SendToProgram("easy\n", &second);
15755         }
15756     }
15757     appData.ponderNextMove = newState;
15758 }
15759
15760 void
15761 NewSettingEvent (int option, int *feature, char *command, int value)
15762 {
15763     char buf[MSG_SIZ];
15764
15765     if (gameMode == EditPosition) EditPositionDone(TRUE);
15766     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15767     if(feature == NULL || *feature) SendToProgram(buf, &first);
15768     if (gameMode == TwoMachinesPlay) {
15769         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15770     }
15771 }
15772
15773 void
15774 ShowThinkingEvent ()
15775 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15776 {
15777     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15778     int newState = appData.showThinking
15779         // [HGM] thinking: other features now need thinking output as well
15780         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15781
15782     if (oldState == newState) return;
15783     oldState = newState;
15784     if (gameMode == EditPosition) EditPositionDone(TRUE);
15785     if (oldState) {
15786         SendToProgram("post\n", &first);
15787         if (gameMode == TwoMachinesPlay) {
15788             SendToProgram("post\n", &second);
15789         }
15790     } else {
15791         SendToProgram("nopost\n", &first);
15792         thinkOutput[0] = NULLCHAR;
15793         if (gameMode == TwoMachinesPlay) {
15794             SendToProgram("nopost\n", &second);
15795         }
15796     }
15797 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15798 }
15799
15800 void
15801 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15802 {
15803   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15804   if (pr == NoProc) return;
15805   AskQuestion(title, question, replyPrefix, pr);
15806 }
15807
15808 void
15809 TypeInEvent (char firstChar)
15810 {
15811     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15812         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15813         gameMode == AnalyzeMode || gameMode == EditGame || 
15814         gameMode == EditPosition || gameMode == IcsExamining ||
15815         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15816         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15817                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15818                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15819         gameMode == Training) PopUpMoveDialog(firstChar);
15820 }
15821
15822 void
15823 TypeInDoneEvent (char *move)
15824 {
15825         Board board;
15826         int n, fromX, fromY, toX, toY;
15827         char promoChar;
15828         ChessMove moveType;
15829
15830         // [HGM] FENedit
15831         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15832                 EditPositionPasteFEN(move);
15833                 return;
15834         }
15835         // [HGM] movenum: allow move number to be typed in any mode
15836         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15837           ToNrEvent(2*n-1);
15838           return;
15839         }
15840         // undocumented kludge: allow command-line option to be typed in!
15841         // (potentially fatal, and does not implement the effect of the option.)
15842         // should only be used for options that are values on which future decisions will be made,
15843         // and definitely not on options that would be used during initialization.
15844         if(strstr(move, "!!! -") == move) {
15845             ParseArgsFromString(move+4);
15846             return;
15847         }
15848
15849       if (gameMode != EditGame && currentMove != forwardMostMove && 
15850         gameMode != Training) {
15851         DisplayMoveError(_("Displayed move is not current"));
15852       } else {
15853         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15854           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15855         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15856         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15857           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15858           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15859         } else {
15860           DisplayMoveError(_("Could not parse move"));
15861         }
15862       }
15863 }
15864
15865 void
15866 DisplayMove (int moveNumber)
15867 {
15868     char message[MSG_SIZ];
15869     char res[MSG_SIZ];
15870     char cpThinkOutput[MSG_SIZ];
15871
15872     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15873
15874     if (moveNumber == forwardMostMove - 1 ||
15875         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15876
15877         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15878
15879         if (strchr(cpThinkOutput, '\n')) {
15880             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15881         }
15882     } else {
15883         *cpThinkOutput = NULLCHAR;
15884     }
15885
15886     /* [AS] Hide thinking from human user */
15887     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15888         *cpThinkOutput = NULLCHAR;
15889         if( thinkOutput[0] != NULLCHAR ) {
15890             int i;
15891
15892             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15893                 cpThinkOutput[i] = '.';
15894             }
15895             cpThinkOutput[i] = NULLCHAR;
15896             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15897         }
15898     }
15899
15900     if (moveNumber == forwardMostMove - 1 &&
15901         gameInfo.resultDetails != NULL) {
15902         if (gameInfo.resultDetails[0] == NULLCHAR) {
15903           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15904         } else {
15905           snprintf(res, MSG_SIZ, " {%s} %s",
15906                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15907         }
15908     } else {
15909         res[0] = NULLCHAR;
15910     }
15911
15912     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15913         DisplayMessage(res, cpThinkOutput);
15914     } else {
15915       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15916                 WhiteOnMove(moveNumber) ? " " : ".. ",
15917                 parseList[moveNumber], res);
15918         DisplayMessage(message, cpThinkOutput);
15919     }
15920 }
15921
15922 void
15923 DisplayComment (int moveNumber, char *text)
15924 {
15925     char title[MSG_SIZ];
15926
15927     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15928       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15929     } else {
15930       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15931               WhiteOnMove(moveNumber) ? " " : ".. ",
15932               parseList[moveNumber]);
15933     }
15934     if (text != NULL && (appData.autoDisplayComment || commentUp))
15935         CommentPopUp(title, text);
15936 }
15937
15938 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15939  * might be busy thinking or pondering.  It can be omitted if your
15940  * gnuchess is configured to stop thinking immediately on any user
15941  * input.  However, that gnuchess feature depends on the FIONREAD
15942  * ioctl, which does not work properly on some flavors of Unix.
15943  */
15944 void
15945 Attention (ChessProgramState *cps)
15946 {
15947 #if ATTENTION
15948     if (!cps->useSigint) return;
15949     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15950     switch (gameMode) {
15951       case MachinePlaysWhite:
15952       case MachinePlaysBlack:
15953       case TwoMachinesPlay:
15954       case IcsPlayingWhite:
15955       case IcsPlayingBlack:
15956       case AnalyzeMode:
15957       case AnalyzeFile:
15958         /* Skip if we know it isn't thinking */
15959         if (!cps->maybeThinking) return;
15960         if (appData.debugMode)
15961           fprintf(debugFP, "Interrupting %s\n", cps->which);
15962         InterruptChildProcess(cps->pr);
15963         cps->maybeThinking = FALSE;
15964         break;
15965       default:
15966         break;
15967     }
15968 #endif /*ATTENTION*/
15969 }
15970
15971 int
15972 CheckFlags ()
15973 {
15974     if (whiteTimeRemaining <= 0) {
15975         if (!whiteFlag) {
15976             whiteFlag = TRUE;
15977             if (appData.icsActive) {
15978                 if (appData.autoCallFlag &&
15979                     gameMode == IcsPlayingBlack && !blackFlag) {
15980                   SendToICS(ics_prefix);
15981                   SendToICS("flag\n");
15982                 }
15983             } else {
15984                 if (blackFlag) {
15985                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15986                 } else {
15987                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15988                     if (appData.autoCallFlag) {
15989                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15990                         return TRUE;
15991                     }
15992                 }
15993             }
15994         }
15995     }
15996     if (blackTimeRemaining <= 0) {
15997         if (!blackFlag) {
15998             blackFlag = TRUE;
15999             if (appData.icsActive) {
16000                 if (appData.autoCallFlag &&
16001                     gameMode == IcsPlayingWhite && !whiteFlag) {
16002                   SendToICS(ics_prefix);
16003                   SendToICS("flag\n");
16004                 }
16005             } else {
16006                 if (whiteFlag) {
16007                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16008                 } else {
16009                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16010                     if (appData.autoCallFlag) {
16011                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16012                         return TRUE;
16013                     }
16014                 }
16015             }
16016         }
16017     }
16018     return FALSE;
16019 }
16020
16021 void
16022 CheckTimeControl ()
16023 {
16024     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16025         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16026
16027     /*
16028      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16029      */
16030     if ( !WhiteOnMove(forwardMostMove) ) {
16031         /* White made time control */
16032         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16033         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16034         /* [HGM] time odds: correct new time quota for time odds! */
16035                                             / WhitePlayer()->timeOdds;
16036         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16037     } else {
16038         lastBlack -= blackTimeRemaining;
16039         /* Black made time control */
16040         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16041                                             / WhitePlayer()->other->timeOdds;
16042         lastWhite = whiteTimeRemaining;
16043     }
16044 }
16045
16046 void
16047 DisplayBothClocks ()
16048 {
16049     int wom = gameMode == EditPosition ?
16050       !blackPlaysFirst : WhiteOnMove(currentMove);
16051     DisplayWhiteClock(whiteTimeRemaining, wom);
16052     DisplayBlackClock(blackTimeRemaining, !wom);
16053 }
16054
16055
16056 /* Timekeeping seems to be a portability nightmare.  I think everyone
16057    has ftime(), but I'm really not sure, so I'm including some ifdefs
16058    to use other calls if you don't.  Clocks will be less accurate if
16059    you have neither ftime nor gettimeofday.
16060 */
16061
16062 /* VS 2008 requires the #include outside of the function */
16063 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16064 #include <sys/timeb.h>
16065 #endif
16066
16067 /* Get the current time as a TimeMark */
16068 void
16069 GetTimeMark (TimeMark *tm)
16070 {
16071 #if HAVE_GETTIMEOFDAY
16072
16073     struct timeval timeVal;
16074     struct timezone timeZone;
16075
16076     gettimeofday(&timeVal, &timeZone);
16077     tm->sec = (long) timeVal.tv_sec;
16078     tm->ms = (int) (timeVal.tv_usec / 1000L);
16079
16080 #else /*!HAVE_GETTIMEOFDAY*/
16081 #if HAVE_FTIME
16082
16083 // include <sys/timeb.h> / moved to just above start of function
16084     struct timeb timeB;
16085
16086     ftime(&timeB);
16087     tm->sec = (long) timeB.time;
16088     tm->ms = (int) timeB.millitm;
16089
16090 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16091     tm->sec = (long) time(NULL);
16092     tm->ms = 0;
16093 #endif
16094 #endif
16095 }
16096
16097 /* Return the difference in milliseconds between two
16098    time marks.  We assume the difference will fit in a long!
16099 */
16100 long
16101 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16102 {
16103     return 1000L*(tm2->sec - tm1->sec) +
16104            (long) (tm2->ms - tm1->ms);
16105 }
16106
16107
16108 /*
16109  * Code to manage the game clocks.
16110  *
16111  * In tournament play, black starts the clock and then white makes a move.
16112  * We give the human user a slight advantage if he is playing white---the
16113  * clocks don't run until he makes his first move, so it takes zero time.
16114  * Also, we don't account for network lag, so we could get out of sync
16115  * with GNU Chess's clock -- but then, referees are always right.
16116  */
16117
16118 static TimeMark tickStartTM;
16119 static long intendedTickLength;
16120
16121 long
16122 NextTickLength (long timeRemaining)
16123 {
16124     long nominalTickLength, nextTickLength;
16125
16126     if (timeRemaining > 0L && timeRemaining <= 10000L)
16127       nominalTickLength = 100L;
16128     else
16129       nominalTickLength = 1000L;
16130     nextTickLength = timeRemaining % nominalTickLength;
16131     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16132
16133     return nextTickLength;
16134 }
16135
16136 /* Adjust clock one minute up or down */
16137 void
16138 AdjustClock (Boolean which, int dir)
16139 {
16140     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16141     if(which) blackTimeRemaining += 60000*dir;
16142     else      whiteTimeRemaining += 60000*dir;
16143     DisplayBothClocks();
16144     adjustedClock = TRUE;
16145 }
16146
16147 /* Stop clocks and reset to a fresh time control */
16148 void
16149 ResetClocks ()
16150 {
16151     (void) StopClockTimer();
16152     if (appData.icsActive) {
16153         whiteTimeRemaining = blackTimeRemaining = 0;
16154     } else if (searchTime) {
16155         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16156         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16157     } else { /* [HGM] correct new time quote for time odds */
16158         whiteTC = blackTC = fullTimeControlString;
16159         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16160         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16161     }
16162     if (whiteFlag || blackFlag) {
16163         DisplayTitle("");
16164         whiteFlag = blackFlag = FALSE;
16165     }
16166     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16167     DisplayBothClocks();
16168     adjustedClock = FALSE;
16169 }
16170
16171 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16172
16173 /* Decrement running clock by amount of time that has passed */
16174 void
16175 DecrementClocks ()
16176 {
16177     long timeRemaining;
16178     long lastTickLength, fudge;
16179     TimeMark now;
16180
16181     if (!appData.clockMode) return;
16182     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16183
16184     GetTimeMark(&now);
16185
16186     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16187
16188     /* Fudge if we woke up a little too soon */
16189     fudge = intendedTickLength - lastTickLength;
16190     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16191
16192     if (WhiteOnMove(forwardMostMove)) {
16193         if(whiteNPS >= 0) lastTickLength = 0;
16194         timeRemaining = whiteTimeRemaining -= lastTickLength;
16195         if(timeRemaining < 0 && !appData.icsActive) {
16196             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16197             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16198                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16199                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16200             }
16201         }
16202         DisplayWhiteClock(whiteTimeRemaining - fudge,
16203                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16204     } else {
16205         if(blackNPS >= 0) lastTickLength = 0;
16206         timeRemaining = blackTimeRemaining -= lastTickLength;
16207         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16208             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16209             if(suddenDeath) {
16210                 blackStartMove = forwardMostMove;
16211                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16212             }
16213         }
16214         DisplayBlackClock(blackTimeRemaining - fudge,
16215                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16216     }
16217     if (CheckFlags()) return;
16218
16219     if(twoBoards) { // count down secondary board's clocks as well
16220         activePartnerTime -= lastTickLength;
16221         partnerUp = 1;
16222         if(activePartner == 'W')
16223             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16224         else
16225             DisplayBlackClock(activePartnerTime, TRUE);
16226         partnerUp = 0;
16227     }
16228
16229     tickStartTM = now;
16230     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16231     StartClockTimer(intendedTickLength);
16232
16233     /* if the time remaining has fallen below the alarm threshold, sound the
16234      * alarm. if the alarm has sounded and (due to a takeback or time control
16235      * with increment) the time remaining has increased to a level above the
16236      * threshold, reset the alarm so it can sound again.
16237      */
16238
16239     if (appData.icsActive && appData.icsAlarm) {
16240
16241         /* make sure we are dealing with the user's clock */
16242         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16243                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16244            )) return;
16245
16246         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16247             alarmSounded = FALSE;
16248         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16249             PlayAlarmSound();
16250             alarmSounded = TRUE;
16251         }
16252     }
16253 }
16254
16255
16256 /* A player has just moved, so stop the previously running
16257    clock and (if in clock mode) start the other one.
16258    We redisplay both clocks in case we're in ICS mode, because
16259    ICS gives us an update to both clocks after every move.
16260    Note that this routine is called *after* forwardMostMove
16261    is updated, so the last fractional tick must be subtracted
16262    from the color that is *not* on move now.
16263 */
16264 void
16265 SwitchClocks (int newMoveNr)
16266 {
16267     long lastTickLength;
16268     TimeMark now;
16269     int flagged = FALSE;
16270
16271     GetTimeMark(&now);
16272
16273     if (StopClockTimer() && appData.clockMode) {
16274         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16275         if (!WhiteOnMove(forwardMostMove)) {
16276             if(blackNPS >= 0) lastTickLength = 0;
16277             blackTimeRemaining -= lastTickLength;
16278            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16279 //         if(pvInfoList[forwardMostMove].time == -1)
16280                  pvInfoList[forwardMostMove].time =               // use GUI time
16281                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16282         } else {
16283            if(whiteNPS >= 0) lastTickLength = 0;
16284            whiteTimeRemaining -= lastTickLength;
16285            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16286 //         if(pvInfoList[forwardMostMove].time == -1)
16287                  pvInfoList[forwardMostMove].time =
16288                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16289         }
16290         flagged = CheckFlags();
16291     }
16292     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16293     CheckTimeControl();
16294
16295     if (flagged || !appData.clockMode) return;
16296
16297     switch (gameMode) {
16298       case MachinePlaysBlack:
16299       case MachinePlaysWhite:
16300       case BeginningOfGame:
16301         if (pausing) return;
16302         break;
16303
16304       case EditGame:
16305       case PlayFromGameFile:
16306       case IcsExamining:
16307         return;
16308
16309       default:
16310         break;
16311     }
16312
16313     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16314         if(WhiteOnMove(forwardMostMove))
16315              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16316         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16317     }
16318
16319     tickStartTM = now;
16320     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16321       whiteTimeRemaining : blackTimeRemaining);
16322     StartClockTimer(intendedTickLength);
16323 }
16324
16325
16326 /* Stop both clocks */
16327 void
16328 StopClocks ()
16329 {
16330     long lastTickLength;
16331     TimeMark now;
16332
16333     if (!StopClockTimer()) return;
16334     if (!appData.clockMode) return;
16335
16336     GetTimeMark(&now);
16337
16338     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16339     if (WhiteOnMove(forwardMostMove)) {
16340         if(whiteNPS >= 0) lastTickLength = 0;
16341         whiteTimeRemaining -= lastTickLength;
16342         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16343     } else {
16344         if(blackNPS >= 0) lastTickLength = 0;
16345         blackTimeRemaining -= lastTickLength;
16346         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16347     }
16348     CheckFlags();
16349 }
16350
16351 /* Start clock of player on move.  Time may have been reset, so
16352    if clock is already running, stop and restart it. */
16353 void
16354 StartClocks ()
16355 {
16356     (void) StopClockTimer(); /* in case it was running already */
16357     DisplayBothClocks();
16358     if (CheckFlags()) return;
16359
16360     if (!appData.clockMode) return;
16361     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16362
16363     GetTimeMark(&tickStartTM);
16364     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16365       whiteTimeRemaining : blackTimeRemaining);
16366
16367    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16368     whiteNPS = blackNPS = -1;
16369     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16370        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16371         whiteNPS = first.nps;
16372     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16373        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16374         blackNPS = first.nps;
16375     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16376         whiteNPS = second.nps;
16377     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16378         blackNPS = second.nps;
16379     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16380
16381     StartClockTimer(intendedTickLength);
16382 }
16383
16384 char *
16385 TimeString (long ms)
16386 {
16387     long second, minute, hour, day;
16388     char *sign = "";
16389     static char buf[32];
16390
16391     if (ms > 0 && ms <= 9900) {
16392       /* convert milliseconds to tenths, rounding up */
16393       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16394
16395       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16396       return buf;
16397     }
16398
16399     /* convert milliseconds to seconds, rounding up */
16400     /* use floating point to avoid strangeness of integer division
16401        with negative dividends on many machines */
16402     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16403
16404     if (second < 0) {
16405         sign = "-";
16406         second = -second;
16407     }
16408
16409     day = second / (60 * 60 * 24);
16410     second = second % (60 * 60 * 24);
16411     hour = second / (60 * 60);
16412     second = second % (60 * 60);
16413     minute = second / 60;
16414     second = second % 60;
16415
16416     if (day > 0)
16417       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16418               sign, day, hour, minute, second);
16419     else if (hour > 0)
16420       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16421     else
16422       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16423
16424     return buf;
16425 }
16426
16427
16428 /*
16429  * This is necessary because some C libraries aren't ANSI C compliant yet.
16430  */
16431 char *
16432 StrStr (char *string, char *match)
16433 {
16434     int i, length;
16435
16436     length = strlen(match);
16437
16438     for (i = strlen(string) - length; i >= 0; i--, string++)
16439       if (!strncmp(match, string, length))
16440         return string;
16441
16442     return NULL;
16443 }
16444
16445 char *
16446 StrCaseStr (char *string, char *match)
16447 {
16448     int i, j, length;
16449
16450     length = strlen(match);
16451
16452     for (i = strlen(string) - length; i >= 0; i--, string++) {
16453         for (j = 0; j < length; j++) {
16454             if (ToLower(match[j]) != ToLower(string[j]))
16455               break;
16456         }
16457         if (j == length) return string;
16458     }
16459
16460     return NULL;
16461 }
16462
16463 #ifndef _amigados
16464 int
16465 StrCaseCmp (char *s1, char *s2)
16466 {
16467     char c1, c2;
16468
16469     for (;;) {
16470         c1 = ToLower(*s1++);
16471         c2 = ToLower(*s2++);
16472         if (c1 > c2) return 1;
16473         if (c1 < c2) return -1;
16474         if (c1 == NULLCHAR) return 0;
16475     }
16476 }
16477
16478
16479 int
16480 ToLower (int c)
16481 {
16482     return isupper(c) ? tolower(c) : c;
16483 }
16484
16485
16486 int
16487 ToUpper (int c)
16488 {
16489     return islower(c) ? toupper(c) : c;
16490 }
16491 #endif /* !_amigados    */
16492
16493 char *
16494 StrSave (char *s)
16495 {
16496   char *ret;
16497
16498   if ((ret = (char *) malloc(strlen(s) + 1)))
16499     {
16500       safeStrCpy(ret, s, strlen(s)+1);
16501     }
16502   return ret;
16503 }
16504
16505 char *
16506 StrSavePtr (char *s, char **savePtr)
16507 {
16508     if (*savePtr) {
16509         free(*savePtr);
16510     }
16511     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16512       safeStrCpy(*savePtr, s, strlen(s)+1);
16513     }
16514     return(*savePtr);
16515 }
16516
16517 char *
16518 PGNDate ()
16519 {
16520     time_t clock;
16521     struct tm *tm;
16522     char buf[MSG_SIZ];
16523
16524     clock = time((time_t *)NULL);
16525     tm = localtime(&clock);
16526     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16527             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16528     return StrSave(buf);
16529 }
16530
16531
16532 char *
16533 PositionToFEN (int move, char *overrideCastling)
16534 {
16535     int i, j, fromX, fromY, toX, toY;
16536     int whiteToPlay;
16537     char buf[MSG_SIZ];
16538     char *p, *q;
16539     int emptycount;
16540     ChessSquare piece;
16541
16542     whiteToPlay = (gameMode == EditPosition) ?
16543       !blackPlaysFirst : (move % 2 == 0);
16544     p = buf;
16545
16546     /* Piece placement data */
16547     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16548         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16549         emptycount = 0;
16550         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16551             if (boards[move][i][j] == EmptySquare) {
16552                 emptycount++;
16553             } else { ChessSquare piece = boards[move][i][j];
16554                 if (emptycount > 0) {
16555                     if(emptycount<10) /* [HGM] can be >= 10 */
16556                         *p++ = '0' + emptycount;
16557                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16558                     emptycount = 0;
16559                 }
16560                 if(PieceToChar(piece) == '+') {
16561                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16562                     *p++ = '+';
16563                     piece = (ChessSquare)(DEMOTED piece);
16564                 }
16565                 *p++ = PieceToChar(piece);
16566                 if(p[-1] == '~') {
16567                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16568                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16569                     *p++ = '~';
16570                 }
16571             }
16572         }
16573         if (emptycount > 0) {
16574             if(emptycount<10) /* [HGM] can be >= 10 */
16575                 *p++ = '0' + emptycount;
16576             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16577             emptycount = 0;
16578         }
16579         *p++ = '/';
16580     }
16581     *(p - 1) = ' ';
16582
16583     /* [HGM] print Crazyhouse or Shogi holdings */
16584     if( gameInfo.holdingsWidth ) {
16585         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16586         q = p;
16587         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16588             piece = boards[move][i][BOARD_WIDTH-1];
16589             if( piece != EmptySquare )
16590               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16591                   *p++ = PieceToChar(piece);
16592         }
16593         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16594             piece = boards[move][BOARD_HEIGHT-i-1][0];
16595             if( piece != EmptySquare )
16596               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16597                   *p++ = PieceToChar(piece);
16598         }
16599
16600         if( q == p ) *p++ = '-';
16601         *p++ = ']';
16602         *p++ = ' ';
16603     }
16604
16605     /* Active color */
16606     *p++ = whiteToPlay ? 'w' : 'b';
16607     *p++ = ' ';
16608
16609   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16610     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16611   } else {
16612   if(nrCastlingRights) {
16613      q = p;
16614      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16615        /* [HGM] write directly from rights */
16616            if(boards[move][CASTLING][2] != NoRights &&
16617               boards[move][CASTLING][0] != NoRights   )
16618                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16619            if(boards[move][CASTLING][2] != NoRights &&
16620               boards[move][CASTLING][1] != NoRights   )
16621                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16622            if(boards[move][CASTLING][5] != NoRights &&
16623               boards[move][CASTLING][3] != NoRights   )
16624                 *p++ = boards[move][CASTLING][3] + AAA;
16625            if(boards[move][CASTLING][5] != NoRights &&
16626               boards[move][CASTLING][4] != NoRights   )
16627                 *p++ = boards[move][CASTLING][4] + AAA;
16628      } else {
16629
16630         /* [HGM] write true castling rights */
16631         if( nrCastlingRights == 6 ) {
16632             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16633                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16634             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16635                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16636             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16637                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16638             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16639                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16640         }
16641      }
16642      if (q == p) *p++ = '-'; /* No castling rights */
16643      *p++ = ' ';
16644   }
16645
16646   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16647      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16648     /* En passant target square */
16649     if (move > backwardMostMove) {
16650         fromX = moveList[move - 1][0] - AAA;
16651         fromY = moveList[move - 1][1] - ONE;
16652         toX = moveList[move - 1][2] - AAA;
16653         toY = moveList[move - 1][3] - ONE;
16654         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16655             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16656             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16657             fromX == toX) {
16658             /* 2-square pawn move just happened */
16659             *p++ = toX + AAA;
16660             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16661         } else {
16662             *p++ = '-';
16663         }
16664     } else if(move == backwardMostMove) {
16665         // [HGM] perhaps we should always do it like this, and forget the above?
16666         if((signed char)boards[move][EP_STATUS] >= 0) {
16667             *p++ = boards[move][EP_STATUS] + AAA;
16668             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16669         } else {
16670             *p++ = '-';
16671         }
16672     } else {
16673         *p++ = '-';
16674     }
16675     *p++ = ' ';
16676   }
16677   }
16678
16679     /* [HGM] find reversible plies */
16680     {   int i = 0, j=move;
16681
16682         if (appData.debugMode) { int k;
16683             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16684             for(k=backwardMostMove; k<=forwardMostMove; k++)
16685                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16686
16687         }
16688
16689         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16690         if( j == backwardMostMove ) i += initialRulePlies;
16691         sprintf(p, "%d ", i);
16692         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16693     }
16694     /* Fullmove number */
16695     sprintf(p, "%d", (move / 2) + 1);
16696
16697     return StrSave(buf);
16698 }
16699
16700 Boolean
16701 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16702 {
16703     int i, j;
16704     char *p, c;
16705     int emptycount;
16706     ChessSquare piece;
16707
16708     p = fen;
16709
16710     /* [HGM] by default clear Crazyhouse holdings, if present */
16711     if(gameInfo.holdingsWidth) {
16712        for(i=0; i<BOARD_HEIGHT; i++) {
16713            board[i][0]             = EmptySquare; /* black holdings */
16714            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16715            board[i][1]             = (ChessSquare) 0; /* black counts */
16716            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16717        }
16718     }
16719
16720     /* Piece placement data */
16721     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16722         j = 0;
16723         for (;;) {
16724             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16725                 if (*p == '/') p++;
16726                 emptycount = gameInfo.boardWidth - j;
16727                 while (emptycount--)
16728                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16729                 break;
16730 #if(BOARD_FILES >= 10)
16731             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16732                 p++; emptycount=10;
16733                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16734                 while (emptycount--)
16735                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16736 #endif
16737             } else if (isdigit(*p)) {
16738                 emptycount = *p++ - '0';
16739                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16740                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16741                 while (emptycount--)
16742                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16743             } else if (*p == '+' || isalpha(*p)) {
16744                 if (j >= gameInfo.boardWidth) return FALSE;
16745                 if(*p=='+') {
16746                     piece = CharToPiece(*++p);
16747                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16748                     piece = (ChessSquare) (PROMOTED piece ); p++;
16749                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16750                 } else piece = CharToPiece(*p++);
16751
16752                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16753                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16754                     piece = (ChessSquare) (PROMOTED piece);
16755                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16756                     p++;
16757                 }
16758                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16759             } else {
16760                 return FALSE;
16761             }
16762         }
16763     }
16764     while (*p == '/' || *p == ' ') p++;
16765
16766     /* [HGM] look for Crazyhouse holdings here */
16767     while(*p==' ') p++;
16768     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16769         if(*p == '[') p++;
16770         if(*p == '-' ) p++; /* empty holdings */ else {
16771             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16772             /* if we would allow FEN reading to set board size, we would   */
16773             /* have to add holdings and shift the board read so far here   */
16774             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16775                 p++;
16776                 if((int) piece >= (int) BlackPawn ) {
16777                     i = (int)piece - (int)BlackPawn;
16778                     i = PieceToNumber((ChessSquare)i);
16779                     if( i >= gameInfo.holdingsSize ) return FALSE;
16780                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16781                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16782                 } else {
16783                     i = (int)piece - (int)WhitePawn;
16784                     i = PieceToNumber((ChessSquare)i);
16785                     if( i >= gameInfo.holdingsSize ) return FALSE;
16786                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16787                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16788                 }
16789             }
16790         }
16791         if(*p == ']') p++;
16792     }
16793
16794     while(*p == ' ') p++;
16795
16796     /* Active color */
16797     c = *p++;
16798     if(appData.colorNickNames) {
16799       if( c == appData.colorNickNames[0] ) c = 'w'; else
16800       if( c == appData.colorNickNames[1] ) c = 'b';
16801     }
16802     switch (c) {
16803       case 'w':
16804         *blackPlaysFirst = FALSE;
16805         break;
16806       case 'b':
16807         *blackPlaysFirst = TRUE;
16808         break;
16809       default:
16810         return FALSE;
16811     }
16812
16813     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16814     /* return the extra info in global variiables             */
16815
16816     /* set defaults in case FEN is incomplete */
16817     board[EP_STATUS] = EP_UNKNOWN;
16818     for(i=0; i<nrCastlingRights; i++ ) {
16819         board[CASTLING][i] =
16820             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16821     }   /* assume possible unless obviously impossible */
16822     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16823     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16824     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16825                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16826     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16827     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16828     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16829                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16830     FENrulePlies = 0;
16831
16832     while(*p==' ') p++;
16833     if(nrCastlingRights) {
16834       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16835           /* castling indicator present, so default becomes no castlings */
16836           for(i=0; i<nrCastlingRights; i++ ) {
16837                  board[CASTLING][i] = NoRights;
16838           }
16839       }
16840       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16841              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16842              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16843              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16844         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16845
16846         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16847             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16848             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16849         }
16850         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16851             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16852         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16853                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16854         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16855                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16856         switch(c) {
16857           case'K':
16858               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16859               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16860               board[CASTLING][2] = whiteKingFile;
16861               break;
16862           case'Q':
16863               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16864               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16865               board[CASTLING][2] = whiteKingFile;
16866               break;
16867           case'k':
16868               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16869               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16870               board[CASTLING][5] = blackKingFile;
16871               break;
16872           case'q':
16873               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16874               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16875               board[CASTLING][5] = blackKingFile;
16876           case '-':
16877               break;
16878           default: /* FRC castlings */
16879               if(c >= 'a') { /* black rights */
16880                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16881                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16882                   if(i == BOARD_RGHT) break;
16883                   board[CASTLING][5] = i;
16884                   c -= AAA;
16885                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16886                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16887                   if(c > i)
16888                       board[CASTLING][3] = c;
16889                   else
16890                       board[CASTLING][4] = c;
16891               } else { /* white rights */
16892                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16893                     if(board[0][i] == WhiteKing) break;
16894                   if(i == BOARD_RGHT) break;
16895                   board[CASTLING][2] = i;
16896                   c -= AAA - 'a' + 'A';
16897                   if(board[0][c] >= WhiteKing) break;
16898                   if(c > i)
16899                       board[CASTLING][0] = c;
16900                   else
16901                       board[CASTLING][1] = c;
16902               }
16903         }
16904       }
16905       for(i=0; i<nrCastlingRights; i++)
16906         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16907     if (appData.debugMode) {
16908         fprintf(debugFP, "FEN castling rights:");
16909         for(i=0; i<nrCastlingRights; i++)
16910         fprintf(debugFP, " %d", board[CASTLING][i]);
16911         fprintf(debugFP, "\n");
16912     }
16913
16914       while(*p==' ') p++;
16915     }
16916
16917     /* read e.p. field in games that know e.p. capture */
16918     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16919        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16920       if(*p=='-') {
16921         p++; board[EP_STATUS] = EP_NONE;
16922       } else {
16923          char c = *p++ - AAA;
16924
16925          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16926          if(*p >= '0' && *p <='9') p++;
16927          board[EP_STATUS] = c;
16928       }
16929     }
16930
16931
16932     if(sscanf(p, "%d", &i) == 1) {
16933         FENrulePlies = i; /* 50-move ply counter */
16934         /* (The move number is still ignored)    */
16935     }
16936
16937     return TRUE;
16938 }
16939
16940 void
16941 EditPositionPasteFEN (char *fen)
16942 {
16943   if (fen != NULL) {
16944     Board initial_position;
16945
16946     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16947       DisplayError(_("Bad FEN position in clipboard"), 0);
16948       return ;
16949     } else {
16950       int savedBlackPlaysFirst = blackPlaysFirst;
16951       EditPositionEvent();
16952       blackPlaysFirst = savedBlackPlaysFirst;
16953       CopyBoard(boards[0], initial_position);
16954       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16955       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16956       DisplayBothClocks();
16957       DrawPosition(FALSE, boards[currentMove]);
16958     }
16959   }
16960 }
16961
16962 static char cseq[12] = "\\   ";
16963
16964 Boolean
16965 set_cont_sequence (char *new_seq)
16966 {
16967     int len;
16968     Boolean ret;
16969
16970     // handle bad attempts to set the sequence
16971         if (!new_seq)
16972                 return 0; // acceptable error - no debug
16973
16974     len = strlen(new_seq);
16975     ret = (len > 0) && (len < sizeof(cseq));
16976     if (ret)
16977       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16978     else if (appData.debugMode)
16979       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16980     return ret;
16981 }
16982
16983 /*
16984     reformat a source message so words don't cross the width boundary.  internal
16985     newlines are not removed.  returns the wrapped size (no null character unless
16986     included in source message).  If dest is NULL, only calculate the size required
16987     for the dest buffer.  lp argument indicats line position upon entry, and it's
16988     passed back upon exit.
16989 */
16990 int
16991 wrap (char *dest, char *src, int count, int width, int *lp)
16992 {
16993     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16994
16995     cseq_len = strlen(cseq);
16996     old_line = line = *lp;
16997     ansi = len = clen = 0;
16998
16999     for (i=0; i < count; i++)
17000     {
17001         if (src[i] == '\033')
17002             ansi = 1;
17003
17004         // if we hit the width, back up
17005         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17006         {
17007             // store i & len in case the word is too long
17008             old_i = i, old_len = len;
17009
17010             // find the end of the last word
17011             while (i && src[i] != ' ' && src[i] != '\n')
17012             {
17013                 i--;
17014                 len--;
17015             }
17016
17017             // word too long?  restore i & len before splitting it
17018             if ((old_i-i+clen) >= width)
17019             {
17020                 i = old_i;
17021                 len = old_len;
17022             }
17023
17024             // extra space?
17025             if (i && src[i-1] == ' ')
17026                 len--;
17027
17028             if (src[i] != ' ' && src[i] != '\n')
17029             {
17030                 i--;
17031                 if (len)
17032                     len--;
17033             }
17034
17035             // now append the newline and continuation sequence
17036             if (dest)
17037                 dest[len] = '\n';
17038             len++;
17039             if (dest)
17040                 strncpy(dest+len, cseq, cseq_len);
17041             len += cseq_len;
17042             line = cseq_len;
17043             clen = cseq_len;
17044             continue;
17045         }
17046
17047         if (dest)
17048             dest[len] = src[i];
17049         len++;
17050         if (!ansi)
17051             line++;
17052         if (src[i] == '\n')
17053             line = 0;
17054         if (src[i] == 'm')
17055             ansi = 0;
17056     }
17057     if (dest && appData.debugMode)
17058     {
17059         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17060             count, width, line, len, *lp);
17061         show_bytes(debugFP, src, count);
17062         fprintf(debugFP, "\ndest: ");
17063         show_bytes(debugFP, dest, len);
17064         fprintf(debugFP, "\n");
17065     }
17066     *lp = dest ? line : old_line;
17067
17068     return len;
17069 }
17070
17071 // [HGM] vari: routines for shelving variations
17072 Boolean modeRestore = FALSE;
17073
17074 void
17075 PushInner (int firstMove, int lastMove)
17076 {
17077         int i, j, nrMoves = lastMove - firstMove;
17078
17079         // push current tail of game on stack
17080         savedResult[storedGames] = gameInfo.result;
17081         savedDetails[storedGames] = gameInfo.resultDetails;
17082         gameInfo.resultDetails = NULL;
17083         savedFirst[storedGames] = firstMove;
17084         savedLast [storedGames] = lastMove;
17085         savedFramePtr[storedGames] = framePtr;
17086         framePtr -= nrMoves; // reserve space for the boards
17087         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17088             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17089             for(j=0; j<MOVE_LEN; j++)
17090                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17091             for(j=0; j<2*MOVE_LEN; j++)
17092                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17093             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17094             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17095             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17096             pvInfoList[firstMove+i-1].depth = 0;
17097             commentList[framePtr+i] = commentList[firstMove+i];
17098             commentList[firstMove+i] = NULL;
17099         }
17100
17101         storedGames++;
17102         forwardMostMove = firstMove; // truncate game so we can start variation
17103 }
17104
17105 void
17106 PushTail (int firstMove, int lastMove)
17107 {
17108         if(appData.icsActive) { // only in local mode
17109                 forwardMostMove = currentMove; // mimic old ICS behavior
17110                 return;
17111         }
17112         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17113
17114         PushInner(firstMove, lastMove);
17115         if(storedGames == 1) GreyRevert(FALSE);
17116         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17117 }
17118
17119 void
17120 PopInner (Boolean annotate)
17121 {
17122         int i, j, nrMoves;
17123         char buf[8000], moveBuf[20];
17124
17125         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17126         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17127         nrMoves = savedLast[storedGames] - currentMove;
17128         if(annotate) {
17129                 int cnt = 10;
17130                 if(!WhiteOnMove(currentMove))
17131                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17132                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17133                 for(i=currentMove; i<forwardMostMove; i++) {
17134                         if(WhiteOnMove(i))
17135                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17136                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17137                         strcat(buf, moveBuf);
17138                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17139                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17140                 }
17141                 strcat(buf, ")");
17142         }
17143         for(i=1; i<=nrMoves; i++) { // copy last variation back
17144             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17145             for(j=0; j<MOVE_LEN; j++)
17146                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17147             for(j=0; j<2*MOVE_LEN; j++)
17148                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17149             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17150             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17151             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17152             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17153             commentList[currentMove+i] = commentList[framePtr+i];
17154             commentList[framePtr+i] = NULL;
17155         }
17156         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17157         framePtr = savedFramePtr[storedGames];
17158         gameInfo.result = savedResult[storedGames];
17159         if(gameInfo.resultDetails != NULL) {
17160             free(gameInfo.resultDetails);
17161       }
17162         gameInfo.resultDetails = savedDetails[storedGames];
17163         forwardMostMove = currentMove + nrMoves;
17164 }
17165
17166 Boolean
17167 PopTail (Boolean annotate)
17168 {
17169         if(appData.icsActive) return FALSE; // only in local mode
17170         if(!storedGames) return FALSE; // sanity
17171         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17172
17173         PopInner(annotate);
17174         if(currentMove < forwardMostMove) ForwardEvent(); else
17175         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17176
17177         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17178         return TRUE;
17179 }
17180
17181 void
17182 CleanupTail ()
17183 {       // remove all shelved variations
17184         int i;
17185         for(i=0; i<storedGames; i++) {
17186             if(savedDetails[i])
17187                 free(savedDetails[i]);
17188             savedDetails[i] = NULL;
17189         }
17190         for(i=framePtr; i<MAX_MOVES; i++) {
17191                 if(commentList[i]) free(commentList[i]);
17192                 commentList[i] = NULL;
17193         }
17194         framePtr = MAX_MOVES-1;
17195         storedGames = 0;
17196 }
17197
17198 void
17199 LoadVariation (int index, char *text)
17200 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17201         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17202         int level = 0, move;
17203
17204         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17205         // first find outermost bracketing variation
17206         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17207             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17208                 if(*p == '{') wait = '}'; else
17209                 if(*p == '[') wait = ']'; else
17210                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17211                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17212             }
17213             if(*p == wait) wait = NULLCHAR; // closing ]} found
17214             p++;
17215         }
17216         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17217         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17218         end[1] = NULLCHAR; // clip off comment beyond variation
17219         ToNrEvent(currentMove-1);
17220         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17221         // kludge: use ParsePV() to append variation to game
17222         move = currentMove;
17223         ParsePV(start, TRUE, TRUE);
17224         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17225         ClearPremoveHighlights();
17226         CommentPopDown();
17227         ToNrEvent(currentMove+1);
17228 }
17229