Move clearing of target squares to after drag end
[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     if (clickType == Press) {
7178         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7179             // must be Edit Position mode with empty-square selected
7180             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7181             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7182             return;
7183         }
7184         /* Finish clickclick move */
7185         if (appData.animate || appData.highlightLastMove) {
7186             SetHighlights(fromX, fromY, toX, toY);
7187         } else {
7188             ClearHighlights();
7189         }
7190         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7191           if(appData.sweepSelect) {
7192             ChessSquare piece = boards[currentMove][fromY][fromX];
7193             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7194             promoSweep = defaultPromoChoice;
7195             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7196             selectFlag = 0; lastX = xPix; lastY = yPix;
7197             Sweep(0); // Pawn that is going to promote: preview promotion piece
7198             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7199             DrawPosition(FALSE, boards[currentMove]);
7200           }
7201           return; // promo popup appears on up-click
7202         }
7203     } else {
7204         /* Finish drag move */
7205         if (appData.highlightLastMove) {
7206             SetHighlights(fromX, fromY, toX, toY);
7207         } else {
7208             ClearHighlights();
7209         }
7210         DragPieceEnd(xPix, yPix); dragging = 0;
7211         /* Don't animate move and drag both */
7212         appData.animate = FALSE;
7213     }
7214     MarkTargetSquares(1);
7215
7216     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7217     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7218         ChessSquare piece = boards[currentMove][fromY][fromX];
7219         if(gameMode == EditPosition && piece != EmptySquare &&
7220            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7221             int n;
7222
7223             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7224                 n = PieceToNumber(piece - (int)BlackPawn);
7225                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7226                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7227                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7228             } else
7229             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7230                 n = PieceToNumber(piece);
7231                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7232                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7233                 boards[currentMove][n][BOARD_WIDTH-2]++;
7234             }
7235             boards[currentMove][fromY][fromX] = EmptySquare;
7236         }
7237         ClearHighlights();
7238         fromX = fromY = -1;
7239         DrawPosition(TRUE, boards[currentMove]);
7240         return;
7241     }
7242
7243     // off-board moves should not be highlighted
7244     if(x < 0 || y < 0) ClearHighlights();
7245
7246     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7247
7248     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7249         SetHighlights(fromX, fromY, toX, toY);
7250         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7251             // [HGM] super: promotion to captured piece selected from holdings
7252             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7253             promotionChoice = TRUE;
7254             // kludge follows to temporarily execute move on display, without promoting yet
7255             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7256             boards[currentMove][toY][toX] = p;
7257             DrawPosition(FALSE, boards[currentMove]);
7258             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7259             boards[currentMove][toY][toX] = q;
7260             DisplayMessage("Click in holdings to choose piece", "");
7261             return;
7262         }
7263         PromotionPopUp();
7264     } else {
7265         int oldMove = currentMove;
7266         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7267         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7268         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7269         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7270            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7271             DrawPosition(TRUE, boards[currentMove]);
7272         fromX = fromY = -1;
7273     }
7274     appData.animate = saveAnimate;
7275     if (appData.animate || appData.animateDragging) {
7276         /* Undo animation damage if needed */
7277         DrawPosition(FALSE, NULL);
7278     }
7279 }
7280
7281 int
7282 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7283 {   // front-end-free part taken out of PieceMenuPopup
7284     int whichMenu; int xSqr, ySqr;
7285
7286     if(seekGraphUp) { // [HGM] seekgraph
7287         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7288         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7289         return -2;
7290     }
7291
7292     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7293          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7294         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7295         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7296         if(action == Press)   {
7297             originalFlip = flipView;
7298             flipView = !flipView; // temporarily flip board to see game from partners perspective
7299             DrawPosition(TRUE, partnerBoard);
7300             DisplayMessage(partnerStatus, "");
7301             partnerUp = TRUE;
7302         } else if(action == Release) {
7303             flipView = originalFlip;
7304             DrawPosition(TRUE, boards[currentMove]);
7305             partnerUp = FALSE;
7306         }
7307         return -2;
7308     }
7309
7310     xSqr = EventToSquare(x, BOARD_WIDTH);
7311     ySqr = EventToSquare(y, BOARD_HEIGHT);
7312     if (action == Release) {
7313         if(pieceSweep != EmptySquare) {
7314             EditPositionMenuEvent(pieceSweep, toX, toY);
7315             pieceSweep = EmptySquare;
7316         } else UnLoadPV(); // [HGM] pv
7317     }
7318     if (action != Press) return -2; // return code to be ignored
7319     switch (gameMode) {
7320       case IcsExamining:
7321         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7322       case EditPosition:
7323         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7324         if (xSqr < 0 || ySqr < 0) return -1;
7325         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7326         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7327         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7328         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7329         NextPiece(0);
7330         return 2; // grab
7331       case IcsObserving:
7332         if(!appData.icsEngineAnalyze) return -1;
7333       case IcsPlayingWhite:
7334       case IcsPlayingBlack:
7335         if(!appData.zippyPlay) goto noZip;
7336       case AnalyzeMode:
7337       case AnalyzeFile:
7338       case MachinePlaysWhite:
7339       case MachinePlaysBlack:
7340       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7341         if (!appData.dropMenu) {
7342           LoadPV(x, y);
7343           return 2; // flag front-end to grab mouse events
7344         }
7345         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7346            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7347       case EditGame:
7348       noZip:
7349         if (xSqr < 0 || ySqr < 0) return -1;
7350         if (!appData.dropMenu || appData.testLegality &&
7351             gameInfo.variant != VariantBughouse &&
7352             gameInfo.variant != VariantCrazyhouse) return -1;
7353         whichMenu = 1; // drop menu
7354         break;
7355       default:
7356         return -1;
7357     }
7358
7359     if (((*fromX = xSqr) < 0) ||
7360         ((*fromY = ySqr) < 0)) {
7361         *fromX = *fromY = -1;
7362         return -1;
7363     }
7364     if (flipView)
7365       *fromX = BOARD_WIDTH - 1 - *fromX;
7366     else
7367       *fromY = BOARD_HEIGHT - 1 - *fromY;
7368
7369     return whichMenu;
7370 }
7371
7372 void
7373 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7374 {
7375 //    char * hint = lastHint;
7376     FrontEndProgramStats stats;
7377
7378     stats.which = cps == &first ? 0 : 1;
7379     stats.depth = cpstats->depth;
7380     stats.nodes = cpstats->nodes;
7381     stats.score = cpstats->score;
7382     stats.time = cpstats->time;
7383     stats.pv = cpstats->movelist;
7384     stats.hint = lastHint;
7385     stats.an_move_index = 0;
7386     stats.an_move_count = 0;
7387
7388     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7389         stats.hint = cpstats->move_name;
7390         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7391         stats.an_move_count = cpstats->nr_moves;
7392     }
7393
7394     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
7395
7396     SetProgramStats( &stats );
7397 }
7398
7399 void
7400 ClearEngineOutputPane (int which)
7401 {
7402     static FrontEndProgramStats dummyStats;
7403     dummyStats.which = which;
7404     dummyStats.pv = "#";
7405     SetProgramStats( &dummyStats );
7406 }
7407
7408 #define MAXPLAYERS 500
7409
7410 char *
7411 TourneyStandings (int display)
7412 {
7413     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7414     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7415     char result, *p, *names[MAXPLAYERS];
7416
7417     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7418         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7419     names[0] = p = strdup(appData.participants);
7420     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7421
7422     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7423
7424     while(result = appData.results[nr]) {
7425         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7426         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7427         wScore = bScore = 0;
7428         switch(result) {
7429           case '+': wScore = 2; break;
7430           case '-': bScore = 2; break;
7431           case '=': wScore = bScore = 1; break;
7432           case ' ':
7433           case '*': return strdup("busy"); // tourney not finished
7434         }
7435         score[w] += wScore;
7436         score[b] += bScore;
7437         games[w]++;
7438         games[b]++;
7439         nr++;
7440     }
7441     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7442     for(w=0; w<nPlayers; w++) {
7443         bScore = -1;
7444         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7445         ranking[w] = b; points[w] = bScore; score[b] = -2;
7446     }
7447     p = malloc(nPlayers*34+1);
7448     for(w=0; w<nPlayers && w<display; w++)
7449         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7450     free(names[0]);
7451     return p;
7452 }
7453
7454 void
7455 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7456 {       // count all piece types
7457         int p, f, r;
7458         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7459         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7460         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7461                 p = board[r][f];
7462                 pCnt[p]++;
7463                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7464                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7465                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7466                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7467                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7468                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7469         }
7470 }
7471
7472 int
7473 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7474 {
7475         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7476         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7477
7478         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7479         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7480         if(myPawns == 2 && nMine == 3) // KPP
7481             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7482         if(myPawns == 1 && nMine == 2) // KP
7483             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7484         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7485             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7486         if(myPawns) return FALSE;
7487         if(pCnt[WhiteRook+side])
7488             return pCnt[BlackRook-side] ||
7489                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7490                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7491                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7492         if(pCnt[WhiteCannon+side]) {
7493             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7494             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7495         }
7496         if(pCnt[WhiteKnight+side])
7497             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7498         return FALSE;
7499 }
7500
7501 int
7502 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7503 {
7504         VariantClass v = gameInfo.variant;
7505
7506         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7507         if(v == VariantShatranj) return TRUE; // always winnable through baring
7508         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7509         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7510
7511         if(v == VariantXiangqi) {
7512                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7513
7514                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7515                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7516                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7517                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7518                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7519                 if(stale) // we have at least one last-rank P plus perhaps C
7520                     return majors // KPKX
7521                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7522                 else // KCA*E*
7523                     return pCnt[WhiteFerz+side] // KCAK
7524                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7525                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7526                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7527
7528         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7529                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7530
7531                 if(nMine == 1) return FALSE; // bare King
7532                 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
7533                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7534                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7535                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7536                 if(pCnt[WhiteKnight+side])
7537                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7538                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7539                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7540                 if(nBishops)
7541                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7542                 if(pCnt[WhiteAlfil+side])
7543                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7544                 if(pCnt[WhiteWazir+side])
7545                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7546         }
7547
7548         return TRUE;
7549 }
7550
7551 int
7552 CompareWithRights (Board b1, Board b2)
7553 {
7554     int rights = 0;
7555     if(!CompareBoards(b1, b2)) return FALSE;
7556     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7557     /* compare castling rights */
7558     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7559            rights++; /* King lost rights, while rook still had them */
7560     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7561         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7562            rights++; /* but at least one rook lost them */
7563     }
7564     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7565            rights++;
7566     if( b1[CASTLING][5] != NoRights ) {
7567         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7568            rights++;
7569     }
7570     return rights == 0;
7571 }
7572
7573 int
7574 Adjudicate (ChessProgramState *cps)
7575 {       // [HGM] some adjudications useful with buggy engines
7576         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7577         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7578         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7579         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7580         int k, count = 0; static int bare = 1;
7581         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7582         Boolean canAdjudicate = !appData.icsActive;
7583
7584         // most tests only when we understand the game, i.e. legality-checking on
7585             if( appData.testLegality )
7586             {   /* [HGM] Some more adjudications for obstinate engines */
7587                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7588                 static int moveCount = 6;
7589                 ChessMove result;
7590                 char *reason = NULL;
7591
7592                 /* Count what is on board. */
7593                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7594
7595                 /* Some material-based adjudications that have to be made before stalemate test */
7596                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7597                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7598                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7599                      if(canAdjudicate && appData.checkMates) {
7600                          if(engineOpponent)
7601                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7602                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7603                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7604                          return 1;
7605                      }
7606                 }
7607
7608                 /* Bare King in Shatranj (loses) or Losers (wins) */
7609                 if( nrW == 1 || nrB == 1) {
7610                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7611                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7612                      if(canAdjudicate && appData.checkMates) {
7613                          if(engineOpponent)
7614                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7615                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7616                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7617                          return 1;
7618                      }
7619                   } else
7620                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7621                   {    /* bare King */
7622                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7623                         if(canAdjudicate && appData.checkMates) {
7624                             /* but only adjudicate if adjudication enabled */
7625                             if(engineOpponent)
7626                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7627                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7628                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7629                             return 1;
7630                         }
7631                   }
7632                 } else bare = 1;
7633
7634
7635             // don't wait for engine to announce game end if we can judge ourselves
7636             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7637               case MT_CHECK:
7638                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7639                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7640                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7641                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7642                             checkCnt++;
7643                         if(checkCnt >= 2) {
7644                             reason = "Xboard adjudication: 3rd check";
7645                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7646                             break;
7647                         }
7648                     }
7649                 }
7650               case MT_NONE:
7651               default:
7652                 break;
7653               case MT_STALEMATE:
7654               case MT_STAINMATE:
7655                 reason = "Xboard adjudication: Stalemate";
7656                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7657                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7658                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7659                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7660                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7661                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7662                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7663                                                                         EP_CHECKMATE : EP_WINS);
7664                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7665                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7666                 }
7667                 break;
7668               case MT_CHECKMATE:
7669                 reason = "Xboard adjudication: Checkmate";
7670                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7671                 break;
7672             }
7673
7674                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7675                     case EP_STALEMATE:
7676                         result = GameIsDrawn; break;
7677                     case EP_CHECKMATE:
7678                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7679                     case EP_WINS:
7680                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7681                     default:
7682                         result = EndOfFile;
7683                 }
7684                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7685                     if(engineOpponent)
7686                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7687                     GameEnds( result, reason, GE_XBOARD );
7688                     return 1;
7689                 }
7690
7691                 /* Next absolutely insufficient mating material. */
7692                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7693                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7694                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7695
7696                      /* always flag draws, for judging claims */
7697                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7698
7699                      if(canAdjudicate && appData.materialDraws) {
7700                          /* but only adjudicate them if adjudication enabled */
7701                          if(engineOpponent) {
7702                            SendToProgram("force\n", engineOpponent); // suppress reply
7703                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7704                          }
7705                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7706                          return 1;
7707                      }
7708                 }
7709
7710                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7711                 if(gameInfo.variant == VariantXiangqi ?
7712                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7713                  : nrW + nrB == 4 &&
7714                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7715                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7716                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7717                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7718                    ) ) {
7719                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7720                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7721                           if(engineOpponent) {
7722                             SendToProgram("force\n", engineOpponent); // suppress reply
7723                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7724                           }
7725                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7726                           return 1;
7727                      }
7728                 } else moveCount = 6;
7729             }
7730
7731         // Repetition draws and 50-move rule can be applied independently of legality testing
7732
7733                 /* Check for rep-draws */
7734                 count = 0;
7735                 for(k = forwardMostMove-2;
7736                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7737                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7738                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7739                     k-=2)
7740                 {   int rights=0;
7741                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7742                         /* compare castling rights */
7743                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7744                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7745                                 rights++; /* King lost rights, while rook still had them */
7746                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7747                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7748                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7749                                    rights++; /* but at least one rook lost them */
7750                         }
7751                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7752                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7753                                 rights++;
7754                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7755                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7756                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7757                                    rights++;
7758                         }
7759                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7760                             && appData.drawRepeats > 1) {
7761                              /* adjudicate after user-specified nr of repeats */
7762                              int result = GameIsDrawn;
7763                              char *details = "XBoard adjudication: repetition draw";
7764                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7765                                 // [HGM] xiangqi: check for forbidden perpetuals
7766                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7767                                 for(m=forwardMostMove; m>k; m-=2) {
7768                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7769                                         ourPerpetual = 0; // the current mover did not always check
7770                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7771                                         hisPerpetual = 0; // the opponent did not always check
7772                                 }
7773                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7774                                                                         ourPerpetual, hisPerpetual);
7775                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7776                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7777                                     details = "Xboard adjudication: perpetual checking";
7778                                 } else
7779                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7780                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7781                                 } else
7782                                 // Now check for perpetual chases
7783                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7784                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7785                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7786                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7787                                         static char resdet[MSG_SIZ];
7788                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7789                                         details = resdet;
7790                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7791                                     } else
7792                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7793                                         break; // Abort repetition-checking loop.
7794                                 }
7795                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7796                              }
7797                              if(engineOpponent) {
7798                                SendToProgram("force\n", engineOpponent); // suppress reply
7799                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7800                              }
7801                              GameEnds( result, details, GE_XBOARD );
7802                              return 1;
7803                         }
7804                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7805                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7806                     }
7807                 }
7808
7809                 /* Now we test for 50-move draws. Determine ply count */
7810                 count = forwardMostMove;
7811                 /* look for last irreversble move */
7812                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7813                     count--;
7814                 /* if we hit starting position, add initial plies */
7815                 if( count == backwardMostMove )
7816                     count -= initialRulePlies;
7817                 count = forwardMostMove - count;
7818                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7819                         // adjust reversible move counter for checks in Xiangqi
7820                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7821                         if(i < backwardMostMove) i = backwardMostMove;
7822                         while(i <= forwardMostMove) {
7823                                 lastCheck = inCheck; // check evasion does not count
7824                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7825                                 if(inCheck || lastCheck) count--; // check does not count
7826                                 i++;
7827                         }
7828                 }
7829                 if( count >= 100)
7830                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7831                          /* this is used to judge if draw claims are legal */
7832                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7833                          if(engineOpponent) {
7834                            SendToProgram("force\n", engineOpponent); // suppress reply
7835                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7836                          }
7837                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7838                          return 1;
7839                 }
7840
7841                 /* if draw offer is pending, treat it as a draw claim
7842                  * when draw condition present, to allow engines a way to
7843                  * claim draws before making their move to avoid a race
7844                  * condition occurring after their move
7845                  */
7846                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7847                          char *p = NULL;
7848                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7849                              p = "Draw claim: 50-move rule";
7850                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7851                              p = "Draw claim: 3-fold repetition";
7852                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7853                              p = "Draw claim: insufficient mating material";
7854                          if( p != NULL && canAdjudicate) {
7855                              if(engineOpponent) {
7856                                SendToProgram("force\n", engineOpponent); // suppress reply
7857                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7858                              }
7859                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7860                              return 1;
7861                          }
7862                 }
7863
7864                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7865                     if(engineOpponent) {
7866                       SendToProgram("force\n", engineOpponent); // suppress reply
7867                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7868                     }
7869                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7870                     return 1;
7871                 }
7872         return 0;
7873 }
7874
7875 char *
7876 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7877 {   // [HGM] book: this routine intercepts moves to simulate book replies
7878     char *bookHit = NULL;
7879
7880     //first determine if the incoming move brings opponent into his book
7881     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7882         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7883     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7884     if(bookHit != NULL && !cps->bookSuspend) {
7885         // make sure opponent is not going to reply after receiving move to book position
7886         SendToProgram("force\n", cps);
7887         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7888     }
7889     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7890     // now arrange restart after book miss
7891     if(bookHit) {
7892         // after a book hit we never send 'go', and the code after the call to this routine
7893         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7894         char buf[MSG_SIZ], *move = bookHit;
7895         if(cps->useSAN) {
7896             int fromX, fromY, toX, toY;
7897             char promoChar;
7898             ChessMove moveType;
7899             move = buf + 30;
7900             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7901                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7902                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7903                                     PosFlags(forwardMostMove),
7904                                     fromY, fromX, toY, toX, promoChar, move);
7905             } else {
7906                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7907                 bookHit = NULL;
7908             }
7909         }
7910         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7911         SendToProgram(buf, cps);
7912         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7913     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7914         SendToProgram("go\n", cps);
7915         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7916     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7917         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7918             SendToProgram("go\n", cps);
7919         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7920     }
7921     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7922 }
7923
7924 int
7925 LoadError (char *errmess, ChessProgramState *cps)
7926 {   // unloads engine and switches back to -ncp mode if it was first
7927     if(cps->initDone) return FALSE;
7928     cps->isr = NULL; // this should suppress further error popups from breaking pipes
7929     DestroyChildProcess(cps->pr, 9 ); // just to be sure
7930     cps->pr = NoProc; 
7931     if(cps == &first) {
7932         appData.noChessProgram = TRUE;
7933         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
7934         gameMode = BeginningOfGame; ModeHighlight();
7935         SetNCPMode();
7936     }
7937     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
7938     DisplayMessage("", ""); // erase waiting message
7939     if(errmess) DisplayError(errmess, 0); // announce reason, if given
7940     return TRUE;
7941 }
7942
7943 char *savedMessage;
7944 ChessProgramState *savedState;
7945 void
7946 DeferredBookMove (void)
7947 {
7948         if(savedState->lastPing != savedState->lastPong)
7949                     ScheduleDelayedEvent(DeferredBookMove, 10);
7950         else
7951         HandleMachineMove(savedMessage, savedState);
7952 }
7953
7954 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7955
7956 void
7957 HandleMachineMove (char *message, ChessProgramState *cps)
7958 {
7959     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7960     char realname[MSG_SIZ];
7961     int fromX, fromY, toX, toY;
7962     ChessMove moveType;
7963     char promoChar;
7964     char *p, *pv=buf1;
7965     int machineWhite, oldError;
7966     char *bookHit;
7967
7968     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7969         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7970         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7971             DisplayError(_("Invalid pairing from pairing engine"), 0);
7972             return;
7973         }
7974         pairingReceived = 1;
7975         NextMatchGame();
7976         return; // Skim the pairing messages here.
7977     }
7978
7979     oldError = cps->userError; cps->userError = 0;
7980
7981 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7982     /*
7983      * Kludge to ignore BEL characters
7984      */
7985     while (*message == '\007') message++;
7986
7987     /*
7988      * [HGM] engine debug message: ignore lines starting with '#' character
7989      */
7990     if(cps->debug && *message == '#') return;
7991
7992     /*
7993      * Look for book output
7994      */
7995     if (cps == &first && bookRequested) {
7996         if (message[0] == '\t' || message[0] == ' ') {
7997             /* Part of the book output is here; append it */
7998             strcat(bookOutput, message);
7999             strcat(bookOutput, "  \n");
8000             return;
8001         } else if (bookOutput[0] != NULLCHAR) {
8002             /* All of book output has arrived; display it */
8003             char *p = bookOutput;
8004             while (*p != NULLCHAR) {
8005                 if (*p == '\t') *p = ' ';
8006                 p++;
8007             }
8008             DisplayInformation(bookOutput);
8009             bookRequested = FALSE;
8010             /* Fall through to parse the current output */
8011         }
8012     }
8013
8014     /*
8015      * Look for machine move.
8016      */
8017     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8018         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8019     {
8020         /* This method is only useful on engines that support ping */
8021         if (cps->lastPing != cps->lastPong) {
8022           if (gameMode == BeginningOfGame) {
8023             /* Extra move from before last new; ignore */
8024             if (appData.debugMode) {
8025                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8026             }
8027           } else {
8028             if (appData.debugMode) {
8029                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8030                         cps->which, gameMode);
8031             }
8032
8033             SendToProgram("undo\n", cps);
8034           }
8035           return;
8036         }
8037
8038         switch (gameMode) {
8039           case BeginningOfGame:
8040             /* Extra move from before last reset; ignore */
8041             if (appData.debugMode) {
8042                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8043             }
8044             return;
8045
8046           case EndOfGame:
8047           case IcsIdle:
8048           default:
8049             /* Extra move after we tried to stop.  The mode test is
8050                not a reliable way of detecting this problem, but it's
8051                the best we can do on engines that don't support ping.
8052             */
8053             if (appData.debugMode) {
8054                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8055                         cps->which, gameMode);
8056             }
8057             SendToProgram("undo\n", cps);
8058             return;
8059
8060           case MachinePlaysWhite:
8061           case IcsPlayingWhite:
8062             machineWhite = TRUE;
8063             break;
8064
8065           case MachinePlaysBlack:
8066           case IcsPlayingBlack:
8067             machineWhite = FALSE;
8068             break;
8069
8070           case TwoMachinesPlay:
8071             machineWhite = (cps->twoMachinesColor[0] == 'w');
8072             break;
8073         }
8074         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8075             if (appData.debugMode) {
8076                 fprintf(debugFP,
8077                         "Ignoring move out of turn by %s, gameMode %d"
8078                         ", forwardMost %d\n",
8079                         cps->which, gameMode, forwardMostMove);
8080             }
8081             return;
8082         }
8083
8084         if(cps->alphaRank) AlphaRank(machineMove, 4);
8085         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8086                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8087             /* Machine move could not be parsed; ignore it. */
8088           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8089                     machineMove, _(cps->which));
8090             DisplayError(buf1, 0);
8091             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8092                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8093             if (gameMode == TwoMachinesPlay) {
8094               GameEnds(machineWhite ? BlackWins : WhiteWins,
8095                        buf1, GE_XBOARD);
8096             }
8097             return;
8098         }
8099
8100         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8101         /* So we have to redo legality test with true e.p. status here,  */
8102         /* to make sure an illegal e.p. capture does not slip through,   */
8103         /* to cause a forfeit on a justified illegal-move complaint      */
8104         /* of the opponent.                                              */
8105         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8106            ChessMove moveType;
8107            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8108                              fromY, fromX, toY, toX, promoChar);
8109             if(moveType == IllegalMove) {
8110               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8111                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8112                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8113                            buf1, GE_XBOARD);
8114                 return;
8115            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8116            /* [HGM] Kludge to handle engines that send FRC-style castling
8117               when they shouldn't (like TSCP-Gothic) */
8118            switch(moveType) {
8119              case WhiteASideCastleFR:
8120              case BlackASideCastleFR:
8121                toX+=2;
8122                currentMoveString[2]++;
8123                break;
8124              case WhiteHSideCastleFR:
8125              case BlackHSideCastleFR:
8126                toX--;
8127                currentMoveString[2]--;
8128                break;
8129              default: ; // nothing to do, but suppresses warning of pedantic compilers
8130            }
8131         }
8132         hintRequested = FALSE;
8133         lastHint[0] = NULLCHAR;
8134         bookRequested = FALSE;
8135         /* Program may be pondering now */
8136         cps->maybeThinking = TRUE;
8137         if (cps->sendTime == 2) cps->sendTime = 1;
8138         if (cps->offeredDraw) cps->offeredDraw--;
8139
8140         /* [AS] Save move info*/
8141         pvInfoList[ forwardMostMove ].score = programStats.score;
8142         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8143         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8144
8145         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8146
8147         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8148         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8149             int count = 0;
8150
8151             while( count < adjudicateLossPlies ) {
8152                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8153
8154                 if( count & 1 ) {
8155                     score = -score; /* Flip score for winning side */
8156                 }
8157
8158                 if( score > adjudicateLossThreshold ) {
8159                     break;
8160                 }
8161
8162                 count++;
8163             }
8164
8165             if( count >= adjudicateLossPlies ) {
8166                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8167
8168                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8169                     "Xboard adjudication",
8170                     GE_XBOARD );
8171
8172                 return;
8173             }
8174         }
8175
8176         if(Adjudicate(cps)) {
8177             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8178             return; // [HGM] adjudicate: for all automatic game ends
8179         }
8180
8181 #if ZIPPY
8182         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8183             first.initDone) {
8184           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8185                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8186                 SendToICS("draw ");
8187                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8188           }
8189           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8190           ics_user_moved = 1;
8191           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8192                 char buf[3*MSG_SIZ];
8193
8194                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8195                         programStats.score / 100.,
8196                         programStats.depth,
8197                         programStats.time / 100.,
8198                         (unsigned int)programStats.nodes,
8199                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8200                         programStats.movelist);
8201                 SendToICS(buf);
8202 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8203           }
8204         }
8205 #endif
8206
8207         /* [AS] Clear stats for next move */
8208         ClearProgramStats();
8209         thinkOutput[0] = NULLCHAR;
8210         hiddenThinkOutputState = 0;
8211
8212         bookHit = NULL;
8213         if (gameMode == TwoMachinesPlay) {
8214             /* [HGM] relaying draw offers moved to after reception of move */
8215             /* and interpreting offer as claim if it brings draw condition */
8216             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8217                 SendToProgram("draw\n", cps->other);
8218             }
8219             if (cps->other->sendTime) {
8220                 SendTimeRemaining(cps->other,
8221                                   cps->other->twoMachinesColor[0] == 'w');
8222             }
8223             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8224             if (firstMove && !bookHit) {
8225                 firstMove = FALSE;
8226                 if (cps->other->useColors) {
8227                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8228                 }
8229                 SendToProgram("go\n", cps->other);
8230             }
8231             cps->other->maybeThinking = TRUE;
8232         }
8233
8234         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8235
8236         if (!pausing && appData.ringBellAfterMoves) {
8237             RingBell();
8238         }
8239
8240         /*
8241          * Reenable menu items that were disabled while
8242          * machine was thinking
8243          */
8244         if (gameMode != TwoMachinesPlay)
8245             SetUserThinkingEnables();
8246
8247         // [HGM] book: after book hit opponent has received move and is now in force mode
8248         // force the book reply into it, and then fake that it outputted this move by jumping
8249         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8250         if(bookHit) {
8251                 static char bookMove[MSG_SIZ]; // a bit generous?
8252
8253                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8254                 strcat(bookMove, bookHit);
8255                 message = bookMove;
8256                 cps = cps->other;
8257                 programStats.nodes = programStats.depth = programStats.time =
8258                 programStats.score = programStats.got_only_move = 0;
8259                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8260
8261                 if(cps->lastPing != cps->lastPong) {
8262                     savedMessage = message; // args for deferred call
8263                     savedState = cps;
8264                     ScheduleDelayedEvent(DeferredBookMove, 10);
8265                     return;
8266                 }
8267                 goto FakeBookMove;
8268         }
8269
8270         return;
8271     }
8272
8273     /* Set special modes for chess engines.  Later something general
8274      *  could be added here; for now there is just one kludge feature,
8275      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8276      *  when "xboard" is given as an interactive command.
8277      */
8278     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8279         cps->useSigint = FALSE;
8280         cps->useSigterm = FALSE;
8281     }
8282     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8283       ParseFeatures(message+8, cps);
8284       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8285     }
8286
8287     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8288                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8289       int dummy, s=6; char buf[MSG_SIZ];
8290       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8291       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8292       if(startedFromSetupPosition) return;
8293       ParseFEN(boards[0], &dummy, message+s);
8294       DrawPosition(TRUE, boards[0]);
8295       startedFromSetupPosition = TRUE;
8296       return;
8297     }
8298     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8299      * want this, I was asked to put it in, and obliged.
8300      */
8301     if (!strncmp(message, "setboard ", 9)) {
8302         Board initial_position;
8303
8304         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8305
8306         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8307             DisplayError(_("Bad FEN received from engine"), 0);
8308             return ;
8309         } else {
8310            Reset(TRUE, FALSE);
8311            CopyBoard(boards[0], initial_position);
8312            initialRulePlies = FENrulePlies;
8313            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8314            else gameMode = MachinePlaysBlack;
8315            DrawPosition(FALSE, boards[currentMove]);
8316         }
8317         return;
8318     }
8319
8320     /*
8321      * Look for communication commands
8322      */
8323     if (!strncmp(message, "telluser ", 9)) {
8324         if(message[9] == '\\' && message[10] == '\\')
8325             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8326         PlayTellSound();
8327         DisplayNote(message + 9);
8328         return;
8329     }
8330     if (!strncmp(message, "tellusererror ", 14)) {
8331         cps->userError = 1;
8332         if(message[14] == '\\' && message[15] == '\\')
8333             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8334         PlayTellSound();
8335         DisplayError(message + 14, 0);
8336         return;
8337     }
8338     if (!strncmp(message, "tellopponent ", 13)) {
8339       if (appData.icsActive) {
8340         if (loggedOn) {
8341           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8342           SendToICS(buf1);
8343         }
8344       } else {
8345         DisplayNote(message + 13);
8346       }
8347       return;
8348     }
8349     if (!strncmp(message, "tellothers ", 11)) {
8350       if (appData.icsActive) {
8351         if (loggedOn) {
8352           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8353           SendToICS(buf1);
8354         }
8355       }
8356       return;
8357     }
8358     if (!strncmp(message, "tellall ", 8)) {
8359       if (appData.icsActive) {
8360         if (loggedOn) {
8361           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8362           SendToICS(buf1);
8363         }
8364       } else {
8365         DisplayNote(message + 8);
8366       }
8367       return;
8368     }
8369     if (strncmp(message, "warning", 7) == 0) {
8370         /* Undocumented feature, use tellusererror in new code */
8371         DisplayError(message, 0);
8372         return;
8373     }
8374     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8375         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8376         strcat(realname, " query");
8377         AskQuestion(realname, buf2, buf1, cps->pr);
8378         return;
8379     }
8380     /* Commands from the engine directly to ICS.  We don't allow these to be
8381      *  sent until we are logged on. Crafty kibitzes have been known to
8382      *  interfere with the login process.
8383      */
8384     if (loggedOn) {
8385         if (!strncmp(message, "tellics ", 8)) {
8386             SendToICS(message + 8);
8387             SendToICS("\n");
8388             return;
8389         }
8390         if (!strncmp(message, "tellicsnoalias ", 15)) {
8391             SendToICS(ics_prefix);
8392             SendToICS(message + 15);
8393             SendToICS("\n");
8394             return;
8395         }
8396         /* The following are for backward compatibility only */
8397         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8398             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8399             SendToICS(ics_prefix);
8400             SendToICS(message);
8401             SendToICS("\n");
8402             return;
8403         }
8404     }
8405     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8406         return;
8407     }
8408     /*
8409      * If the move is illegal, cancel it and redraw the board.
8410      * Also deal with other error cases.  Matching is rather loose
8411      * here to accommodate engines written before the spec.
8412      */
8413     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8414         strncmp(message, "Error", 5) == 0) {
8415         if (StrStr(message, "name") ||
8416             StrStr(message, "rating") || StrStr(message, "?") ||
8417             StrStr(message, "result") || StrStr(message, "board") ||
8418             StrStr(message, "bk") || StrStr(message, "computer") ||
8419             StrStr(message, "variant") || StrStr(message, "hint") ||
8420             StrStr(message, "random") || StrStr(message, "depth") ||
8421             StrStr(message, "accepted")) {
8422             return;
8423         }
8424         if (StrStr(message, "protover")) {
8425           /* Program is responding to input, so it's apparently done
8426              initializing, and this error message indicates it is
8427              protocol version 1.  So we don't need to wait any longer
8428              for it to initialize and send feature commands. */
8429           FeatureDone(cps, 1);
8430           cps->protocolVersion = 1;
8431           return;
8432         }
8433         cps->maybeThinking = FALSE;
8434
8435         if (StrStr(message, "draw")) {
8436             /* Program doesn't have "draw" command */
8437             cps->sendDrawOffers = 0;
8438             return;
8439         }
8440         if (cps->sendTime != 1 &&
8441             (StrStr(message, "time") || StrStr(message, "otim"))) {
8442           /* Program apparently doesn't have "time" or "otim" command */
8443           cps->sendTime = 0;
8444           return;
8445         }
8446         if (StrStr(message, "analyze")) {
8447             cps->analysisSupport = FALSE;
8448             cps->analyzing = FALSE;
8449 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8450             EditGameEvent(); // [HGM] try to preserve loaded game
8451             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8452             DisplayError(buf2, 0);
8453             return;
8454         }
8455         if (StrStr(message, "(no matching move)st")) {
8456           /* Special kludge for GNU Chess 4 only */
8457           cps->stKludge = TRUE;
8458           SendTimeControl(cps, movesPerSession, timeControl,
8459                           timeIncrement, appData.searchDepth,
8460                           searchTime);
8461           return;
8462         }
8463         if (StrStr(message, "(no matching move)sd")) {
8464           /* Special kludge for GNU Chess 4 only */
8465           cps->sdKludge = TRUE;
8466           SendTimeControl(cps, movesPerSession, timeControl,
8467                           timeIncrement, appData.searchDepth,
8468                           searchTime);
8469           return;
8470         }
8471         if (!StrStr(message, "llegal")) {
8472             return;
8473         }
8474         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8475             gameMode == IcsIdle) return;
8476         if (forwardMostMove <= backwardMostMove) return;
8477         if (pausing) PauseEvent();
8478       if(appData.forceIllegal) {
8479             // [HGM] illegal: machine refused move; force position after move into it
8480           SendToProgram("force\n", cps);
8481           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8482                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8483                 // when black is to move, while there might be nothing on a2 or black
8484                 // might already have the move. So send the board as if white has the move.
8485                 // But first we must change the stm of the engine, as it refused the last move
8486                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8487                 if(WhiteOnMove(forwardMostMove)) {
8488                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8489                     SendBoard(cps, forwardMostMove); // kludgeless board
8490                 } else {
8491                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8492                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8493                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8494                 }
8495           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8496             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8497                  gameMode == TwoMachinesPlay)
8498               SendToProgram("go\n", cps);
8499             return;
8500       } else
8501         if (gameMode == PlayFromGameFile) {
8502             /* Stop reading this game file */
8503             gameMode = EditGame;
8504             ModeHighlight();
8505         }
8506         /* [HGM] illegal-move claim should forfeit game when Xboard */
8507         /* only passes fully legal moves                            */
8508         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8509             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8510                                 "False illegal-move claim", GE_XBOARD );
8511             return; // do not take back move we tested as valid
8512         }
8513         currentMove = forwardMostMove-1;
8514         DisplayMove(currentMove-1); /* before DisplayMoveError */
8515         SwitchClocks(forwardMostMove-1); // [HGM] race
8516         DisplayBothClocks();
8517         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8518                 parseList[currentMove], _(cps->which));
8519         DisplayMoveError(buf1);
8520         DrawPosition(FALSE, boards[currentMove]);
8521
8522         SetUserThinkingEnables();
8523         return;
8524     }
8525     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8526         /* Program has a broken "time" command that
8527            outputs a string not ending in newline.
8528            Don't use it. */
8529         cps->sendTime = 0;
8530     }
8531
8532     /*
8533      * If chess program startup fails, exit with an error message.
8534      * Attempts to recover here are futile. [HGM] Well, we try anyway
8535      */
8536     if ((StrStr(message, "unknown host") != NULL)
8537         || (StrStr(message, "No remote directory") != NULL)
8538         || (StrStr(message, "not found") != NULL)
8539         || (StrStr(message, "No such file") != NULL)
8540         || (StrStr(message, "can't alloc") != NULL)
8541         || (StrStr(message, "Permission denied") != NULL)) {
8542
8543         cps->maybeThinking = FALSE;
8544         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8545                 _(cps->which), cps->program, cps->host, message);
8546         RemoveInputSource(cps->isr);
8547         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8548             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8549             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8550         }
8551         return;
8552     }
8553
8554     /*
8555      * Look for hint output
8556      */
8557     if (sscanf(message, "Hint: %s", buf1) == 1) {
8558         if (cps == &first && hintRequested) {
8559             hintRequested = FALSE;
8560             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8561                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8562                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8563                                     PosFlags(forwardMostMove),
8564                                     fromY, fromX, toY, toX, promoChar, buf1);
8565                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8566                 DisplayInformation(buf2);
8567             } else {
8568                 /* Hint move could not be parsed!? */
8569               snprintf(buf2, sizeof(buf2),
8570                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8571                         buf1, _(cps->which));
8572                 DisplayError(buf2, 0);
8573             }
8574         } else {
8575           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8576         }
8577         return;
8578     }
8579
8580     /*
8581      * Ignore other messages if game is not in progress
8582      */
8583     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8584         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8585
8586     /*
8587      * look for win, lose, draw, or draw offer
8588      */
8589     if (strncmp(message, "1-0", 3) == 0) {
8590         char *p, *q, *r = "";
8591         p = strchr(message, '{');
8592         if (p) {
8593             q = strchr(p, '}');
8594             if (q) {
8595                 *q = NULLCHAR;
8596                 r = p + 1;
8597             }
8598         }
8599         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8600         return;
8601     } else if (strncmp(message, "0-1", 3) == 0) {
8602         char *p, *q, *r = "";
8603         p = strchr(message, '{');
8604         if (p) {
8605             q = strchr(p, '}');
8606             if (q) {
8607                 *q = NULLCHAR;
8608                 r = p + 1;
8609             }
8610         }
8611         /* Kludge for Arasan 4.1 bug */
8612         if (strcmp(r, "Black resigns") == 0) {
8613             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8614             return;
8615         }
8616         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8617         return;
8618     } else if (strncmp(message, "1/2", 3) == 0) {
8619         char *p, *q, *r = "";
8620         p = strchr(message, '{');
8621         if (p) {
8622             q = strchr(p, '}');
8623             if (q) {
8624                 *q = NULLCHAR;
8625                 r = p + 1;
8626             }
8627         }
8628
8629         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8630         return;
8631
8632     } else if (strncmp(message, "White resign", 12) == 0) {
8633         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8634         return;
8635     } else if (strncmp(message, "Black resign", 12) == 0) {
8636         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8637         return;
8638     } else if (strncmp(message, "White matches", 13) == 0 ||
8639                strncmp(message, "Black matches", 13) == 0   ) {
8640         /* [HGM] ignore GNUShogi noises */
8641         return;
8642     } else if (strncmp(message, "White", 5) == 0 &&
8643                message[5] != '(' &&
8644                StrStr(message, "Black") == NULL) {
8645         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8646         return;
8647     } else if (strncmp(message, "Black", 5) == 0 &&
8648                message[5] != '(') {
8649         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8650         return;
8651     } else if (strcmp(message, "resign") == 0 ||
8652                strcmp(message, "computer resigns") == 0) {
8653         switch (gameMode) {
8654           case MachinePlaysBlack:
8655           case IcsPlayingBlack:
8656             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8657             break;
8658           case MachinePlaysWhite:
8659           case IcsPlayingWhite:
8660             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8661             break;
8662           case TwoMachinesPlay:
8663             if (cps->twoMachinesColor[0] == 'w')
8664               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8665             else
8666               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8667             break;
8668           default:
8669             /* can't happen */
8670             break;
8671         }
8672         return;
8673     } else if (strncmp(message, "opponent mates", 14) == 0) {
8674         switch (gameMode) {
8675           case MachinePlaysBlack:
8676           case IcsPlayingBlack:
8677             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8678             break;
8679           case MachinePlaysWhite:
8680           case IcsPlayingWhite:
8681             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8682             break;
8683           case TwoMachinesPlay:
8684             if (cps->twoMachinesColor[0] == 'w')
8685               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8686             else
8687               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8688             break;
8689           default:
8690             /* can't happen */
8691             break;
8692         }
8693         return;
8694     } else if (strncmp(message, "computer mates", 14) == 0) {
8695         switch (gameMode) {
8696           case MachinePlaysBlack:
8697           case IcsPlayingBlack:
8698             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8699             break;
8700           case MachinePlaysWhite:
8701           case IcsPlayingWhite:
8702             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8703             break;
8704           case TwoMachinesPlay:
8705             if (cps->twoMachinesColor[0] == 'w')
8706               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8707             else
8708               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8709             break;
8710           default:
8711             /* can't happen */
8712             break;
8713         }
8714         return;
8715     } else if (strncmp(message, "checkmate", 9) == 0) {
8716         if (WhiteOnMove(forwardMostMove)) {
8717             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8718         } else {
8719             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8720         }
8721         return;
8722     } else if (strstr(message, "Draw") != NULL ||
8723                strstr(message, "game is a draw") != NULL) {
8724         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8725         return;
8726     } else if (strstr(message, "offer") != NULL &&
8727                strstr(message, "draw") != NULL) {
8728 #if ZIPPY
8729         if (appData.zippyPlay && first.initDone) {
8730             /* Relay offer to ICS */
8731             SendToICS(ics_prefix);
8732             SendToICS("draw\n");
8733         }
8734 #endif
8735         cps->offeredDraw = 2; /* valid until this engine moves twice */
8736         if (gameMode == TwoMachinesPlay) {
8737             if (cps->other->offeredDraw) {
8738                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8739             /* [HGM] in two-machine mode we delay relaying draw offer      */
8740             /* until after we also have move, to see if it is really claim */
8741             }
8742         } else if (gameMode == MachinePlaysWhite ||
8743                    gameMode == MachinePlaysBlack) {
8744           if (userOfferedDraw) {
8745             DisplayInformation(_("Machine accepts your draw offer"));
8746             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8747           } else {
8748             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8749           }
8750         }
8751     }
8752
8753
8754     /*
8755      * Look for thinking output
8756      */
8757     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8758           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8759                                 ) {
8760         int plylev, mvleft, mvtot, curscore, time;
8761         char mvname[MOVE_LEN];
8762         u64 nodes; // [DM]
8763         char plyext;
8764         int ignore = FALSE;
8765         int prefixHint = FALSE;
8766         mvname[0] = NULLCHAR;
8767
8768         switch (gameMode) {
8769           case MachinePlaysBlack:
8770           case IcsPlayingBlack:
8771             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8772             break;
8773           case MachinePlaysWhite:
8774           case IcsPlayingWhite:
8775             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8776             break;
8777           case AnalyzeMode:
8778           case AnalyzeFile:
8779             break;
8780           case IcsObserving: /* [DM] icsEngineAnalyze */
8781             if (!appData.icsEngineAnalyze) ignore = TRUE;
8782             break;
8783           case TwoMachinesPlay:
8784             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8785                 ignore = TRUE;
8786             }
8787             break;
8788           default:
8789             ignore = TRUE;
8790             break;
8791         }
8792
8793         if (!ignore) {
8794             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8795             buf1[0] = NULLCHAR;
8796             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8797                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8798
8799                 if (plyext != ' ' && plyext != '\t') {
8800                     time *= 100;
8801                 }
8802
8803                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8804                 if( cps->scoreIsAbsolute &&
8805                     ( gameMode == MachinePlaysBlack ||
8806                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8807                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8808                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8809                      !WhiteOnMove(currentMove)
8810                     ) )
8811                 {
8812                     curscore = -curscore;
8813                 }
8814
8815                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8816
8817                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8818                         char buf[MSG_SIZ];
8819                         FILE *f;
8820                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8821                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8822                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8823                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8824                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8825                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8826                                 fclose(f);
8827                         } else DisplayError(_("failed writing PV"), 0);
8828                 }
8829
8830                 tempStats.depth = plylev;
8831                 tempStats.nodes = nodes;
8832                 tempStats.time = time;
8833                 tempStats.score = curscore;
8834                 tempStats.got_only_move = 0;
8835
8836                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8837                         int ticklen;
8838
8839                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8840                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8841                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8842                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8843                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8844                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8845                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8846                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8847                 }
8848
8849                 /* Buffer overflow protection */
8850                 if (pv[0] != NULLCHAR) {
8851                     if (strlen(pv) >= sizeof(tempStats.movelist)
8852                         && appData.debugMode) {
8853                         fprintf(debugFP,
8854                                 "PV is too long; using the first %u bytes.\n",
8855                                 (unsigned) sizeof(tempStats.movelist) - 1);
8856                     }
8857
8858                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8859                 } else {
8860                     sprintf(tempStats.movelist, " no PV\n");
8861                 }
8862
8863                 if (tempStats.seen_stat) {
8864                     tempStats.ok_to_send = 1;
8865                 }
8866
8867                 if (strchr(tempStats.movelist, '(') != NULL) {
8868                     tempStats.line_is_book = 1;
8869                     tempStats.nr_moves = 0;
8870                     tempStats.moves_left = 0;
8871                 } else {
8872                     tempStats.line_is_book = 0;
8873                 }
8874
8875                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8876                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8877
8878                 SendProgramStatsToFrontend( cps, &tempStats );
8879
8880                 /*
8881                     [AS] Protect the thinkOutput buffer from overflow... this
8882                     is only useful if buf1 hasn't overflowed first!
8883                 */
8884                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8885                          plylev,
8886                          (gameMode == TwoMachinesPlay ?
8887                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8888                          ((double) curscore) / 100.0,
8889                          prefixHint ? lastHint : "",
8890                          prefixHint ? " " : "" );
8891
8892                 if( buf1[0] != NULLCHAR ) {
8893                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8894
8895                     if( strlen(pv) > max_len ) {
8896                         if( appData.debugMode) {
8897                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8898                         }
8899                         pv[max_len+1] = '\0';
8900                     }
8901
8902                     strcat( thinkOutput, pv);
8903                 }
8904
8905                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8906                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8907                     DisplayMove(currentMove - 1);
8908                 }
8909                 return;
8910
8911             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8912                 /* crafty (9.25+) says "(only move) <move>"
8913                  * if there is only 1 legal move
8914                  */
8915                 sscanf(p, "(only move) %s", buf1);
8916                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8917                 sprintf(programStats.movelist, "%s (only move)", buf1);
8918                 programStats.depth = 1;
8919                 programStats.nr_moves = 1;
8920                 programStats.moves_left = 1;
8921                 programStats.nodes = 1;
8922                 programStats.time = 1;
8923                 programStats.got_only_move = 1;
8924
8925                 /* Not really, but we also use this member to
8926                    mean "line isn't going to change" (Crafty
8927                    isn't searching, so stats won't change) */
8928                 programStats.line_is_book = 1;
8929
8930                 SendProgramStatsToFrontend( cps, &programStats );
8931
8932                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8933                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8934                     DisplayMove(currentMove - 1);
8935                 }
8936                 return;
8937             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8938                               &time, &nodes, &plylev, &mvleft,
8939                               &mvtot, mvname) >= 5) {
8940                 /* The stat01: line is from Crafty (9.29+) in response
8941                    to the "." command */
8942                 programStats.seen_stat = 1;
8943                 cps->maybeThinking = TRUE;
8944
8945                 if (programStats.got_only_move || !appData.periodicUpdates)
8946                   return;
8947
8948                 programStats.depth = plylev;
8949                 programStats.time = time;
8950                 programStats.nodes = nodes;
8951                 programStats.moves_left = mvleft;
8952                 programStats.nr_moves = mvtot;
8953                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8954                 programStats.ok_to_send = 1;
8955                 programStats.movelist[0] = '\0';
8956
8957                 SendProgramStatsToFrontend( cps, &programStats );
8958
8959                 return;
8960
8961             } else if (strncmp(message,"++",2) == 0) {
8962                 /* Crafty 9.29+ outputs this */
8963                 programStats.got_fail = 2;
8964                 return;
8965
8966             } else if (strncmp(message,"--",2) == 0) {
8967                 /* Crafty 9.29+ outputs this */
8968                 programStats.got_fail = 1;
8969                 return;
8970
8971             } else if (thinkOutput[0] != NULLCHAR &&
8972                        strncmp(message, "    ", 4) == 0) {
8973                 unsigned message_len;
8974
8975                 p = message;
8976                 while (*p && *p == ' ') p++;
8977
8978                 message_len = strlen( p );
8979
8980                 /* [AS] Avoid buffer overflow */
8981                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8982                     strcat(thinkOutput, " ");
8983                     strcat(thinkOutput, p);
8984                 }
8985
8986                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8987                     strcat(programStats.movelist, " ");
8988                     strcat(programStats.movelist, p);
8989                 }
8990
8991                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8992                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8993                     DisplayMove(currentMove - 1);
8994                 }
8995                 return;
8996             }
8997         }
8998         else {
8999             buf1[0] = NULLCHAR;
9000
9001             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9002                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9003             {
9004                 ChessProgramStats cpstats;
9005
9006                 if (plyext != ' ' && plyext != '\t') {
9007                     time *= 100;
9008                 }
9009
9010                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9011                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9012                     curscore = -curscore;
9013                 }
9014
9015                 cpstats.depth = plylev;
9016                 cpstats.nodes = nodes;
9017                 cpstats.time = time;
9018                 cpstats.score = curscore;
9019                 cpstats.got_only_move = 0;
9020                 cpstats.movelist[0] = '\0';
9021
9022                 if (buf1[0] != NULLCHAR) {
9023                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9024                 }
9025
9026                 cpstats.ok_to_send = 0;
9027                 cpstats.line_is_book = 0;
9028                 cpstats.nr_moves = 0;
9029                 cpstats.moves_left = 0;
9030
9031                 SendProgramStatsToFrontend( cps, &cpstats );
9032             }
9033         }
9034     }
9035 }
9036
9037
9038 /* Parse a game score from the character string "game", and
9039    record it as the history of the current game.  The game
9040    score is NOT assumed to start from the standard position.
9041    The display is not updated in any way.
9042    */
9043 void
9044 ParseGameHistory (char *game)
9045 {
9046     ChessMove moveType;
9047     int fromX, fromY, toX, toY, boardIndex;
9048     char promoChar;
9049     char *p, *q;
9050     char buf[MSG_SIZ];
9051
9052     if (appData.debugMode)
9053       fprintf(debugFP, "Parsing game history: %s\n", game);
9054
9055     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9056     gameInfo.site = StrSave(appData.icsHost);
9057     gameInfo.date = PGNDate();
9058     gameInfo.round = StrSave("-");
9059
9060     /* Parse out names of players */
9061     while (*game == ' ') game++;
9062     p = buf;
9063     while (*game != ' ') *p++ = *game++;
9064     *p = NULLCHAR;
9065     gameInfo.white = StrSave(buf);
9066     while (*game == ' ') game++;
9067     p = buf;
9068     while (*game != ' ' && *game != '\n') *p++ = *game++;
9069     *p = NULLCHAR;
9070     gameInfo.black = StrSave(buf);
9071
9072     /* Parse moves */
9073     boardIndex = blackPlaysFirst ? 1 : 0;
9074     yynewstr(game);
9075     for (;;) {
9076         yyboardindex = boardIndex;
9077         moveType = (ChessMove) Myylex();
9078         switch (moveType) {
9079           case IllegalMove:             /* maybe suicide chess, etc. */
9080   if (appData.debugMode) {
9081     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9082     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9083     setbuf(debugFP, NULL);
9084   }
9085           case WhitePromotion:
9086           case BlackPromotion:
9087           case WhiteNonPromotion:
9088           case BlackNonPromotion:
9089           case NormalMove:
9090           case WhiteCapturesEnPassant:
9091           case BlackCapturesEnPassant:
9092           case WhiteKingSideCastle:
9093           case WhiteQueenSideCastle:
9094           case BlackKingSideCastle:
9095           case BlackQueenSideCastle:
9096           case WhiteKingSideCastleWild:
9097           case WhiteQueenSideCastleWild:
9098           case BlackKingSideCastleWild:
9099           case BlackQueenSideCastleWild:
9100           /* PUSH Fabien */
9101           case WhiteHSideCastleFR:
9102           case WhiteASideCastleFR:
9103           case BlackHSideCastleFR:
9104           case BlackASideCastleFR:
9105           /* POP Fabien */
9106             fromX = currentMoveString[0] - AAA;
9107             fromY = currentMoveString[1] - ONE;
9108             toX = currentMoveString[2] - AAA;
9109             toY = currentMoveString[3] - ONE;
9110             promoChar = currentMoveString[4];
9111             break;
9112           case WhiteDrop:
9113           case BlackDrop:
9114             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9115             fromX = moveType == WhiteDrop ?
9116               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9117             (int) CharToPiece(ToLower(currentMoveString[0]));
9118             fromY = DROP_RANK;
9119             toX = currentMoveString[2] - AAA;
9120             toY = currentMoveString[3] - ONE;
9121             promoChar = NULLCHAR;
9122             break;
9123           case AmbiguousMove:
9124             /* bug? */
9125             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9126   if (appData.debugMode) {
9127     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9128     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9129     setbuf(debugFP, NULL);
9130   }
9131             DisplayError(buf, 0);
9132             return;
9133           case ImpossibleMove:
9134             /* bug? */
9135             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9136   if (appData.debugMode) {
9137     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9138     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9139     setbuf(debugFP, NULL);
9140   }
9141             DisplayError(buf, 0);
9142             return;
9143           case EndOfFile:
9144             if (boardIndex < backwardMostMove) {
9145                 /* Oops, gap.  How did that happen? */
9146                 DisplayError(_("Gap in move list"), 0);
9147                 return;
9148             }
9149             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9150             if (boardIndex > forwardMostMove) {
9151                 forwardMostMove = boardIndex;
9152             }
9153             return;
9154           case ElapsedTime:
9155             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9156                 strcat(parseList[boardIndex-1], " ");
9157                 strcat(parseList[boardIndex-1], yy_text);
9158             }
9159             continue;
9160           case Comment:
9161           case PGNTag:
9162           case NAG:
9163           default:
9164             /* ignore */
9165             continue;
9166           case WhiteWins:
9167           case BlackWins:
9168           case GameIsDrawn:
9169           case GameUnfinished:
9170             if (gameMode == IcsExamining) {
9171                 if (boardIndex < backwardMostMove) {
9172                     /* Oops, gap.  How did that happen? */
9173                     return;
9174                 }
9175                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9176                 return;
9177             }
9178             gameInfo.result = moveType;
9179             p = strchr(yy_text, '{');
9180             if (p == NULL) p = strchr(yy_text, '(');
9181             if (p == NULL) {
9182                 p = yy_text;
9183                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9184             } else {
9185                 q = strchr(p, *p == '{' ? '}' : ')');
9186                 if (q != NULL) *q = NULLCHAR;
9187                 p++;
9188             }
9189             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9190             gameInfo.resultDetails = StrSave(p);
9191             continue;
9192         }
9193         if (boardIndex >= forwardMostMove &&
9194             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9195             backwardMostMove = blackPlaysFirst ? 1 : 0;
9196             return;
9197         }
9198         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9199                                  fromY, fromX, toY, toX, promoChar,
9200                                  parseList[boardIndex]);
9201         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9202         /* currentMoveString is set as a side-effect of yylex */
9203         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9204         strcat(moveList[boardIndex], "\n");
9205         boardIndex++;
9206         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9207         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9208           case MT_NONE:
9209           case MT_STALEMATE:
9210           default:
9211             break;
9212           case MT_CHECK:
9213             if(gameInfo.variant != VariantShogi)
9214                 strcat(parseList[boardIndex - 1], "+");
9215             break;
9216           case MT_CHECKMATE:
9217           case MT_STAINMATE:
9218             strcat(parseList[boardIndex - 1], "#");
9219             break;
9220         }
9221     }
9222 }
9223
9224
9225 /* Apply a move to the given board  */
9226 void
9227 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9228 {
9229   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9230   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9231
9232     /* [HGM] compute & store e.p. status and castling rights for new position */
9233     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9234
9235       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9236       oldEP = (signed char)board[EP_STATUS];
9237       board[EP_STATUS] = EP_NONE;
9238
9239   if (fromY == DROP_RANK) {
9240         /* must be first */
9241         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9242             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9243             return;
9244         }
9245         piece = board[toY][toX] = (ChessSquare) fromX;
9246   } else {
9247       int i;
9248
9249       if( board[toY][toX] != EmptySquare )
9250            board[EP_STATUS] = EP_CAPTURE;
9251
9252       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9253            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9254                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9255       } else
9256       if( board[fromY][fromX] == WhitePawn ) {
9257            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9258                board[EP_STATUS] = EP_PAWN_MOVE;
9259            if( toY-fromY==2) {
9260                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9261                         gameInfo.variant != VariantBerolina || toX < fromX)
9262                       board[EP_STATUS] = toX | berolina;
9263                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9264                         gameInfo.variant != VariantBerolina || toX > fromX)
9265                       board[EP_STATUS] = toX;
9266            }
9267       } else
9268       if( board[fromY][fromX] == BlackPawn ) {
9269            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9270                board[EP_STATUS] = EP_PAWN_MOVE;
9271            if( toY-fromY== -2) {
9272                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9273                         gameInfo.variant != VariantBerolina || toX < fromX)
9274                       board[EP_STATUS] = toX | berolina;
9275                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9276                         gameInfo.variant != VariantBerolina || toX > fromX)
9277                       board[EP_STATUS] = toX;
9278            }
9279        }
9280
9281        for(i=0; i<nrCastlingRights; i++) {
9282            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9283               board[CASTLING][i] == toX   && castlingRank[i] == toY
9284              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9285        }
9286
9287      if (fromX == toX && fromY == toY) return;
9288
9289      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9290      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9291      if(gameInfo.variant == VariantKnightmate)
9292          king += (int) WhiteUnicorn - (int) WhiteKing;
9293
9294     /* Code added by Tord: */
9295     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9296     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9297         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9298       board[fromY][fromX] = EmptySquare;
9299       board[toY][toX] = EmptySquare;
9300       if((toX > fromX) != (piece == WhiteRook)) {
9301         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9302       } else {
9303         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9304       }
9305     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9306                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9307       board[fromY][fromX] = EmptySquare;
9308       board[toY][toX] = EmptySquare;
9309       if((toX > fromX) != (piece == BlackRook)) {
9310         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9311       } else {
9312         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9313       }
9314     /* End of code added by Tord */
9315
9316     } else if (board[fromY][fromX] == king
9317         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9318         && toY == fromY && toX > fromX+1) {
9319         board[fromY][fromX] = EmptySquare;
9320         board[toY][toX] = king;
9321         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9322         board[fromY][BOARD_RGHT-1] = EmptySquare;
9323     } else if (board[fromY][fromX] == king
9324         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9325                && toY == fromY && toX < fromX-1) {
9326         board[fromY][fromX] = EmptySquare;
9327         board[toY][toX] = king;
9328         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9329         board[fromY][BOARD_LEFT] = EmptySquare;
9330     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9331                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9332                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9333                ) {
9334         /* white pawn promotion */
9335         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9336         if(gameInfo.variant==VariantBughouse ||
9337            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9338             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9339         board[fromY][fromX] = EmptySquare;
9340     } else if ((fromY >= BOARD_HEIGHT>>1)
9341                && (toX != fromX)
9342                && gameInfo.variant != VariantXiangqi
9343                && gameInfo.variant != VariantBerolina
9344                && (board[fromY][fromX] == WhitePawn)
9345                && (board[toY][toX] == EmptySquare)) {
9346         board[fromY][fromX] = EmptySquare;
9347         board[toY][toX] = WhitePawn;
9348         captured = board[toY - 1][toX];
9349         board[toY - 1][toX] = EmptySquare;
9350     } else if ((fromY == BOARD_HEIGHT-4)
9351                && (toX == fromX)
9352                && gameInfo.variant == VariantBerolina
9353                && (board[fromY][fromX] == WhitePawn)
9354                && (board[toY][toX] == EmptySquare)) {
9355         board[fromY][fromX] = EmptySquare;
9356         board[toY][toX] = WhitePawn;
9357         if(oldEP & EP_BEROLIN_A) {
9358                 captured = board[fromY][fromX-1];
9359                 board[fromY][fromX-1] = EmptySquare;
9360         }else{  captured = board[fromY][fromX+1];
9361                 board[fromY][fromX+1] = EmptySquare;
9362         }
9363     } else if (board[fromY][fromX] == king
9364         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9365                && toY == fromY && toX > fromX+1) {
9366         board[fromY][fromX] = EmptySquare;
9367         board[toY][toX] = king;
9368         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9369         board[fromY][BOARD_RGHT-1] = EmptySquare;
9370     } else if (board[fromY][fromX] == king
9371         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9372                && toY == fromY && toX < fromX-1) {
9373         board[fromY][fromX] = EmptySquare;
9374         board[toY][toX] = king;
9375         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9376         board[fromY][BOARD_LEFT] = EmptySquare;
9377     } else if (fromY == 7 && fromX == 3
9378                && board[fromY][fromX] == BlackKing
9379                && toY == 7 && toX == 5) {
9380         board[fromY][fromX] = EmptySquare;
9381         board[toY][toX] = BlackKing;
9382         board[fromY][7] = EmptySquare;
9383         board[toY][4] = BlackRook;
9384     } else if (fromY == 7 && fromX == 3
9385                && board[fromY][fromX] == BlackKing
9386                && toY == 7 && toX == 1) {
9387         board[fromY][fromX] = EmptySquare;
9388         board[toY][toX] = BlackKing;
9389         board[fromY][0] = EmptySquare;
9390         board[toY][2] = BlackRook;
9391     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9392                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9393                && toY < promoRank && promoChar
9394                ) {
9395         /* black pawn promotion */
9396         board[toY][toX] = CharToPiece(ToLower(promoChar));
9397         if(gameInfo.variant==VariantBughouse ||
9398            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9399             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9400         board[fromY][fromX] = EmptySquare;
9401     } else if ((fromY < BOARD_HEIGHT>>1)
9402                && (toX != fromX)
9403                && gameInfo.variant != VariantXiangqi
9404                && gameInfo.variant != VariantBerolina
9405                && (board[fromY][fromX] == BlackPawn)
9406                && (board[toY][toX] == EmptySquare)) {
9407         board[fromY][fromX] = EmptySquare;
9408         board[toY][toX] = BlackPawn;
9409         captured = board[toY + 1][toX];
9410         board[toY + 1][toX] = EmptySquare;
9411     } else if ((fromY == 3)
9412                && (toX == fromX)
9413                && gameInfo.variant == VariantBerolina
9414                && (board[fromY][fromX] == BlackPawn)
9415                && (board[toY][toX] == EmptySquare)) {
9416         board[fromY][fromX] = EmptySquare;
9417         board[toY][toX] = BlackPawn;
9418         if(oldEP & EP_BEROLIN_A) {
9419                 captured = board[fromY][fromX-1];
9420                 board[fromY][fromX-1] = EmptySquare;
9421         }else{  captured = board[fromY][fromX+1];
9422                 board[fromY][fromX+1] = EmptySquare;
9423         }
9424     } else {
9425         board[toY][toX] = board[fromY][fromX];
9426         board[fromY][fromX] = EmptySquare;
9427     }
9428   }
9429
9430     if (gameInfo.holdingsWidth != 0) {
9431
9432       /* !!A lot more code needs to be written to support holdings  */
9433       /* [HGM] OK, so I have written it. Holdings are stored in the */
9434       /* penultimate board files, so they are automaticlly stored   */
9435       /* in the game history.                                       */
9436       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9437                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9438         /* Delete from holdings, by decreasing count */
9439         /* and erasing image if necessary            */
9440         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9441         if(p < (int) BlackPawn) { /* white drop */
9442              p -= (int)WhitePawn;
9443                  p = PieceToNumber((ChessSquare)p);
9444              if(p >= gameInfo.holdingsSize) p = 0;
9445              if(--board[p][BOARD_WIDTH-2] <= 0)
9446                   board[p][BOARD_WIDTH-1] = EmptySquare;
9447              if((int)board[p][BOARD_WIDTH-2] < 0)
9448                         board[p][BOARD_WIDTH-2] = 0;
9449         } else {                  /* black drop */
9450              p -= (int)BlackPawn;
9451                  p = PieceToNumber((ChessSquare)p);
9452              if(p >= gameInfo.holdingsSize) p = 0;
9453              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9454                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9455              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9456                         board[BOARD_HEIGHT-1-p][1] = 0;
9457         }
9458       }
9459       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9460           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9461         /* [HGM] holdings: Add to holdings, if holdings exist */
9462         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9463                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9464                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9465         }
9466         p = (int) captured;
9467         if (p >= (int) BlackPawn) {
9468           p -= (int)BlackPawn;
9469           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9470                   /* in Shogi restore piece to its original  first */
9471                   captured = (ChessSquare) (DEMOTED captured);
9472                   p = DEMOTED p;
9473           }
9474           p = PieceToNumber((ChessSquare)p);
9475           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9476           board[p][BOARD_WIDTH-2]++;
9477           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9478         } else {
9479           p -= (int)WhitePawn;
9480           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9481                   captured = (ChessSquare) (DEMOTED captured);
9482                   p = DEMOTED p;
9483           }
9484           p = PieceToNumber((ChessSquare)p);
9485           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9486           board[BOARD_HEIGHT-1-p][1]++;
9487           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9488         }
9489       }
9490     } else if (gameInfo.variant == VariantAtomic) {
9491       if (captured != EmptySquare) {
9492         int y, x;
9493         for (y = toY-1; y <= toY+1; y++) {
9494           for (x = toX-1; x <= toX+1; x++) {
9495             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9496                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9497               board[y][x] = EmptySquare;
9498             }
9499           }
9500         }
9501         board[toY][toX] = EmptySquare;
9502       }
9503     }
9504     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9505         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9506     } else
9507     if(promoChar == '+') {
9508         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9509         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9510     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9511         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9512         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9513            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9514         board[toY][toX] = newPiece;
9515     }
9516     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9517                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9518         // [HGM] superchess: take promotion piece out of holdings
9519         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9520         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9521             if(!--board[k][BOARD_WIDTH-2])
9522                 board[k][BOARD_WIDTH-1] = EmptySquare;
9523         } else {
9524             if(!--board[BOARD_HEIGHT-1-k][1])
9525                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9526         }
9527     }
9528
9529 }
9530
9531 /* Updates forwardMostMove */
9532 void
9533 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9534 {
9535 //    forwardMostMove++; // [HGM] bare: moved downstream
9536
9537     (void) CoordsToAlgebraic(boards[forwardMostMove],
9538                              PosFlags(forwardMostMove),
9539                              fromY, fromX, toY, toX, promoChar,
9540                              parseList[forwardMostMove]);
9541
9542     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9543         int timeLeft; static int lastLoadFlag=0; int king, piece;
9544         piece = boards[forwardMostMove][fromY][fromX];
9545         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9546         if(gameInfo.variant == VariantKnightmate)
9547             king += (int) WhiteUnicorn - (int) WhiteKing;
9548         if(forwardMostMove == 0) {
9549             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9550                 fprintf(serverMoves, "%s;", UserName());
9551             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9552                 fprintf(serverMoves, "%s;", second.tidy);
9553             fprintf(serverMoves, "%s;", first.tidy);
9554             if(gameMode == MachinePlaysWhite)
9555                 fprintf(serverMoves, "%s;", UserName());
9556             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9557                 fprintf(serverMoves, "%s;", second.tidy);
9558         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9559         lastLoadFlag = loadFlag;
9560         // print base move
9561         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9562         // print castling suffix
9563         if( toY == fromY && piece == king ) {
9564             if(toX-fromX > 1)
9565                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9566             if(fromX-toX >1)
9567                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9568         }
9569         // e.p. suffix
9570         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9571              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9572              boards[forwardMostMove][toY][toX] == EmptySquare
9573              && fromX != toX && fromY != toY)
9574                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9575         // promotion suffix
9576         if(promoChar != NULLCHAR)
9577                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9578         if(!loadFlag) {
9579                 char buf[MOVE_LEN*2], *p; int len;
9580             fprintf(serverMoves, "/%d/%d",
9581                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9582             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9583             else                      timeLeft = blackTimeRemaining/1000;
9584             fprintf(serverMoves, "/%d", timeLeft);
9585                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9586                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9587                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9588             fprintf(serverMoves, "/%s", buf);
9589         }
9590         fflush(serverMoves);
9591     }
9592
9593     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9594         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9595       return;
9596     }
9597     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9598     if (commentList[forwardMostMove+1] != NULL) {
9599         free(commentList[forwardMostMove+1]);
9600         commentList[forwardMostMove+1] = NULL;
9601     }
9602     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9603     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9604     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9605     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9606     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9607     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9608     adjustedClock = FALSE;
9609     gameInfo.result = GameUnfinished;
9610     if (gameInfo.resultDetails != NULL) {
9611         free(gameInfo.resultDetails);
9612         gameInfo.resultDetails = NULL;
9613     }
9614     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9615                               moveList[forwardMostMove - 1]);
9616     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9617       case MT_NONE:
9618       case MT_STALEMATE:
9619       default:
9620         break;
9621       case MT_CHECK:
9622         if(gameInfo.variant != VariantShogi)
9623             strcat(parseList[forwardMostMove - 1], "+");
9624         break;
9625       case MT_CHECKMATE:
9626       case MT_STAINMATE:
9627         strcat(parseList[forwardMostMove - 1], "#");
9628         break;
9629     }
9630
9631 }
9632
9633 /* Updates currentMove if not pausing */
9634 void
9635 ShowMove (int fromX, int fromY, int toX, int toY)
9636 {
9637     int instant = (gameMode == PlayFromGameFile) ?
9638         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9639     if(appData.noGUI) return;
9640     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9641         if (!instant) {
9642             if (forwardMostMove == currentMove + 1) {
9643                 AnimateMove(boards[forwardMostMove - 1],
9644                             fromX, fromY, toX, toY);
9645             }
9646             if (appData.highlightLastMove) {
9647                 SetHighlights(fromX, fromY, toX, toY);
9648             }
9649         }
9650         currentMove = forwardMostMove;
9651     }
9652
9653     if (instant) return;
9654
9655     DisplayMove(currentMove - 1);
9656     DrawPosition(FALSE, boards[currentMove]);
9657     DisplayBothClocks();
9658     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9659 }
9660
9661 void
9662 SendEgtPath (ChessProgramState *cps)
9663 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9664         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9665
9666         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9667
9668         while(*p) {
9669             char c, *q = name+1, *r, *s;
9670
9671             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9672             while(*p && *p != ',') *q++ = *p++;
9673             *q++ = ':'; *q = 0;
9674             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9675                 strcmp(name, ",nalimov:") == 0 ) {
9676                 // take nalimov path from the menu-changeable option first, if it is defined
9677               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9678                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9679             } else
9680             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9681                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9682                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9683                 s = r = StrStr(s, ":") + 1; // beginning of path info
9684                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9685                 c = *r; *r = 0;             // temporarily null-terminate path info
9686                     *--q = 0;               // strip of trailig ':' from name
9687                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9688                 *r = c;
9689                 SendToProgram(buf,cps);     // send egtbpath command for this format
9690             }
9691             if(*p == ',') p++; // read away comma to position for next format name
9692         }
9693 }
9694
9695 void
9696 InitChessProgram (ChessProgramState *cps, int setup)
9697 /* setup needed to setup FRC opening position */
9698 {
9699     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9700     if (appData.noChessProgram) return;
9701     hintRequested = FALSE;
9702     bookRequested = FALSE;
9703
9704     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9705     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9706     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9707     if(cps->memSize) { /* [HGM] memory */
9708       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9709         SendToProgram(buf, cps);
9710     }
9711     SendEgtPath(cps); /* [HGM] EGT */
9712     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9713       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9714         SendToProgram(buf, cps);
9715     }
9716
9717     SendToProgram(cps->initString, cps);
9718     if (gameInfo.variant != VariantNormal &&
9719         gameInfo.variant != VariantLoadable
9720         /* [HGM] also send variant if board size non-standard */
9721         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9722                                             ) {
9723       char *v = VariantName(gameInfo.variant);
9724       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9725         /* [HGM] in protocol 1 we have to assume all variants valid */
9726         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9727         DisplayFatalError(buf, 0, 1);
9728         return;
9729       }
9730
9731       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9732       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9733       if( gameInfo.variant == VariantXiangqi )
9734            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9735       if( gameInfo.variant == VariantShogi )
9736            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9737       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9738            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9739       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9740           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9741            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9742       if( gameInfo.variant == VariantCourier )
9743            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9744       if( gameInfo.variant == VariantSuper )
9745            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9746       if( gameInfo.variant == VariantGreat )
9747            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9748       if( gameInfo.variant == VariantSChess )
9749            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9750       if( gameInfo.variant == VariantGrand )
9751            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9752
9753       if(overruled) {
9754         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9755                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9756            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9757            if(StrStr(cps->variants, b) == NULL) {
9758                // specific sized variant not known, check if general sizing allowed
9759                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9760                    if(StrStr(cps->variants, "boardsize") == NULL) {
9761                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9762                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9763                        DisplayFatalError(buf, 0, 1);
9764                        return;
9765                    }
9766                    /* [HGM] here we really should compare with the maximum supported board size */
9767                }
9768            }
9769       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9770       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9771       SendToProgram(buf, cps);
9772     }
9773     currentlyInitializedVariant = gameInfo.variant;
9774
9775     /* [HGM] send opening position in FRC to first engine */
9776     if(setup) {
9777           SendToProgram("force\n", cps);
9778           SendBoard(cps, 0);
9779           /* engine is now in force mode! Set flag to wake it up after first move. */
9780           setboardSpoiledMachineBlack = 1;
9781     }
9782
9783     if (cps->sendICS) {
9784       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9785       SendToProgram(buf, cps);
9786     }
9787     cps->maybeThinking = FALSE;
9788     cps->offeredDraw = 0;
9789     if (!appData.icsActive) {
9790         SendTimeControl(cps, movesPerSession, timeControl,
9791                         timeIncrement, appData.searchDepth,
9792                         searchTime);
9793     }
9794     if (appData.showThinking
9795         // [HGM] thinking: four options require thinking output to be sent
9796         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9797                                 ) {
9798         SendToProgram("post\n", cps);
9799     }
9800     SendToProgram("hard\n", cps);
9801     if (!appData.ponderNextMove) {
9802         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9803            it without being sure what state we are in first.  "hard"
9804            is not a toggle, so that one is OK.
9805          */
9806         SendToProgram("easy\n", cps);
9807     }
9808     if (cps->usePing) {
9809       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9810       SendToProgram(buf, cps);
9811     }
9812     cps->initDone = TRUE;
9813     ClearEngineOutputPane(cps == &second);
9814 }
9815
9816
9817 void
9818 StartChessProgram (ChessProgramState *cps)
9819 {
9820     char buf[MSG_SIZ];
9821     int err;
9822
9823     if (appData.noChessProgram) return;
9824     cps->initDone = FALSE;
9825
9826     if (strcmp(cps->host, "localhost") == 0) {
9827         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9828     } else if (*appData.remoteShell == NULLCHAR) {
9829         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9830     } else {
9831         if (*appData.remoteUser == NULLCHAR) {
9832           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9833                     cps->program);
9834         } else {
9835           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9836                     cps->host, appData.remoteUser, cps->program);
9837         }
9838         err = StartChildProcess(buf, "", &cps->pr);
9839     }
9840
9841     if (err != 0) {
9842       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9843         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9844         if(cps != &first) return;
9845         appData.noChessProgram = TRUE;
9846         ThawUI();
9847         SetNCPMode();
9848 //      DisplayFatalError(buf, err, 1);
9849 //      cps->pr = NoProc;
9850 //      cps->isr = NULL;
9851         return;
9852     }
9853
9854     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9855     if (cps->protocolVersion > 1) {
9856       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9857       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9858       cps->comboCnt = 0;  //                and values of combo boxes
9859       SendToProgram(buf, cps);
9860     } else {
9861       SendToProgram("xboard\n", cps);
9862     }
9863 }
9864
9865 void
9866 TwoMachinesEventIfReady P((void))
9867 {
9868   static int curMess = 0;
9869   if (first.lastPing != first.lastPong) {
9870     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9871     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9872     return;
9873   }
9874   if (second.lastPing != second.lastPong) {
9875     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9876     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9877     return;
9878   }
9879   DisplayMessage("", ""); curMess = 0;
9880   ThawUI();
9881   TwoMachinesEvent();
9882 }
9883
9884 char *
9885 MakeName (char *template)
9886 {
9887     time_t clock;
9888     struct tm *tm;
9889     static char buf[MSG_SIZ];
9890     char *p = buf;
9891     int i;
9892
9893     clock = time((time_t *)NULL);
9894     tm = localtime(&clock);
9895
9896     while(*p++ = *template++) if(p[-1] == '%') {
9897         switch(*template++) {
9898           case 0:   *p = 0; return buf;
9899           case 'Y': i = tm->tm_year+1900; break;
9900           case 'y': i = tm->tm_year-100; break;
9901           case 'M': i = tm->tm_mon+1; break;
9902           case 'd': i = tm->tm_mday; break;
9903           case 'h': i = tm->tm_hour; break;
9904           case 'm': i = tm->tm_min; break;
9905           case 's': i = tm->tm_sec; break;
9906           default:  i = 0;
9907         }
9908         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9909     }
9910     return buf;
9911 }
9912
9913 int
9914 CountPlayers (char *p)
9915 {
9916     int n = 0;
9917     while(p = strchr(p, '\n')) p++, n++; // count participants
9918     return n;
9919 }
9920
9921 FILE *
9922 WriteTourneyFile (char *results, FILE *f)
9923 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9924     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9925     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9926         // create a file with tournament description
9927         fprintf(f, "-participants {%s}\n", appData.participants);
9928         fprintf(f, "-seedBase %d\n", appData.seedBase);
9929         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9930         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9931         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9932         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9933         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9934         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9935         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9936         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9937         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9938         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9939         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9940         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9941         if(searchTime > 0)
9942                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9943         else {
9944                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9945                 fprintf(f, "-tc %s\n", appData.timeControl);
9946                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9947         }
9948         fprintf(f, "-results \"%s\"\n", results);
9949     }
9950     return f;
9951 }
9952
9953 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9954
9955 void
9956 Substitute (char *participants, int expunge)
9957 {
9958     int i, changed, changes=0, nPlayers=0;
9959     char *p, *q, *r, buf[MSG_SIZ];
9960     if(participants == NULL) return;
9961     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9962     r = p = participants; q = appData.participants;
9963     while(*p && *p == *q) {
9964         if(*p == '\n') r = p+1, nPlayers++;
9965         p++; q++;
9966     }
9967     if(*p) { // difference
9968         while(*p && *p++ != '\n');
9969         while(*q && *q++ != '\n');
9970       changed = nPlayers;
9971         changes = 1 + (strcmp(p, q) != 0);
9972     }
9973     if(changes == 1) { // a single engine mnemonic was changed
9974         q = r; while(*q) nPlayers += (*q++ == '\n');
9975         p = buf; while(*r && (*p = *r++) != '\n') p++;
9976         *p = NULLCHAR;
9977         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9978         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9979         if(mnemonic[i]) { // The substitute is valid
9980             FILE *f;
9981             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9982                 flock(fileno(f), LOCK_EX);
9983                 ParseArgsFromFile(f);
9984                 fseek(f, 0, SEEK_SET);
9985                 FREE(appData.participants); appData.participants = participants;
9986                 if(expunge) { // erase results of replaced engine
9987                     int len = strlen(appData.results), w, b, dummy;
9988                     for(i=0; i<len; i++) {
9989                         Pairing(i, nPlayers, &w, &b, &dummy);
9990                         if((w == changed || b == changed) && appData.results[i] == '*') {
9991                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9992                             fclose(f);
9993                             return;
9994                         }
9995                     }
9996                     for(i=0; i<len; i++) {
9997                         Pairing(i, nPlayers, &w, &b, &dummy);
9998                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9999                     }
10000                 }
10001                 WriteTourneyFile(appData.results, f);
10002                 fclose(f); // release lock
10003                 return;
10004             }
10005         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10006     }
10007     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10008     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10009     free(participants);
10010     return;
10011 }
10012
10013 int
10014 CreateTourney (char *name)
10015 {
10016         FILE *f;
10017         if(matchMode && strcmp(name, appData.tourneyFile)) {
10018              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10019         }
10020         if(name[0] == NULLCHAR) {
10021             if(appData.participants[0])
10022                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10023             return 0;
10024         }
10025         f = fopen(name, "r");
10026         if(f) { // file exists
10027             ASSIGN(appData.tourneyFile, name);
10028             ParseArgsFromFile(f); // parse it
10029         } else {
10030             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10031             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10032                 DisplayError(_("Not enough participants"), 0);
10033                 return 0;
10034             }
10035             ASSIGN(appData.tourneyFile, name);
10036             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10037             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10038         }
10039         fclose(f);
10040         appData.noChessProgram = FALSE;
10041         appData.clockMode = TRUE;
10042         SetGNUMode();
10043         return 1;
10044 }
10045
10046 int
10047 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10048 {
10049     char buf[MSG_SIZ], *p, *q;
10050     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10051     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10052     skip = !all && group[0]; // if group requested, we start in skip mode
10053     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10054         p = names; q = buf; header = 0;
10055         while(*p && *p != '\n') *q++ = *p++;
10056         *q = 0;
10057         if(*p == '\n') p++;
10058         if(buf[0] == '#') {
10059             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10060             depth++; // we must be entering a new group
10061             if(all) continue; // suppress printing group headers when complete list requested
10062             header = 1;
10063             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10064         }
10065         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10066         if(engineList[i]) free(engineList[i]);
10067         engineList[i] = strdup(buf);
10068         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10069         if(engineMnemonic[i]) free(engineMnemonic[i]);
10070         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10071             strcat(buf, " (");
10072             sscanf(q + 8, "%s", buf + strlen(buf));
10073             strcat(buf, ")");
10074         }
10075         engineMnemonic[i] = strdup(buf);
10076         i++;
10077     }
10078     engineList[i] = engineMnemonic[i] = NULL;
10079     return i;
10080 }
10081
10082 // following implemented as macro to avoid type limitations
10083 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10084
10085 void
10086 SwapEngines (int n)
10087 {   // swap settings for first engine and other engine (so far only some selected options)
10088     int h;
10089     char *p;
10090     if(n == 0) return;
10091     SWAP(directory, p)
10092     SWAP(chessProgram, p)
10093     SWAP(isUCI, h)
10094     SWAP(hasOwnBookUCI, h)
10095     SWAP(protocolVersion, h)
10096     SWAP(reuse, h)
10097     SWAP(scoreIsAbsolute, h)
10098     SWAP(timeOdds, h)
10099     SWAP(logo, p)
10100     SWAP(pgnName, p)
10101     SWAP(pvSAN, h)
10102     SWAP(engOptions, p)
10103     SWAP(engInitString, p)
10104     SWAP(computerString, p)
10105     SWAP(features, p)
10106     SWAP(fenOverride, p)
10107     SWAP(NPS, h)
10108     SWAP(accumulateTC, h)
10109     SWAP(host, p)
10110 }
10111
10112 int
10113 SetPlayer (int player, char *p)
10114 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10115     int i;
10116     char buf[MSG_SIZ], *engineName;
10117     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10118     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10119     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10120     if(mnemonic[i]) {
10121         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10122         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10123         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10124         ParseArgsFromString(buf);
10125     }
10126     free(engineName);
10127     return i;
10128 }
10129
10130 char *recentEngines;
10131
10132 void
10133 RecentEngineEvent (int nr)
10134 {
10135     int n;
10136 //    SwapEngines(1); // bump first to second
10137 //    ReplaceEngine(&second, 1); // and load it there
10138     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10139     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10140     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10141         ReplaceEngine(&first, 0);
10142         FloatToFront(&appData.recentEngineList, command[n]);
10143     }
10144 }
10145
10146 int
10147 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10148 {   // determine players from game number
10149     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10150
10151     if(appData.tourneyType == 0) {
10152         roundsPerCycle = (nPlayers - 1) | 1;
10153         pairingsPerRound = nPlayers / 2;
10154     } else if(appData.tourneyType > 0) {
10155         roundsPerCycle = nPlayers - appData.tourneyType;
10156         pairingsPerRound = appData.tourneyType;
10157     }
10158     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10159     gamesPerCycle = gamesPerRound * roundsPerCycle;
10160     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10161     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10162     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10163     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10164     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10165     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10166
10167     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10168     if(appData.roundSync) *syncInterval = gamesPerRound;
10169
10170     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10171
10172     if(appData.tourneyType == 0) {
10173         if(curPairing == (nPlayers-1)/2 ) {
10174             *whitePlayer = curRound;
10175             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10176         } else {
10177             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10178             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10179             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10180             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10181         }
10182     } else if(appData.tourneyType > 1) {
10183         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10184         *whitePlayer = curRound + appData.tourneyType;
10185     } else if(appData.tourneyType > 0) {
10186         *whitePlayer = curPairing;
10187         *blackPlayer = curRound + appData.tourneyType;
10188     }
10189
10190     // take care of white/black alternation per round. 
10191     // For cycles and games this is already taken care of by default, derived from matchGame!
10192     return curRound & 1;
10193 }
10194
10195 int
10196 NextTourneyGame (int nr, int *swapColors)
10197 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10198     char *p, *q;
10199     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10200     FILE *tf;
10201     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10202     tf = fopen(appData.tourneyFile, "r");
10203     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10204     ParseArgsFromFile(tf); fclose(tf);
10205     InitTimeControls(); // TC might be altered from tourney file
10206
10207     nPlayers = CountPlayers(appData.participants); // count participants
10208     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10209     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10210
10211     if(syncInterval) {
10212         p = q = appData.results;
10213         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10214         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10215             DisplayMessage(_("Waiting for other game(s)"),"");
10216             waitingForGame = TRUE;
10217             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10218             return 0;
10219         }
10220         waitingForGame = FALSE;
10221     }
10222
10223     if(appData.tourneyType < 0) {
10224         if(nr>=0 && !pairingReceived) {
10225             char buf[1<<16];
10226             if(pairing.pr == NoProc) {
10227                 if(!appData.pairingEngine[0]) {
10228                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10229                     return 0;
10230                 }
10231                 StartChessProgram(&pairing); // starts the pairing engine
10232             }
10233             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10234             SendToProgram(buf, &pairing);
10235             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10236             SendToProgram(buf, &pairing);
10237             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10238         }
10239         pairingReceived = 0;                              // ... so we continue here 
10240         *swapColors = 0;
10241         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10242         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10243         matchGame = 1; roundNr = nr / syncInterval + 1;
10244     }
10245
10246     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10247
10248     // redefine engines, engine dir, etc.
10249     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10250     if(first.pr == NoProc) {
10251       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10252       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10253     }
10254     if(second.pr == NoProc) {
10255       SwapEngines(1);
10256       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10257       SwapEngines(1);         // and make that valid for second engine by swapping
10258       InitEngine(&second, 1);
10259     }
10260     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10261     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10262     return 1;
10263 }
10264
10265 void
10266 NextMatchGame ()
10267 {   // performs game initialization that does not invoke engines, and then tries to start the game
10268     int res, firstWhite, swapColors = 0;
10269     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10270     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
10271         char buf[MSG_SIZ];
10272         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10273         if(strcmp(buf, currentDebugFile)) { // name has changed
10274             FILE *f = fopen(buf, "w");
10275             if(f) { // if opening the new file failed, just keep using the old one
10276                 ASSIGN(currentDebugFile, buf);
10277                 fclose(debugFP);
10278                 debugFP = f;
10279             }
10280             if(appData.serverFileName) {
10281                 if(serverFP) fclose(serverFP);
10282                 serverFP = fopen(appData.serverFileName, "w");
10283                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10284                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10285             }
10286         }
10287     }
10288     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10289     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10290     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10291     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10292     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10293     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10294     Reset(FALSE, first.pr != NoProc);
10295     res = LoadGameOrPosition(matchGame); // setup game
10296     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10297     if(!res) return; // abort when bad game/pos file
10298     TwoMachinesEvent();
10299 }
10300
10301 void
10302 UserAdjudicationEvent (int result)
10303 {
10304     ChessMove gameResult = GameIsDrawn;
10305
10306     if( result > 0 ) {
10307         gameResult = WhiteWins;
10308     }
10309     else if( result < 0 ) {
10310         gameResult = BlackWins;
10311     }
10312
10313     if( gameMode == TwoMachinesPlay ) {
10314         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10315     }
10316 }
10317
10318
10319 // [HGM] save: calculate checksum of game to make games easily identifiable
10320 int
10321 StringCheckSum (char *s)
10322 {
10323         int i = 0;
10324         if(s==NULL) return 0;
10325         while(*s) i = i*259 + *s++;
10326         return i;
10327 }
10328
10329 int
10330 GameCheckSum ()
10331 {
10332         int i, sum=0;
10333         for(i=backwardMostMove; i<forwardMostMove; i++) {
10334                 sum += pvInfoList[i].depth;
10335                 sum += StringCheckSum(parseList[i]);
10336                 sum += StringCheckSum(commentList[i]);
10337                 sum *= 261;
10338         }
10339         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10340         return sum + StringCheckSum(commentList[i]);
10341 } // end of save patch
10342
10343 void
10344 GameEnds (ChessMove result, char *resultDetails, int whosays)
10345 {
10346     GameMode nextGameMode;
10347     int isIcsGame;
10348     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10349
10350     if(endingGame) return; /* [HGM] crash: forbid recursion */
10351     endingGame = 1;
10352     if(twoBoards) { // [HGM] dual: switch back to one board
10353         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10354         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10355     }
10356     if (appData.debugMode) {
10357       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10358               result, resultDetails ? resultDetails : "(null)", whosays);
10359     }
10360
10361     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10362
10363     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10364         /* If we are playing on ICS, the server decides when the
10365            game is over, but the engine can offer to draw, claim
10366            a draw, or resign.
10367          */
10368 #if ZIPPY
10369         if (appData.zippyPlay && first.initDone) {
10370             if (result == GameIsDrawn) {
10371                 /* In case draw still needs to be claimed */
10372                 SendToICS(ics_prefix);
10373                 SendToICS("draw\n");
10374             } else if (StrCaseStr(resultDetails, "resign")) {
10375                 SendToICS(ics_prefix);
10376                 SendToICS("resign\n");
10377             }
10378         }
10379 #endif
10380         endingGame = 0; /* [HGM] crash */
10381         return;
10382     }
10383
10384     /* If we're loading the game from a file, stop */
10385     if (whosays == GE_FILE) {
10386       (void) StopLoadGameTimer();
10387       gameFileFP = NULL;
10388     }
10389
10390     /* Cancel draw offers */
10391     first.offeredDraw = second.offeredDraw = 0;
10392
10393     /* If this is an ICS game, only ICS can really say it's done;
10394        if not, anyone can. */
10395     isIcsGame = (gameMode == IcsPlayingWhite ||
10396                  gameMode == IcsPlayingBlack ||
10397                  gameMode == IcsObserving    ||
10398                  gameMode == IcsExamining);
10399
10400     if (!isIcsGame || whosays == GE_ICS) {
10401         /* OK -- not an ICS game, or ICS said it was done */
10402         StopClocks();
10403         if (!isIcsGame && !appData.noChessProgram)
10404           SetUserThinkingEnables();
10405
10406         /* [HGM] if a machine claims the game end we verify this claim */
10407         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10408             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10409                 char claimer;
10410                 ChessMove trueResult = (ChessMove) -1;
10411
10412                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10413                                             first.twoMachinesColor[0] :
10414                                             second.twoMachinesColor[0] ;
10415
10416                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10417                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10418                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10419                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10420                 } else
10421                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10422                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10423                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10424                 } else
10425                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10426                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10427                 }
10428
10429                 // now verify win claims, but not in drop games, as we don't understand those yet
10430                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10431                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10432                     (result == WhiteWins && claimer == 'w' ||
10433                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10434                       if (appData.debugMode) {
10435                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10436                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10437                       }
10438                       if(result != trueResult) {
10439                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10440                               result = claimer == 'w' ? BlackWins : WhiteWins;
10441                               resultDetails = buf;
10442                       }
10443                 } else
10444                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10445                     && (forwardMostMove <= backwardMostMove ||
10446                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10447                         (claimer=='b')==(forwardMostMove&1))
10448                                                                                   ) {
10449                       /* [HGM] verify: draws that were not flagged are false claims */
10450                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10451                       result = claimer == 'w' ? BlackWins : WhiteWins;
10452                       resultDetails = buf;
10453                 }
10454                 /* (Claiming a loss is accepted no questions asked!) */
10455             }
10456             /* [HGM] bare: don't allow bare King to win */
10457             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10458                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10459                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10460                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10461                && result != GameIsDrawn)
10462             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10463                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10464                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10465                         if(p >= 0 && p <= (int)WhiteKing) k++;
10466                 }
10467                 if (appData.debugMode) {
10468                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10469                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10470                 }
10471                 if(k <= 1) {
10472                         result = GameIsDrawn;
10473                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10474                         resultDetails = buf;
10475                 }
10476             }
10477         }
10478
10479
10480         if(serverMoves != NULL && !loadFlag) { char c = '=';
10481             if(result==WhiteWins) c = '+';
10482             if(result==BlackWins) c = '-';
10483             if(resultDetails != NULL)
10484                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10485         }
10486         if (resultDetails != NULL) {
10487             gameInfo.result = result;
10488             gameInfo.resultDetails = StrSave(resultDetails);
10489
10490             /* display last move only if game was not loaded from file */
10491             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10492                 DisplayMove(currentMove - 1);
10493
10494             if (forwardMostMove != 0) {
10495                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10496                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10497                                                                 ) {
10498                     if (*appData.saveGameFile != NULLCHAR) {
10499                         SaveGameToFile(appData.saveGameFile, TRUE);
10500                     } else if (appData.autoSaveGames) {
10501                         AutoSaveGame();
10502                     }
10503                     if (*appData.savePositionFile != NULLCHAR) {
10504                         SavePositionToFile(appData.savePositionFile);
10505                     }
10506                 }
10507             }
10508
10509             /* Tell program how game ended in case it is learning */
10510             /* [HGM] Moved this to after saving the PGN, just in case */
10511             /* engine died and we got here through time loss. In that */
10512             /* case we will get a fatal error writing the pipe, which */
10513             /* would otherwise lose us the PGN.                       */
10514             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10515             /* output during GameEnds should never be fatal anymore   */
10516             if (gameMode == MachinePlaysWhite ||
10517                 gameMode == MachinePlaysBlack ||
10518                 gameMode == TwoMachinesPlay ||
10519                 gameMode == IcsPlayingWhite ||
10520                 gameMode == IcsPlayingBlack ||
10521                 gameMode == BeginningOfGame) {
10522                 char buf[MSG_SIZ];
10523                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10524                         resultDetails);
10525                 if (first.pr != NoProc) {
10526                     SendToProgram(buf, &first);
10527                 }
10528                 if (second.pr != NoProc &&
10529                     gameMode == TwoMachinesPlay) {
10530                     SendToProgram(buf, &second);
10531                 }
10532             }
10533         }
10534
10535         if (appData.icsActive) {
10536             if (appData.quietPlay &&
10537                 (gameMode == IcsPlayingWhite ||
10538                  gameMode == IcsPlayingBlack)) {
10539                 SendToICS(ics_prefix);
10540                 SendToICS("set shout 1\n");
10541             }
10542             nextGameMode = IcsIdle;
10543             ics_user_moved = FALSE;
10544             /* clean up premove.  It's ugly when the game has ended and the
10545              * premove highlights are still on the board.
10546              */
10547             if (gotPremove) {
10548               gotPremove = FALSE;
10549               ClearPremoveHighlights();
10550               DrawPosition(FALSE, boards[currentMove]);
10551             }
10552             if (whosays == GE_ICS) {
10553                 switch (result) {
10554                 case WhiteWins:
10555                     if (gameMode == IcsPlayingWhite)
10556                         PlayIcsWinSound();
10557                     else if(gameMode == IcsPlayingBlack)
10558                         PlayIcsLossSound();
10559                     break;
10560                 case BlackWins:
10561                     if (gameMode == IcsPlayingBlack)
10562                         PlayIcsWinSound();
10563                     else if(gameMode == IcsPlayingWhite)
10564                         PlayIcsLossSound();
10565                     break;
10566                 case GameIsDrawn:
10567                     PlayIcsDrawSound();
10568                     break;
10569                 default:
10570                     PlayIcsUnfinishedSound();
10571                 }
10572             }
10573         } else if (gameMode == EditGame ||
10574                    gameMode == PlayFromGameFile ||
10575                    gameMode == AnalyzeMode ||
10576                    gameMode == AnalyzeFile) {
10577             nextGameMode = gameMode;
10578         } else {
10579             nextGameMode = EndOfGame;
10580         }
10581         pausing = FALSE;
10582         ModeHighlight();
10583     } else {
10584         nextGameMode = gameMode;
10585     }
10586
10587     if (appData.noChessProgram) {
10588         gameMode = nextGameMode;
10589         ModeHighlight();
10590         endingGame = 0; /* [HGM] crash */
10591         return;
10592     }
10593
10594     if (first.reuse) {
10595         /* Put first chess program into idle state */
10596         if (first.pr != NoProc &&
10597             (gameMode == MachinePlaysWhite ||
10598              gameMode == MachinePlaysBlack ||
10599              gameMode == TwoMachinesPlay ||
10600              gameMode == IcsPlayingWhite ||
10601              gameMode == IcsPlayingBlack ||
10602              gameMode == BeginningOfGame)) {
10603             SendToProgram("force\n", &first);
10604             if (first.usePing) {
10605               char buf[MSG_SIZ];
10606               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10607               SendToProgram(buf, &first);
10608             }
10609         }
10610     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10611         /* Kill off first chess program */
10612         if (first.isr != NULL)
10613           RemoveInputSource(first.isr);
10614         first.isr = NULL;
10615
10616         if (first.pr != NoProc) {
10617             ExitAnalyzeMode();
10618             DoSleep( appData.delayBeforeQuit );
10619             SendToProgram("quit\n", &first);
10620             DoSleep( appData.delayAfterQuit );
10621             DestroyChildProcess(first.pr, first.useSigterm);
10622         }
10623         first.pr = NoProc;
10624     }
10625     if (second.reuse) {
10626         /* Put second chess program into idle state */
10627         if (second.pr != NoProc &&
10628             gameMode == TwoMachinesPlay) {
10629             SendToProgram("force\n", &second);
10630             if (second.usePing) {
10631               char buf[MSG_SIZ];
10632               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10633               SendToProgram(buf, &second);
10634             }
10635         }
10636     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10637         /* Kill off second chess program */
10638         if (second.isr != NULL)
10639           RemoveInputSource(second.isr);
10640         second.isr = NULL;
10641
10642         if (second.pr != NoProc) {
10643             DoSleep( appData.delayBeforeQuit );
10644             SendToProgram("quit\n", &second);
10645             DoSleep( appData.delayAfterQuit );
10646             DestroyChildProcess(second.pr, second.useSigterm);
10647         }
10648         second.pr = NoProc;
10649     }
10650
10651     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10652         char resChar = '=';
10653         switch (result) {
10654         case WhiteWins:
10655           resChar = '+';
10656           if (first.twoMachinesColor[0] == 'w') {
10657             first.matchWins++;
10658           } else {
10659             second.matchWins++;
10660           }
10661           break;
10662         case BlackWins:
10663           resChar = '-';
10664           if (first.twoMachinesColor[0] == 'b') {
10665             first.matchWins++;
10666           } else {
10667             second.matchWins++;
10668           }
10669           break;
10670         case GameUnfinished:
10671           resChar = ' ';
10672         default:
10673           break;
10674         }
10675
10676         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10677         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10678             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10679             ReserveGame(nextGame, resChar); // sets nextGame
10680             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10681             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10682         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10683
10684         if (nextGame <= appData.matchGames && !abortMatch) {
10685             gameMode = nextGameMode;
10686             matchGame = nextGame; // this will be overruled in tourney mode!
10687             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10688             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10689             endingGame = 0; /* [HGM] crash */
10690             return;
10691         } else {
10692             gameMode = nextGameMode;
10693             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10694                      first.tidy, second.tidy,
10695                      first.matchWins, second.matchWins,
10696                      appData.matchGames - (first.matchWins + second.matchWins));
10697             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10698             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10699             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10700             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10701                 first.twoMachinesColor = "black\n";
10702                 second.twoMachinesColor = "white\n";
10703             } else {
10704                 first.twoMachinesColor = "white\n";
10705                 second.twoMachinesColor = "black\n";
10706             }
10707         }
10708     }
10709     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10710         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10711       ExitAnalyzeMode();
10712     gameMode = nextGameMode;
10713     ModeHighlight();
10714     endingGame = 0;  /* [HGM] crash */
10715     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10716         if(matchMode == TRUE) { // match through command line: exit with or without popup
10717             if(ranking) {
10718                 ToNrEvent(forwardMostMove);
10719                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10720                 else ExitEvent(0);
10721             } else DisplayFatalError(buf, 0, 0);
10722         } else { // match through menu; just stop, with or without popup
10723             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10724             ModeHighlight();
10725             if(ranking){
10726                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10727             } else DisplayNote(buf);
10728       }
10729       if(ranking) free(ranking);
10730     }
10731 }
10732
10733 /* Assumes program was just initialized (initString sent).
10734    Leaves program in force mode. */
10735 void
10736 FeedMovesToProgram (ChessProgramState *cps, int upto)
10737 {
10738     int i;
10739
10740     if (appData.debugMode)
10741       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10742               startedFromSetupPosition ? "position and " : "",
10743               backwardMostMove, upto, cps->which);
10744     if(currentlyInitializedVariant != gameInfo.variant) {
10745       char buf[MSG_SIZ];
10746         // [HGM] variantswitch: make engine aware of new variant
10747         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10748                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10749         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10750         SendToProgram(buf, cps);
10751         currentlyInitializedVariant = gameInfo.variant;
10752     }
10753     SendToProgram("force\n", cps);
10754     if (startedFromSetupPosition) {
10755         SendBoard(cps, backwardMostMove);
10756     if (appData.debugMode) {
10757         fprintf(debugFP, "feedMoves\n");
10758     }
10759     }
10760     for (i = backwardMostMove; i < upto; i++) {
10761         SendMoveToProgram(i, cps);
10762     }
10763 }
10764
10765
10766 int
10767 ResurrectChessProgram ()
10768 {
10769      /* The chess program may have exited.
10770         If so, restart it and feed it all the moves made so far. */
10771     static int doInit = 0;
10772
10773     if (appData.noChessProgram) return 1;
10774
10775     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10776         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10777         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10778         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10779     } else {
10780         if (first.pr != NoProc) return 1;
10781         StartChessProgram(&first);
10782     }
10783     InitChessProgram(&first, FALSE);
10784     FeedMovesToProgram(&first, currentMove);
10785
10786     if (!first.sendTime) {
10787         /* can't tell gnuchess what its clock should read,
10788            so we bow to its notion. */
10789         ResetClocks();
10790         timeRemaining[0][currentMove] = whiteTimeRemaining;
10791         timeRemaining[1][currentMove] = blackTimeRemaining;
10792     }
10793
10794     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10795                 appData.icsEngineAnalyze) && first.analysisSupport) {
10796       SendToProgram("analyze\n", &first);
10797       first.analyzing = TRUE;
10798     }
10799     return 1;
10800 }
10801
10802 /*
10803  * Button procedures
10804  */
10805 void
10806 Reset (int redraw, int init)
10807 {
10808     int i;
10809
10810     if (appData.debugMode) {
10811         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10812                 redraw, init, gameMode);
10813     }
10814     CleanupTail(); // [HGM] vari: delete any stored variations
10815     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10816     pausing = pauseExamInvalid = FALSE;
10817     startedFromSetupPosition = blackPlaysFirst = FALSE;
10818     firstMove = TRUE;
10819     whiteFlag = blackFlag = FALSE;
10820     userOfferedDraw = FALSE;
10821     hintRequested = bookRequested = FALSE;
10822     first.maybeThinking = FALSE;
10823     second.maybeThinking = FALSE;
10824     first.bookSuspend = FALSE; // [HGM] book
10825     second.bookSuspend = FALSE;
10826     thinkOutput[0] = NULLCHAR;
10827     lastHint[0] = NULLCHAR;
10828     ClearGameInfo(&gameInfo);
10829     gameInfo.variant = StringToVariant(appData.variant);
10830     ics_user_moved = ics_clock_paused = FALSE;
10831     ics_getting_history = H_FALSE;
10832     ics_gamenum = -1;
10833     white_holding[0] = black_holding[0] = NULLCHAR;
10834     ClearProgramStats();
10835     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10836
10837     ResetFrontEnd();
10838     ClearHighlights();
10839     flipView = appData.flipView;
10840     ClearPremoveHighlights();
10841     gotPremove = FALSE;
10842     alarmSounded = FALSE;
10843
10844     GameEnds(EndOfFile, NULL, GE_PLAYER);
10845     if(appData.serverMovesName != NULL) {
10846         /* [HGM] prepare to make moves file for broadcasting */
10847         clock_t t = clock();
10848         if(serverMoves != NULL) fclose(serverMoves);
10849         serverMoves = fopen(appData.serverMovesName, "r");
10850         if(serverMoves != NULL) {
10851             fclose(serverMoves);
10852             /* delay 15 sec before overwriting, so all clients can see end */
10853             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10854         }
10855         serverMoves = fopen(appData.serverMovesName, "w");
10856     }
10857
10858     ExitAnalyzeMode();
10859     gameMode = BeginningOfGame;
10860     ModeHighlight();
10861     if(appData.icsActive) gameInfo.variant = VariantNormal;
10862     currentMove = forwardMostMove = backwardMostMove = 0;
10863     MarkTargetSquares(1);
10864     InitPosition(redraw);
10865     for (i = 0; i < MAX_MOVES; i++) {
10866         if (commentList[i] != NULL) {
10867             free(commentList[i]);
10868             commentList[i] = NULL;
10869         }
10870     }
10871     ResetClocks();
10872     timeRemaining[0][0] = whiteTimeRemaining;
10873     timeRemaining[1][0] = blackTimeRemaining;
10874
10875     if (first.pr == NoProc) {
10876         StartChessProgram(&first);
10877     }
10878     if (init) {
10879             InitChessProgram(&first, startedFromSetupPosition);
10880     }
10881     DisplayTitle("");
10882     DisplayMessage("", "");
10883     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10884     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10885     ClearMap();        // [HGM] exclude: invalidate map
10886 }
10887
10888 void
10889 AutoPlayGameLoop ()
10890 {
10891     for (;;) {
10892         if (!AutoPlayOneMove())
10893           return;
10894         if (matchMode || appData.timeDelay == 0)
10895           continue;
10896         if (appData.timeDelay < 0)
10897           return;
10898         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10899         break;
10900     }
10901 }
10902
10903
10904 int
10905 AutoPlayOneMove ()
10906 {
10907     int fromX, fromY, toX, toY;
10908
10909     if (appData.debugMode) {
10910       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10911     }
10912
10913     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10914       return FALSE;
10915
10916     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10917       pvInfoList[currentMove].depth = programStats.depth;
10918       pvInfoList[currentMove].score = programStats.score;
10919       pvInfoList[currentMove].time  = 0;
10920       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10921     }
10922
10923     if (currentMove >= forwardMostMove) {
10924       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10925 //      gameMode = EndOfGame;
10926 //      ModeHighlight();
10927
10928       /* [AS] Clear current move marker at the end of a game */
10929       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10930
10931       return FALSE;
10932     }
10933
10934     toX = moveList[currentMove][2] - AAA;
10935     toY = moveList[currentMove][3] - ONE;
10936
10937     if (moveList[currentMove][1] == '@') {
10938         if (appData.highlightLastMove) {
10939             SetHighlights(-1, -1, toX, toY);
10940         }
10941     } else {
10942         fromX = moveList[currentMove][0] - AAA;
10943         fromY = moveList[currentMove][1] - ONE;
10944
10945         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10946
10947         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10948
10949         if (appData.highlightLastMove) {
10950             SetHighlights(fromX, fromY, toX, toY);
10951         }
10952     }
10953     DisplayMove(currentMove);
10954     SendMoveToProgram(currentMove++, &first);
10955     DisplayBothClocks();
10956     DrawPosition(FALSE, boards[currentMove]);
10957     // [HGM] PV info: always display, routine tests if empty
10958     DisplayComment(currentMove - 1, commentList[currentMove]);
10959     return TRUE;
10960 }
10961
10962
10963 int
10964 LoadGameOneMove (ChessMove readAhead)
10965 {
10966     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10967     char promoChar = NULLCHAR;
10968     ChessMove moveType;
10969     char move[MSG_SIZ];
10970     char *p, *q;
10971
10972     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10973         gameMode != AnalyzeMode && gameMode != Training) {
10974         gameFileFP = NULL;
10975         return FALSE;
10976     }
10977
10978     yyboardindex = forwardMostMove;
10979     if (readAhead != EndOfFile) {
10980       moveType = readAhead;
10981     } else {
10982       if (gameFileFP == NULL)
10983           return FALSE;
10984       moveType = (ChessMove) Myylex();
10985     }
10986
10987     done = FALSE;
10988     switch (moveType) {
10989       case Comment:
10990         if (appData.debugMode)
10991           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10992         p = yy_text;
10993
10994         /* append the comment but don't display it */
10995         AppendComment(currentMove, p, FALSE);
10996         return TRUE;
10997
10998       case WhiteCapturesEnPassant:
10999       case BlackCapturesEnPassant:
11000       case WhitePromotion:
11001       case BlackPromotion:
11002       case WhiteNonPromotion:
11003       case BlackNonPromotion:
11004       case NormalMove:
11005       case WhiteKingSideCastle:
11006       case WhiteQueenSideCastle:
11007       case BlackKingSideCastle:
11008       case BlackQueenSideCastle:
11009       case WhiteKingSideCastleWild:
11010       case WhiteQueenSideCastleWild:
11011       case BlackKingSideCastleWild:
11012       case BlackQueenSideCastleWild:
11013       /* PUSH Fabien */
11014       case WhiteHSideCastleFR:
11015       case WhiteASideCastleFR:
11016       case BlackHSideCastleFR:
11017       case BlackASideCastleFR:
11018       /* POP Fabien */
11019         if (appData.debugMode)
11020           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11021         fromX = currentMoveString[0] - AAA;
11022         fromY = currentMoveString[1] - ONE;
11023         toX = currentMoveString[2] - AAA;
11024         toY = currentMoveString[3] - ONE;
11025         promoChar = currentMoveString[4];
11026         break;
11027
11028       case WhiteDrop:
11029       case BlackDrop:
11030         if (appData.debugMode)
11031           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11032         fromX = moveType == WhiteDrop ?
11033           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11034         (int) CharToPiece(ToLower(currentMoveString[0]));
11035         fromY = DROP_RANK;
11036         toX = currentMoveString[2] - AAA;
11037         toY = currentMoveString[3] - ONE;
11038         break;
11039
11040       case WhiteWins:
11041       case BlackWins:
11042       case GameIsDrawn:
11043       case GameUnfinished:
11044         if (appData.debugMode)
11045           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11046         p = strchr(yy_text, '{');
11047         if (p == NULL) p = strchr(yy_text, '(');
11048         if (p == NULL) {
11049             p = yy_text;
11050             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11051         } else {
11052             q = strchr(p, *p == '{' ? '}' : ')');
11053             if (q != NULL) *q = NULLCHAR;
11054             p++;
11055         }
11056         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11057         GameEnds(moveType, p, GE_FILE);
11058         done = TRUE;
11059         if (cmailMsgLoaded) {
11060             ClearHighlights();
11061             flipView = WhiteOnMove(currentMove);
11062             if (moveType == GameUnfinished) flipView = !flipView;
11063             if (appData.debugMode)
11064               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11065         }
11066         break;
11067
11068       case EndOfFile:
11069         if (appData.debugMode)
11070           fprintf(debugFP, "Parser hit end of file\n");
11071         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11072           case MT_NONE:
11073           case MT_CHECK:
11074             break;
11075           case MT_CHECKMATE:
11076           case MT_STAINMATE:
11077             if (WhiteOnMove(currentMove)) {
11078                 GameEnds(BlackWins, "Black mates", GE_FILE);
11079             } else {
11080                 GameEnds(WhiteWins, "White mates", GE_FILE);
11081             }
11082             break;
11083           case MT_STALEMATE:
11084             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11085             break;
11086         }
11087         done = TRUE;
11088         break;
11089
11090       case MoveNumberOne:
11091         if (lastLoadGameStart == GNUChessGame) {
11092             /* GNUChessGames have numbers, but they aren't move numbers */
11093             if (appData.debugMode)
11094               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11095                       yy_text, (int) moveType);
11096             return LoadGameOneMove(EndOfFile); /* tail recursion */
11097         }
11098         /* else fall thru */
11099
11100       case XBoardGame:
11101       case GNUChessGame:
11102       case PGNTag:
11103         /* Reached start of next game in file */
11104         if (appData.debugMode)
11105           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11106         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11107           case MT_NONE:
11108           case MT_CHECK:
11109             break;
11110           case MT_CHECKMATE:
11111           case MT_STAINMATE:
11112             if (WhiteOnMove(currentMove)) {
11113                 GameEnds(BlackWins, "Black mates", GE_FILE);
11114             } else {
11115                 GameEnds(WhiteWins, "White mates", GE_FILE);
11116             }
11117             break;
11118           case MT_STALEMATE:
11119             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11120             break;
11121         }
11122         done = TRUE;
11123         break;
11124
11125       case PositionDiagram:     /* should not happen; ignore */
11126       case ElapsedTime:         /* ignore */
11127       case NAG:                 /* ignore */
11128         if (appData.debugMode)
11129           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11130                   yy_text, (int) moveType);
11131         return LoadGameOneMove(EndOfFile); /* tail recursion */
11132
11133       case IllegalMove:
11134         if (appData.testLegality) {
11135             if (appData.debugMode)
11136               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11137             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11138                     (forwardMostMove / 2) + 1,
11139                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11140             DisplayError(move, 0);
11141             done = TRUE;
11142         } else {
11143             if (appData.debugMode)
11144               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11145                       yy_text, currentMoveString);
11146             fromX = currentMoveString[0] - AAA;
11147             fromY = currentMoveString[1] - ONE;
11148             toX = currentMoveString[2] - AAA;
11149             toY = currentMoveString[3] - ONE;
11150             promoChar = currentMoveString[4];
11151         }
11152         break;
11153
11154       case AmbiguousMove:
11155         if (appData.debugMode)
11156           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11157         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11158                 (forwardMostMove / 2) + 1,
11159                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11160         DisplayError(move, 0);
11161         done = TRUE;
11162         break;
11163
11164       default:
11165       case ImpossibleMove:
11166         if (appData.debugMode)
11167           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11168         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11169                 (forwardMostMove / 2) + 1,
11170                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11171         DisplayError(move, 0);
11172         done = TRUE;
11173         break;
11174     }
11175
11176     if (done) {
11177         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11178             DrawPosition(FALSE, boards[currentMove]);
11179             DisplayBothClocks();
11180             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11181               DisplayComment(currentMove - 1, commentList[currentMove]);
11182         }
11183         (void) StopLoadGameTimer();
11184         gameFileFP = NULL;
11185         cmailOldMove = forwardMostMove;
11186         return FALSE;
11187     } else {
11188         /* currentMoveString is set as a side-effect of yylex */
11189
11190         thinkOutput[0] = NULLCHAR;
11191         MakeMove(fromX, fromY, toX, toY, promoChar);
11192         currentMove = forwardMostMove;
11193         return TRUE;
11194     }
11195 }
11196
11197 /* Load the nth game from the given file */
11198 int
11199 LoadGameFromFile (char *filename, int n, char *title, int useList)
11200 {
11201     FILE *f;
11202     char buf[MSG_SIZ];
11203
11204     if (strcmp(filename, "-") == 0) {
11205         f = stdin;
11206         title = "stdin";
11207     } else {
11208         f = fopen(filename, "rb");
11209         if (f == NULL) {
11210           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11211             DisplayError(buf, errno);
11212             return FALSE;
11213         }
11214     }
11215     if (fseek(f, 0, 0) == -1) {
11216         /* f is not seekable; probably a pipe */
11217         useList = FALSE;
11218     }
11219     if (useList && n == 0) {
11220         int error = GameListBuild(f);
11221         if (error) {
11222             DisplayError(_("Cannot build game list"), error);
11223         } else if (!ListEmpty(&gameList) &&
11224                    ((ListGame *) gameList.tailPred)->number > 1) {
11225             GameListPopUp(f, title);
11226             return TRUE;
11227         }
11228         GameListDestroy();
11229         n = 1;
11230     }
11231     if (n == 0) n = 1;
11232     return LoadGame(f, n, title, FALSE);
11233 }
11234
11235
11236 void
11237 MakeRegisteredMove ()
11238 {
11239     int fromX, fromY, toX, toY;
11240     char promoChar;
11241     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11242         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11243           case CMAIL_MOVE:
11244           case CMAIL_DRAW:
11245             if (appData.debugMode)
11246               fprintf(debugFP, "Restoring %s for game %d\n",
11247                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11248
11249             thinkOutput[0] = NULLCHAR;
11250             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11251             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11252             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11253             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11254             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11255             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11256             MakeMove(fromX, fromY, toX, toY, promoChar);
11257             ShowMove(fromX, fromY, toX, toY);
11258
11259             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11260               case MT_NONE:
11261               case MT_CHECK:
11262                 break;
11263
11264               case MT_CHECKMATE:
11265               case MT_STAINMATE:
11266                 if (WhiteOnMove(currentMove)) {
11267                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11268                 } else {
11269                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11270                 }
11271                 break;
11272
11273               case MT_STALEMATE:
11274                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11275                 break;
11276             }
11277
11278             break;
11279
11280           case CMAIL_RESIGN:
11281             if (WhiteOnMove(currentMove)) {
11282                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11283             } else {
11284                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11285             }
11286             break;
11287
11288           case CMAIL_ACCEPT:
11289             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11290             break;
11291
11292           default:
11293             break;
11294         }
11295     }
11296
11297     return;
11298 }
11299
11300 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11301 int
11302 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11303 {
11304     int retVal;
11305
11306     if (gameNumber > nCmailGames) {
11307         DisplayError(_("No more games in this message"), 0);
11308         return FALSE;
11309     }
11310     if (f == lastLoadGameFP) {
11311         int offset = gameNumber - lastLoadGameNumber;
11312         if (offset == 0) {
11313             cmailMsg[0] = NULLCHAR;
11314             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11315                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11316                 nCmailMovesRegistered--;
11317             }
11318             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11319             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11320                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11321             }
11322         } else {
11323             if (! RegisterMove()) return FALSE;
11324         }
11325     }
11326
11327     retVal = LoadGame(f, gameNumber, title, useList);
11328
11329     /* Make move registered during previous look at this game, if any */
11330     MakeRegisteredMove();
11331
11332     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11333         commentList[currentMove]
11334           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11335         DisplayComment(currentMove - 1, commentList[currentMove]);
11336     }
11337
11338     return retVal;
11339 }
11340
11341 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11342 int
11343 ReloadGame (int offset)
11344 {
11345     int gameNumber = lastLoadGameNumber + offset;
11346     if (lastLoadGameFP == NULL) {
11347         DisplayError(_("No game has been loaded yet"), 0);
11348         return FALSE;
11349     }
11350     if (gameNumber <= 0) {
11351         DisplayError(_("Can't back up any further"), 0);
11352         return FALSE;
11353     }
11354     if (cmailMsgLoaded) {
11355         return CmailLoadGame(lastLoadGameFP, gameNumber,
11356                              lastLoadGameTitle, lastLoadGameUseList);
11357     } else {
11358         return LoadGame(lastLoadGameFP, gameNumber,
11359                         lastLoadGameTitle, lastLoadGameUseList);
11360     }
11361 }
11362
11363 int keys[EmptySquare+1];
11364
11365 int
11366 PositionMatches (Board b1, Board b2)
11367 {
11368     int r, f, sum=0;
11369     switch(appData.searchMode) {
11370         case 1: return CompareWithRights(b1, b2);
11371         case 2:
11372             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11373                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11374             }
11375             return TRUE;
11376         case 3:
11377             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11378               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11379                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11380             }
11381             return sum==0;
11382         case 4:
11383             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11384                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11385             }
11386             return sum==0;
11387     }
11388     return TRUE;
11389 }
11390
11391 #define Q_PROMO  4
11392 #define Q_EP     3
11393 #define Q_BCASTL 2
11394 #define Q_WCASTL 1
11395
11396 int pieceList[256], quickBoard[256];
11397 ChessSquare pieceType[256] = { EmptySquare };
11398 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11399 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11400 int soughtTotal, turn;
11401 Boolean epOK, flipSearch;
11402
11403 typedef struct {
11404     unsigned char piece, to;
11405 } Move;
11406
11407 #define DSIZE (250000)
11408
11409 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11410 Move *moveDatabase = initialSpace;
11411 unsigned int movePtr, dataSize = DSIZE;
11412
11413 int
11414 MakePieceList (Board board, int *counts)
11415 {
11416     int r, f, n=Q_PROMO, total=0;
11417     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11418     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11419         int sq = f + (r<<4);
11420         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11421             quickBoard[sq] = ++n;
11422             pieceList[n] = sq;
11423             pieceType[n] = board[r][f];
11424             counts[board[r][f]]++;
11425             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11426             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11427             total++;
11428         }
11429     }
11430     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11431     return total;
11432 }
11433
11434 void
11435 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11436 {
11437     int sq = fromX + (fromY<<4);
11438     int piece = quickBoard[sq];
11439     quickBoard[sq] = 0;
11440     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11441     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11442         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11443         moveDatabase[movePtr++].piece = Q_WCASTL;
11444         quickBoard[sq] = piece;
11445         piece = quickBoard[from]; quickBoard[from] = 0;
11446         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11447     } else
11448     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11449         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11450         moveDatabase[movePtr++].piece = Q_BCASTL;
11451         quickBoard[sq] = piece;
11452         piece = quickBoard[from]; quickBoard[from] = 0;
11453         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11454     } else
11455     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11456         quickBoard[(fromY<<4)+toX] = 0;
11457         moveDatabase[movePtr].piece = Q_EP;
11458         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11459         moveDatabase[movePtr].to = sq;
11460     } else
11461     if(promoPiece != pieceType[piece]) {
11462         moveDatabase[movePtr++].piece = Q_PROMO;
11463         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11464     }
11465     moveDatabase[movePtr].piece = piece;
11466     quickBoard[sq] = piece;
11467     movePtr++;
11468 }
11469
11470 int
11471 PackGame (Board board)
11472 {
11473     Move *newSpace = NULL;
11474     moveDatabase[movePtr].piece = 0; // terminate previous game
11475     if(movePtr > dataSize) {
11476         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11477         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11478         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11479         if(newSpace) {
11480             int i;
11481             Move *p = moveDatabase, *q = newSpace;
11482             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11483             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11484             moveDatabase = newSpace;
11485         } else { // calloc failed, we must be out of memory. Too bad...
11486             dataSize = 0; // prevent calloc events for all subsequent games
11487             return 0;     // and signal this one isn't cached
11488         }
11489     }
11490     movePtr++;
11491     MakePieceList(board, counts);
11492     return movePtr;
11493 }
11494
11495 int
11496 QuickCompare (Board board, int *minCounts, int *maxCounts)
11497 {   // compare according to search mode
11498     int r, f;
11499     switch(appData.searchMode)
11500     {
11501       case 1: // exact position match
11502         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11503         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11504             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11505         }
11506         break;
11507       case 2: // can have extra material on empty squares
11508         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11509             if(board[r][f] == EmptySquare) continue;
11510             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11511         }
11512         break;
11513       case 3: // material with exact Pawn structure
11514         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11515             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11516             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11517         } // fall through to material comparison
11518       case 4: // exact material
11519         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11520         break;
11521       case 6: // material range with given imbalance
11522         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11523         // fall through to range comparison
11524       case 5: // material range
11525         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11526     }
11527     return TRUE;
11528 }
11529
11530 int
11531 QuickScan (Board board, Move *move)
11532 {   // reconstruct game,and compare all positions in it
11533     int cnt=0, stretch=0, total = MakePieceList(board, counts), delayedKing = -1;
11534     do {
11535         int piece = move->piece;
11536         int to = move->to, from = pieceList[piece];
11537         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11538           if(!piece) return -1;
11539           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11540             piece = (++move)->piece;
11541             from = pieceList[piece];
11542             counts[pieceType[piece]]--;
11543             pieceType[piece] = (ChessSquare) move->to;
11544             counts[move->to]++;
11545           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11546             counts[pieceType[quickBoard[to]]]--;
11547             quickBoard[to] = 0; total--;
11548             move++;
11549             continue;
11550           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11551             int rook;
11552             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11553             from  = pieceList[piece]; // so this must be King
11554             quickBoard[from] = 0;
11555             pieceList[piece] = to;
11556             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11557             quickBoard[from] = 0; // rook
11558             quickBoard[to] = piece;
11559             to = move->to; piece = move->piece;
11560             goto aftercastle;
11561           }
11562         }
11563         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11564         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11565         quickBoard[from] = 0;
11566       aftercastle:
11567         quickBoard[to] = piece;
11568         pieceList[piece] = to;
11569         cnt++; turn ^= 3;
11570         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11571            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11572            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11573                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11574           ) {
11575             static int lastCounts[EmptySquare+1];
11576             int i;
11577             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11578             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11579         } else stretch = 0;
11580         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11581         move++; delayedKing = -1;
11582     } while(1);
11583 }
11584
11585 void
11586 InitSearch ()
11587 {
11588     int r, f;
11589     flipSearch = FALSE;
11590     CopyBoard(soughtBoard, boards[currentMove]);
11591     soughtTotal = MakePieceList(soughtBoard, maxSought);
11592     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11593     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11594     CopyBoard(reverseBoard, boards[currentMove]);
11595     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11596         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11597         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11598         reverseBoard[r][f] = piece;
11599     }
11600     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11601     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11602     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11603                  || (boards[currentMove][CASTLING][2] == NoRights || 
11604                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11605                  && (boards[currentMove][CASTLING][5] == NoRights || 
11606                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11607       ) {
11608         flipSearch = TRUE;
11609         CopyBoard(flipBoard, soughtBoard);
11610         CopyBoard(rotateBoard, reverseBoard);
11611         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11612             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11613             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11614         }
11615     }
11616     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11617     if(appData.searchMode >= 5) {
11618         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11619         MakePieceList(soughtBoard, minSought);
11620         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11621     }
11622     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11623         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11624 }
11625
11626 GameInfo dummyInfo;
11627
11628 int
11629 GameContainsPosition (FILE *f, ListGame *lg)
11630 {
11631     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11632     int fromX, fromY, toX, toY;
11633     char promoChar;
11634     static int initDone=FALSE;
11635
11636     // weed out games based on numerical tag comparison
11637     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11638     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11639     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11640     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11641     if(!initDone) {
11642         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11643         initDone = TRUE;
11644     }
11645     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11646     else CopyBoard(boards[scratch], initialPosition); // default start position
11647     if(lg->moves) {
11648         turn = btm + 1;
11649         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11650         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11651     }
11652     if(btm) plyNr++;
11653     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11654     fseek(f, lg->offset, 0);
11655     yynewfile(f);
11656     while(1) {
11657         yyboardindex = scratch;
11658         quickFlag = plyNr+1;
11659         next = Myylex();
11660         quickFlag = 0;
11661         switch(next) {
11662             case PGNTag:
11663                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11664             default:
11665                 continue;
11666
11667             case XBoardGame:
11668             case GNUChessGame:
11669                 if(plyNr) return -1; // after we have seen moves, this is for new game
11670               continue;
11671
11672             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11673             case ImpossibleMove:
11674             case WhiteWins: // game ends here with these four
11675             case BlackWins:
11676             case GameIsDrawn:
11677             case GameUnfinished:
11678                 return -1;
11679
11680             case IllegalMove:
11681                 if(appData.testLegality) return -1;
11682             case WhiteCapturesEnPassant:
11683             case BlackCapturesEnPassant:
11684             case WhitePromotion:
11685             case BlackPromotion:
11686             case WhiteNonPromotion:
11687             case BlackNonPromotion:
11688             case NormalMove:
11689             case WhiteKingSideCastle:
11690             case WhiteQueenSideCastle:
11691             case BlackKingSideCastle:
11692             case BlackQueenSideCastle:
11693             case WhiteKingSideCastleWild:
11694             case WhiteQueenSideCastleWild:
11695             case BlackKingSideCastleWild:
11696             case BlackQueenSideCastleWild:
11697             case WhiteHSideCastleFR:
11698             case WhiteASideCastleFR:
11699             case BlackHSideCastleFR:
11700             case BlackASideCastleFR:
11701                 fromX = currentMoveString[0] - AAA;
11702                 fromY = currentMoveString[1] - ONE;
11703                 toX = currentMoveString[2] - AAA;
11704                 toY = currentMoveString[3] - ONE;
11705                 promoChar = currentMoveString[4];
11706                 break;
11707             case WhiteDrop:
11708             case BlackDrop:
11709                 fromX = next == WhiteDrop ?
11710                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11711                   (int) CharToPiece(ToLower(currentMoveString[0]));
11712                 fromY = DROP_RANK;
11713                 toX = currentMoveString[2] - AAA;
11714                 toY = currentMoveString[3] - ONE;
11715                 promoChar = 0;
11716                 break;
11717         }
11718         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11719         plyNr++;
11720         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11721         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11722         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11723         if(appData.findMirror) {
11724             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11725             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11726         }
11727     }
11728 }
11729
11730 /* Load the nth game from open file f */
11731 int
11732 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11733 {
11734     ChessMove cm;
11735     char buf[MSG_SIZ];
11736     int gn = gameNumber;
11737     ListGame *lg = NULL;
11738     int numPGNTags = 0;
11739     int err, pos = -1;
11740     GameMode oldGameMode;
11741     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11742
11743     if (appData.debugMode)
11744         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11745
11746     if (gameMode == Training )
11747         SetTrainingModeOff();
11748
11749     oldGameMode = gameMode;
11750     if (gameMode != BeginningOfGame) {
11751       Reset(FALSE, TRUE);
11752     }
11753
11754     gameFileFP = f;
11755     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11756         fclose(lastLoadGameFP);
11757     }
11758
11759     if (useList) {
11760         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11761
11762         if (lg) {
11763             fseek(f, lg->offset, 0);
11764             GameListHighlight(gameNumber);
11765             pos = lg->position;
11766             gn = 1;
11767         }
11768         else {
11769             DisplayError(_("Game number out of range"), 0);
11770             return FALSE;
11771         }
11772     } else {
11773         GameListDestroy();
11774         if (fseek(f, 0, 0) == -1) {
11775             if (f == lastLoadGameFP ?
11776                 gameNumber == lastLoadGameNumber + 1 :
11777                 gameNumber == 1) {
11778                 gn = 1;
11779             } else {
11780                 DisplayError(_("Can't seek on game file"), 0);
11781                 return FALSE;
11782             }
11783         }
11784     }
11785     lastLoadGameFP = f;
11786     lastLoadGameNumber = gameNumber;
11787     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11788     lastLoadGameUseList = useList;
11789
11790     yynewfile(f);
11791
11792     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11793       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11794                 lg->gameInfo.black);
11795             DisplayTitle(buf);
11796     } else if (*title != NULLCHAR) {
11797         if (gameNumber > 1) {
11798           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11799             DisplayTitle(buf);
11800         } else {
11801             DisplayTitle(title);
11802         }
11803     }
11804
11805     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11806         gameMode = PlayFromGameFile;
11807         ModeHighlight();
11808     }
11809
11810     currentMove = forwardMostMove = backwardMostMove = 0;
11811     CopyBoard(boards[0], initialPosition);
11812     StopClocks();
11813
11814     /*
11815      * Skip the first gn-1 games in the file.
11816      * Also skip over anything that precedes an identifiable
11817      * start of game marker, to avoid being confused by
11818      * garbage at the start of the file.  Currently
11819      * recognized start of game markers are the move number "1",
11820      * the pattern "gnuchess .* game", the pattern
11821      * "^[#;%] [^ ]* game file", and a PGN tag block.
11822      * A game that starts with one of the latter two patterns
11823      * will also have a move number 1, possibly
11824      * following a position diagram.
11825      * 5-4-02: Let's try being more lenient and allowing a game to
11826      * start with an unnumbered move.  Does that break anything?
11827      */
11828     cm = lastLoadGameStart = EndOfFile;
11829     while (gn > 0) {
11830         yyboardindex = forwardMostMove;
11831         cm = (ChessMove) Myylex();
11832         switch (cm) {
11833           case EndOfFile:
11834             if (cmailMsgLoaded) {
11835                 nCmailGames = CMAIL_MAX_GAMES - gn;
11836             } else {
11837                 Reset(TRUE, TRUE);
11838                 DisplayError(_("Game not found in file"), 0);
11839             }
11840             return FALSE;
11841
11842           case GNUChessGame:
11843           case XBoardGame:
11844             gn--;
11845             lastLoadGameStart = cm;
11846             break;
11847
11848           case MoveNumberOne:
11849             switch (lastLoadGameStart) {
11850               case GNUChessGame:
11851               case XBoardGame:
11852               case PGNTag:
11853                 break;
11854               case MoveNumberOne:
11855               case EndOfFile:
11856                 gn--;           /* count this game */
11857                 lastLoadGameStart = cm;
11858                 break;
11859               default:
11860                 /* impossible */
11861                 break;
11862             }
11863             break;
11864
11865           case PGNTag:
11866             switch (lastLoadGameStart) {
11867               case GNUChessGame:
11868               case PGNTag:
11869               case MoveNumberOne:
11870               case EndOfFile:
11871                 gn--;           /* count this game */
11872                 lastLoadGameStart = cm;
11873                 break;
11874               case XBoardGame:
11875                 lastLoadGameStart = cm; /* game counted already */
11876                 break;
11877               default:
11878                 /* impossible */
11879                 break;
11880             }
11881             if (gn > 0) {
11882                 do {
11883                     yyboardindex = forwardMostMove;
11884                     cm = (ChessMove) Myylex();
11885                 } while (cm == PGNTag || cm == Comment);
11886             }
11887             break;
11888
11889           case WhiteWins:
11890           case BlackWins:
11891           case GameIsDrawn:
11892             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11893                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11894                     != CMAIL_OLD_RESULT) {
11895                     nCmailResults ++ ;
11896                     cmailResult[  CMAIL_MAX_GAMES
11897                                 - gn - 1] = CMAIL_OLD_RESULT;
11898                 }
11899             }
11900             break;
11901
11902           case NormalMove:
11903             /* Only a NormalMove can be at the start of a game
11904              * without a position diagram. */
11905             if (lastLoadGameStart == EndOfFile ) {
11906               gn--;
11907               lastLoadGameStart = MoveNumberOne;
11908             }
11909             break;
11910
11911           default:
11912             break;
11913         }
11914     }
11915
11916     if (appData.debugMode)
11917       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11918
11919     if (cm == XBoardGame) {
11920         /* Skip any header junk before position diagram and/or move 1 */
11921         for (;;) {
11922             yyboardindex = forwardMostMove;
11923             cm = (ChessMove) Myylex();
11924
11925             if (cm == EndOfFile ||
11926                 cm == GNUChessGame || cm == XBoardGame) {
11927                 /* Empty game; pretend end-of-file and handle later */
11928                 cm = EndOfFile;
11929                 break;
11930             }
11931
11932             if (cm == MoveNumberOne || cm == PositionDiagram ||
11933                 cm == PGNTag || cm == Comment)
11934               break;
11935         }
11936     } else if (cm == GNUChessGame) {
11937         if (gameInfo.event != NULL) {
11938             free(gameInfo.event);
11939         }
11940         gameInfo.event = StrSave(yy_text);
11941     }
11942
11943     startedFromSetupPosition = FALSE;
11944     while (cm == PGNTag) {
11945         if (appData.debugMode)
11946           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11947         err = ParsePGNTag(yy_text, &gameInfo);
11948         if (!err) numPGNTags++;
11949
11950         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11951         if(gameInfo.variant != oldVariant) {
11952             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11953             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11954             InitPosition(TRUE);
11955             oldVariant = gameInfo.variant;
11956             if (appData.debugMode)
11957               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11958         }
11959
11960
11961         if (gameInfo.fen != NULL) {
11962           Board initial_position;
11963           startedFromSetupPosition = TRUE;
11964           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11965             Reset(TRUE, TRUE);
11966             DisplayError(_("Bad FEN position in file"), 0);
11967             return FALSE;
11968           }
11969           CopyBoard(boards[0], initial_position);
11970           if (blackPlaysFirst) {
11971             currentMove = forwardMostMove = backwardMostMove = 1;
11972             CopyBoard(boards[1], initial_position);
11973             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11974             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11975             timeRemaining[0][1] = whiteTimeRemaining;
11976             timeRemaining[1][1] = blackTimeRemaining;
11977             if (commentList[0] != NULL) {
11978               commentList[1] = commentList[0];
11979               commentList[0] = NULL;
11980             }
11981           } else {
11982             currentMove = forwardMostMove = backwardMostMove = 0;
11983           }
11984           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11985           {   int i;
11986               initialRulePlies = FENrulePlies;
11987               for( i=0; i< nrCastlingRights; i++ )
11988                   initialRights[i] = initial_position[CASTLING][i];
11989           }
11990           yyboardindex = forwardMostMove;
11991           free(gameInfo.fen);
11992           gameInfo.fen = NULL;
11993         }
11994
11995         yyboardindex = forwardMostMove;
11996         cm = (ChessMove) Myylex();
11997
11998         /* Handle comments interspersed among the tags */
11999         while (cm == Comment) {
12000             char *p;
12001             if (appData.debugMode)
12002               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12003             p = yy_text;
12004             AppendComment(currentMove, p, FALSE);
12005             yyboardindex = forwardMostMove;
12006             cm = (ChessMove) Myylex();
12007         }
12008     }
12009
12010     /* don't rely on existence of Event tag since if game was
12011      * pasted from clipboard the Event tag may not exist
12012      */
12013     if (numPGNTags > 0){
12014         char *tags;
12015         if (gameInfo.variant == VariantNormal) {
12016           VariantClass v = StringToVariant(gameInfo.event);
12017           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12018           if(v < VariantShogi) gameInfo.variant = v;
12019         }
12020         if (!matchMode) {
12021           if( appData.autoDisplayTags ) {
12022             tags = PGNTags(&gameInfo);
12023             TagsPopUp(tags, CmailMsg());
12024             free(tags);
12025           }
12026         }
12027     } else {
12028         /* Make something up, but don't display it now */
12029         SetGameInfo();
12030         TagsPopDown();
12031     }
12032
12033     if (cm == PositionDiagram) {
12034         int i, j;
12035         char *p;
12036         Board initial_position;
12037
12038         if (appData.debugMode)
12039           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12040
12041         if (!startedFromSetupPosition) {
12042             p = yy_text;
12043             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12044               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12045                 switch (*p) {
12046                   case '{':
12047                   case '[':
12048                   case '-':
12049                   case ' ':
12050                   case '\t':
12051                   case '\n':
12052                   case '\r':
12053                     break;
12054                   default:
12055                     initial_position[i][j++] = CharToPiece(*p);
12056                     break;
12057                 }
12058             while (*p == ' ' || *p == '\t' ||
12059                    *p == '\n' || *p == '\r') p++;
12060
12061             if (strncmp(p, "black", strlen("black"))==0)
12062               blackPlaysFirst = TRUE;
12063             else
12064               blackPlaysFirst = FALSE;
12065             startedFromSetupPosition = TRUE;
12066
12067             CopyBoard(boards[0], initial_position);
12068             if (blackPlaysFirst) {
12069                 currentMove = forwardMostMove = backwardMostMove = 1;
12070                 CopyBoard(boards[1], initial_position);
12071                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12072                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12073                 timeRemaining[0][1] = whiteTimeRemaining;
12074                 timeRemaining[1][1] = blackTimeRemaining;
12075                 if (commentList[0] != NULL) {
12076                     commentList[1] = commentList[0];
12077                     commentList[0] = NULL;
12078                 }
12079             } else {
12080                 currentMove = forwardMostMove = backwardMostMove = 0;
12081             }
12082         }
12083         yyboardindex = forwardMostMove;
12084         cm = (ChessMove) Myylex();
12085     }
12086
12087     if (first.pr == NoProc) {
12088         StartChessProgram(&first);
12089     }
12090     InitChessProgram(&first, FALSE);
12091     SendToProgram("force\n", &first);
12092     if (startedFromSetupPosition) {
12093         SendBoard(&first, forwardMostMove);
12094     if (appData.debugMode) {
12095         fprintf(debugFP, "Load Game\n");
12096     }
12097         DisplayBothClocks();
12098     }
12099
12100     /* [HGM] server: flag to write setup moves in broadcast file as one */
12101     loadFlag = appData.suppressLoadMoves;
12102
12103     while (cm == Comment) {
12104         char *p;
12105         if (appData.debugMode)
12106           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12107         p = yy_text;
12108         AppendComment(currentMove, p, FALSE);
12109         yyboardindex = forwardMostMove;
12110         cm = (ChessMove) Myylex();
12111     }
12112
12113     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12114         cm == WhiteWins || cm == BlackWins ||
12115         cm == GameIsDrawn || cm == GameUnfinished) {
12116         DisplayMessage("", _("No moves in game"));
12117         if (cmailMsgLoaded) {
12118             if (appData.debugMode)
12119               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12120             ClearHighlights();
12121             flipView = FALSE;
12122         }
12123         DrawPosition(FALSE, boards[currentMove]);
12124         DisplayBothClocks();
12125         gameMode = EditGame;
12126         ModeHighlight();
12127         gameFileFP = NULL;
12128         cmailOldMove = 0;
12129         return TRUE;
12130     }
12131
12132     // [HGM] PV info: routine tests if comment empty
12133     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12134         DisplayComment(currentMove - 1, commentList[currentMove]);
12135     }
12136     if (!matchMode && appData.timeDelay != 0)
12137       DrawPosition(FALSE, boards[currentMove]);
12138
12139     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12140       programStats.ok_to_send = 1;
12141     }
12142
12143     /* if the first token after the PGN tags is a move
12144      * and not move number 1, retrieve it from the parser
12145      */
12146     if (cm != MoveNumberOne)
12147         LoadGameOneMove(cm);
12148
12149     /* load the remaining moves from the file */
12150     while (LoadGameOneMove(EndOfFile)) {
12151       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12152       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12153     }
12154
12155     /* rewind to the start of the game */
12156     currentMove = backwardMostMove;
12157
12158     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12159
12160     if (oldGameMode == AnalyzeFile ||
12161         oldGameMode == AnalyzeMode) {
12162       AnalyzeFileEvent();
12163     }
12164
12165     if (!matchMode && pos > 0) {
12166         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12167     } else
12168     if (matchMode || appData.timeDelay == 0) {
12169       ToEndEvent();
12170     } else if (appData.timeDelay > 0) {
12171       AutoPlayGameLoop();
12172     }
12173
12174     if (appData.debugMode)
12175         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12176
12177     loadFlag = 0; /* [HGM] true game starts */
12178     return TRUE;
12179 }
12180
12181 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12182 int
12183 ReloadPosition (int offset)
12184 {
12185     int positionNumber = lastLoadPositionNumber + offset;
12186     if (lastLoadPositionFP == NULL) {
12187         DisplayError(_("No position has been loaded yet"), 0);
12188         return FALSE;
12189     }
12190     if (positionNumber <= 0) {
12191         DisplayError(_("Can't back up any further"), 0);
12192         return FALSE;
12193     }
12194     return LoadPosition(lastLoadPositionFP, positionNumber,
12195                         lastLoadPositionTitle);
12196 }
12197
12198 /* Load the nth position from the given file */
12199 int
12200 LoadPositionFromFile (char *filename, int n, char *title)
12201 {
12202     FILE *f;
12203     char buf[MSG_SIZ];
12204
12205     if (strcmp(filename, "-") == 0) {
12206         return LoadPosition(stdin, n, "stdin");
12207     } else {
12208         f = fopen(filename, "rb");
12209         if (f == NULL) {
12210             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12211             DisplayError(buf, errno);
12212             return FALSE;
12213         } else {
12214             return LoadPosition(f, n, title);
12215         }
12216     }
12217 }
12218
12219 /* Load the nth position from the given open file, and close it */
12220 int
12221 LoadPosition (FILE *f, int positionNumber, char *title)
12222 {
12223     char *p, line[MSG_SIZ];
12224     Board initial_position;
12225     int i, j, fenMode, pn;
12226
12227     if (gameMode == Training )
12228         SetTrainingModeOff();
12229
12230     if (gameMode != BeginningOfGame) {
12231         Reset(FALSE, TRUE);
12232     }
12233     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12234         fclose(lastLoadPositionFP);
12235     }
12236     if (positionNumber == 0) positionNumber = 1;
12237     lastLoadPositionFP = f;
12238     lastLoadPositionNumber = positionNumber;
12239     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12240     if (first.pr == NoProc && !appData.noChessProgram) {
12241       StartChessProgram(&first);
12242       InitChessProgram(&first, FALSE);
12243     }
12244     pn = positionNumber;
12245     if (positionNumber < 0) {
12246         /* Negative position number means to seek to that byte offset */
12247         if (fseek(f, -positionNumber, 0) == -1) {
12248             DisplayError(_("Can't seek on position file"), 0);
12249             return FALSE;
12250         };
12251         pn = 1;
12252     } else {
12253         if (fseek(f, 0, 0) == -1) {
12254             if (f == lastLoadPositionFP ?
12255                 positionNumber == lastLoadPositionNumber + 1 :
12256                 positionNumber == 1) {
12257                 pn = 1;
12258             } else {
12259                 DisplayError(_("Can't seek on position file"), 0);
12260                 return FALSE;
12261             }
12262         }
12263     }
12264     /* See if this file is FEN or old-style xboard */
12265     if (fgets(line, MSG_SIZ, f) == NULL) {
12266         DisplayError(_("Position not found in file"), 0);
12267         return FALSE;
12268     }
12269     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12270     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12271
12272     if (pn >= 2) {
12273         if (fenMode || line[0] == '#') pn--;
12274         while (pn > 0) {
12275             /* skip positions before number pn */
12276             if (fgets(line, MSG_SIZ, f) == NULL) {
12277                 Reset(TRUE, TRUE);
12278                 DisplayError(_("Position not found in file"), 0);
12279                 return FALSE;
12280             }
12281             if (fenMode || line[0] == '#') pn--;
12282         }
12283     }
12284
12285     if (fenMode) {
12286         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12287             DisplayError(_("Bad FEN position in file"), 0);
12288             return FALSE;
12289         }
12290     } else {
12291         (void) fgets(line, MSG_SIZ, f);
12292         (void) fgets(line, MSG_SIZ, f);
12293
12294         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12295             (void) fgets(line, MSG_SIZ, f);
12296             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12297                 if (*p == ' ')
12298                   continue;
12299                 initial_position[i][j++] = CharToPiece(*p);
12300             }
12301         }
12302
12303         blackPlaysFirst = FALSE;
12304         if (!feof(f)) {
12305             (void) fgets(line, MSG_SIZ, f);
12306             if (strncmp(line, "black", strlen("black"))==0)
12307               blackPlaysFirst = TRUE;
12308         }
12309     }
12310     startedFromSetupPosition = TRUE;
12311
12312     CopyBoard(boards[0], initial_position);
12313     if (blackPlaysFirst) {
12314         currentMove = forwardMostMove = backwardMostMove = 1;
12315         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12316         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12317         CopyBoard(boards[1], initial_position);
12318         DisplayMessage("", _("Black to play"));
12319     } else {
12320         currentMove = forwardMostMove = backwardMostMove = 0;
12321         DisplayMessage("", _("White to play"));
12322     }
12323     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12324     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12325         SendToProgram("force\n", &first);
12326         SendBoard(&first, forwardMostMove);
12327     }
12328     if (appData.debugMode) {
12329 int i, j;
12330   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12331   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12332         fprintf(debugFP, "Load Position\n");
12333     }
12334
12335     if (positionNumber > 1) {
12336       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12337         DisplayTitle(line);
12338     } else {
12339         DisplayTitle(title);
12340     }
12341     gameMode = EditGame;
12342     ModeHighlight();
12343     ResetClocks();
12344     timeRemaining[0][1] = whiteTimeRemaining;
12345     timeRemaining[1][1] = blackTimeRemaining;
12346     DrawPosition(FALSE, boards[currentMove]);
12347
12348     return TRUE;
12349 }
12350
12351
12352 void
12353 CopyPlayerNameIntoFileName (char **dest, char *src)
12354 {
12355     while (*src != NULLCHAR && *src != ',') {
12356         if (*src == ' ') {
12357             *(*dest)++ = '_';
12358             src++;
12359         } else {
12360             *(*dest)++ = *src++;
12361         }
12362     }
12363 }
12364
12365 char *
12366 DefaultFileName (char *ext)
12367 {
12368     static char def[MSG_SIZ];
12369     char *p;
12370
12371     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12372         p = def;
12373         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12374         *p++ = '-';
12375         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12376         *p++ = '.';
12377         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12378     } else {
12379         def[0] = NULLCHAR;
12380     }
12381     return def;
12382 }
12383
12384 /* Save the current game to the given file */
12385 int
12386 SaveGameToFile (char *filename, int append)
12387 {
12388     FILE *f;
12389     char buf[MSG_SIZ];
12390     int result, i, t,tot=0;
12391
12392     if (strcmp(filename, "-") == 0) {
12393         return SaveGame(stdout, 0, NULL);
12394     } else {
12395         for(i=0; i<10; i++) { // upto 10 tries
12396              f = fopen(filename, append ? "a" : "w");
12397              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12398              if(f || errno != 13) break;
12399              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12400              tot += t;
12401         }
12402         if (f == NULL) {
12403             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12404             DisplayError(buf, errno);
12405             return FALSE;
12406         } else {
12407             safeStrCpy(buf, lastMsg, MSG_SIZ);
12408             DisplayMessage(_("Waiting for access to save file"), "");
12409             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12410             DisplayMessage(_("Saving game"), "");
12411             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12412             result = SaveGame(f, 0, NULL);
12413             DisplayMessage(buf, "");
12414             return result;
12415         }
12416     }
12417 }
12418
12419 char *
12420 SavePart (char *str)
12421 {
12422     static char buf[MSG_SIZ];
12423     char *p;
12424
12425     p = strchr(str, ' ');
12426     if (p == NULL) return str;
12427     strncpy(buf, str, p - str);
12428     buf[p - str] = NULLCHAR;
12429     return buf;
12430 }
12431
12432 #define PGN_MAX_LINE 75
12433
12434 #define PGN_SIDE_WHITE  0
12435 #define PGN_SIDE_BLACK  1
12436
12437 static int
12438 FindFirstMoveOutOfBook (int side)
12439 {
12440     int result = -1;
12441
12442     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12443         int index = backwardMostMove;
12444         int has_book_hit = 0;
12445
12446         if( (index % 2) != side ) {
12447             index++;
12448         }
12449
12450         while( index < forwardMostMove ) {
12451             /* Check to see if engine is in book */
12452             int depth = pvInfoList[index].depth;
12453             int score = pvInfoList[index].score;
12454             int in_book = 0;
12455
12456             if( depth <= 2 ) {
12457                 in_book = 1;
12458             }
12459             else if( score == 0 && depth == 63 ) {
12460                 in_book = 1; /* Zappa */
12461             }
12462             else if( score == 2 && depth == 99 ) {
12463                 in_book = 1; /* Abrok */
12464             }
12465
12466             has_book_hit += in_book;
12467
12468             if( ! in_book ) {
12469                 result = index;
12470
12471                 break;
12472             }
12473
12474             index += 2;
12475         }
12476     }
12477
12478     return result;
12479 }
12480
12481 void
12482 GetOutOfBookInfo (char * buf)
12483 {
12484     int oob[2];
12485     int i;
12486     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12487
12488     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12489     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12490
12491     *buf = '\0';
12492
12493     if( oob[0] >= 0 || oob[1] >= 0 ) {
12494         for( i=0; i<2; i++ ) {
12495             int idx = oob[i];
12496
12497             if( idx >= 0 ) {
12498                 if( i > 0 && oob[0] >= 0 ) {
12499                     strcat( buf, "   " );
12500                 }
12501
12502                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12503                 sprintf( buf+strlen(buf), "%s%.2f",
12504                     pvInfoList[idx].score >= 0 ? "+" : "",
12505                     pvInfoList[idx].score / 100.0 );
12506             }
12507         }
12508     }
12509 }
12510
12511 /* Save game in PGN style and close the file */
12512 int
12513 SaveGamePGN (FILE *f)
12514 {
12515     int i, offset, linelen, newblock;
12516     time_t tm;
12517 //    char *movetext;
12518     char numtext[32];
12519     int movelen, numlen, blank;
12520     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12521
12522     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12523
12524     tm = time((time_t *) NULL);
12525
12526     PrintPGNTags(f, &gameInfo);
12527
12528     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12529
12530     if (backwardMostMove > 0 || startedFromSetupPosition) {
12531         char *fen = PositionToFEN(backwardMostMove, NULL);
12532         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12533         fprintf(f, "\n{--------------\n");
12534         PrintPosition(f, backwardMostMove);
12535         fprintf(f, "--------------}\n");
12536         free(fen);
12537     }
12538     else {
12539         /* [AS] Out of book annotation */
12540         if( appData.saveOutOfBookInfo ) {
12541             char buf[64];
12542
12543             GetOutOfBookInfo( buf );
12544
12545             if( buf[0] != '\0' ) {
12546                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12547             }
12548         }
12549
12550         fprintf(f, "\n");
12551     }
12552
12553     i = backwardMostMove;
12554     linelen = 0;
12555     newblock = TRUE;
12556
12557     while (i < forwardMostMove) {
12558         /* Print comments preceding this move */
12559         if (commentList[i] != NULL) {
12560             if (linelen > 0) fprintf(f, "\n");
12561             fprintf(f, "%s", commentList[i]);
12562             linelen = 0;
12563             newblock = TRUE;
12564         }
12565
12566         /* Format move number */
12567         if ((i % 2) == 0)
12568           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12569         else
12570           if (newblock)
12571             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12572           else
12573             numtext[0] = NULLCHAR;
12574
12575         numlen = strlen(numtext);
12576         newblock = FALSE;
12577
12578         /* Print move number */
12579         blank = linelen > 0 && numlen > 0;
12580         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12581             fprintf(f, "\n");
12582             linelen = 0;
12583             blank = 0;
12584         }
12585         if (blank) {
12586             fprintf(f, " ");
12587             linelen++;
12588         }
12589         fprintf(f, "%s", numtext);
12590         linelen += numlen;
12591
12592         /* Get move */
12593         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12594         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12595
12596         /* Print move */
12597         blank = linelen > 0 && movelen > 0;
12598         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12599             fprintf(f, "\n");
12600             linelen = 0;
12601             blank = 0;
12602         }
12603         if (blank) {
12604             fprintf(f, " ");
12605             linelen++;
12606         }
12607         fprintf(f, "%s", move_buffer);
12608         linelen += movelen;
12609
12610         /* [AS] Add PV info if present */
12611         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12612             /* [HGM] add time */
12613             char buf[MSG_SIZ]; int seconds;
12614
12615             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12616
12617             if( seconds <= 0)
12618               buf[0] = 0;
12619             else
12620               if( seconds < 30 )
12621                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12622               else
12623                 {
12624                   seconds = (seconds + 4)/10; // round to full seconds
12625                   if( seconds < 60 )
12626                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12627                   else
12628                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12629                 }
12630
12631             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12632                       pvInfoList[i].score >= 0 ? "+" : "",
12633                       pvInfoList[i].score / 100.0,
12634                       pvInfoList[i].depth,
12635                       buf );
12636
12637             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12638
12639             /* Print score/depth */
12640             blank = linelen > 0 && movelen > 0;
12641             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12642                 fprintf(f, "\n");
12643                 linelen = 0;
12644                 blank = 0;
12645             }
12646             if (blank) {
12647                 fprintf(f, " ");
12648                 linelen++;
12649             }
12650             fprintf(f, "%s", move_buffer);
12651             linelen += movelen;
12652         }
12653
12654         i++;
12655     }
12656
12657     /* Start a new line */
12658     if (linelen > 0) fprintf(f, "\n");
12659
12660     /* Print comments after last move */
12661     if (commentList[i] != NULL) {
12662         fprintf(f, "%s\n", commentList[i]);
12663     }
12664
12665     /* Print result */
12666     if (gameInfo.resultDetails != NULL &&
12667         gameInfo.resultDetails[0] != NULLCHAR) {
12668         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12669                 PGNResult(gameInfo.result));
12670     } else {
12671         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12672     }
12673
12674     fclose(f);
12675     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12676     return TRUE;
12677 }
12678
12679 /* Save game in old style and close the file */
12680 int
12681 SaveGameOldStyle (FILE *f)
12682 {
12683     int i, offset;
12684     time_t tm;
12685
12686     tm = time((time_t *) NULL);
12687
12688     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12689     PrintOpponents(f);
12690
12691     if (backwardMostMove > 0 || startedFromSetupPosition) {
12692         fprintf(f, "\n[--------------\n");
12693         PrintPosition(f, backwardMostMove);
12694         fprintf(f, "--------------]\n");
12695     } else {
12696         fprintf(f, "\n");
12697     }
12698
12699     i = backwardMostMove;
12700     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12701
12702     while (i < forwardMostMove) {
12703         if (commentList[i] != NULL) {
12704             fprintf(f, "[%s]\n", commentList[i]);
12705         }
12706
12707         if ((i % 2) == 1) {
12708             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12709             i++;
12710         } else {
12711             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12712             i++;
12713             if (commentList[i] != NULL) {
12714                 fprintf(f, "\n");
12715                 continue;
12716             }
12717             if (i >= forwardMostMove) {
12718                 fprintf(f, "\n");
12719                 break;
12720             }
12721             fprintf(f, "%s\n", parseList[i]);
12722             i++;
12723         }
12724     }
12725
12726     if (commentList[i] != NULL) {
12727         fprintf(f, "[%s]\n", commentList[i]);
12728     }
12729
12730     /* This isn't really the old style, but it's close enough */
12731     if (gameInfo.resultDetails != NULL &&
12732         gameInfo.resultDetails[0] != NULLCHAR) {
12733         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12734                 gameInfo.resultDetails);
12735     } else {
12736         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12737     }
12738
12739     fclose(f);
12740     return TRUE;
12741 }
12742
12743 /* Save the current game to open file f and close the file */
12744 int
12745 SaveGame (FILE *f, int dummy, char *dummy2)
12746 {
12747     if (gameMode == EditPosition) EditPositionDone(TRUE);
12748     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12749     if (appData.oldSaveStyle)
12750       return SaveGameOldStyle(f);
12751     else
12752       return SaveGamePGN(f);
12753 }
12754
12755 /* Save the current position to the given file */
12756 int
12757 SavePositionToFile (char *filename)
12758 {
12759     FILE *f;
12760     char buf[MSG_SIZ];
12761
12762     if (strcmp(filename, "-") == 0) {
12763         return SavePosition(stdout, 0, NULL);
12764     } else {
12765         f = fopen(filename, "a");
12766         if (f == NULL) {
12767             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12768             DisplayError(buf, errno);
12769             return FALSE;
12770         } else {
12771             safeStrCpy(buf, lastMsg, MSG_SIZ);
12772             DisplayMessage(_("Waiting for access to save file"), "");
12773             flock(fileno(f), LOCK_EX); // [HGM] lock
12774             DisplayMessage(_("Saving position"), "");
12775             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12776             SavePosition(f, 0, NULL);
12777             DisplayMessage(buf, "");
12778             return TRUE;
12779         }
12780     }
12781 }
12782
12783 /* Save the current position to the given open file and close the file */
12784 int
12785 SavePosition (FILE *f, int dummy, char *dummy2)
12786 {
12787     time_t tm;
12788     char *fen;
12789
12790     if (gameMode == EditPosition) EditPositionDone(TRUE);
12791     if (appData.oldSaveStyle) {
12792         tm = time((time_t *) NULL);
12793
12794         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12795         PrintOpponents(f);
12796         fprintf(f, "[--------------\n");
12797         PrintPosition(f, currentMove);
12798         fprintf(f, "--------------]\n");
12799     } else {
12800         fen = PositionToFEN(currentMove, NULL);
12801         fprintf(f, "%s\n", fen);
12802         free(fen);
12803     }
12804     fclose(f);
12805     return TRUE;
12806 }
12807
12808 void
12809 ReloadCmailMsgEvent (int unregister)
12810 {
12811 #if !WIN32
12812     static char *inFilename = NULL;
12813     static char *outFilename;
12814     int i;
12815     struct stat inbuf, outbuf;
12816     int status;
12817
12818     /* Any registered moves are unregistered if unregister is set, */
12819     /* i.e. invoked by the signal handler */
12820     if (unregister) {
12821         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12822             cmailMoveRegistered[i] = FALSE;
12823             if (cmailCommentList[i] != NULL) {
12824                 free(cmailCommentList[i]);
12825                 cmailCommentList[i] = NULL;
12826             }
12827         }
12828         nCmailMovesRegistered = 0;
12829     }
12830
12831     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12832         cmailResult[i] = CMAIL_NOT_RESULT;
12833     }
12834     nCmailResults = 0;
12835
12836     if (inFilename == NULL) {
12837         /* Because the filenames are static they only get malloced once  */
12838         /* and they never get freed                                      */
12839         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12840         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12841
12842         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12843         sprintf(outFilename, "%s.out", appData.cmailGameName);
12844     }
12845
12846     status = stat(outFilename, &outbuf);
12847     if (status < 0) {
12848         cmailMailedMove = FALSE;
12849     } else {
12850         status = stat(inFilename, &inbuf);
12851         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12852     }
12853
12854     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12855        counts the games, notes how each one terminated, etc.
12856
12857        It would be nice to remove this kludge and instead gather all
12858        the information while building the game list.  (And to keep it
12859        in the game list nodes instead of having a bunch of fixed-size
12860        parallel arrays.)  Note this will require getting each game's
12861        termination from the PGN tags, as the game list builder does
12862        not process the game moves.  --mann
12863        */
12864     cmailMsgLoaded = TRUE;
12865     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12866
12867     /* Load first game in the file or popup game menu */
12868     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12869
12870 #endif /* !WIN32 */
12871     return;
12872 }
12873
12874 int
12875 RegisterMove ()
12876 {
12877     FILE *f;
12878     char string[MSG_SIZ];
12879
12880     if (   cmailMailedMove
12881         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12882         return TRUE;            /* Allow free viewing  */
12883     }
12884
12885     /* Unregister move to ensure that we don't leave RegisterMove        */
12886     /* with the move registered when the conditions for registering no   */
12887     /* longer hold                                                       */
12888     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12889         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12890         nCmailMovesRegistered --;
12891
12892         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12893           {
12894               free(cmailCommentList[lastLoadGameNumber - 1]);
12895               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12896           }
12897     }
12898
12899     if (cmailOldMove == -1) {
12900         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12901         return FALSE;
12902     }
12903
12904     if (currentMove > cmailOldMove + 1) {
12905         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12906         return FALSE;
12907     }
12908
12909     if (currentMove < cmailOldMove) {
12910         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12911         return FALSE;
12912     }
12913
12914     if (forwardMostMove > currentMove) {
12915         /* Silently truncate extra moves */
12916         TruncateGame();
12917     }
12918
12919     if (   (currentMove == cmailOldMove + 1)
12920         || (   (currentMove == cmailOldMove)
12921             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12922                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12923         if (gameInfo.result != GameUnfinished) {
12924             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12925         }
12926
12927         if (commentList[currentMove] != NULL) {
12928             cmailCommentList[lastLoadGameNumber - 1]
12929               = StrSave(commentList[currentMove]);
12930         }
12931         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12932
12933         if (appData.debugMode)
12934           fprintf(debugFP, "Saving %s for game %d\n",
12935                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12936
12937         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12938
12939         f = fopen(string, "w");
12940         if (appData.oldSaveStyle) {
12941             SaveGameOldStyle(f); /* also closes the file */
12942
12943             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12944             f = fopen(string, "w");
12945             SavePosition(f, 0, NULL); /* also closes the file */
12946         } else {
12947             fprintf(f, "{--------------\n");
12948             PrintPosition(f, currentMove);
12949             fprintf(f, "--------------}\n\n");
12950
12951             SaveGame(f, 0, NULL); /* also closes the file*/
12952         }
12953
12954         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12955         nCmailMovesRegistered ++;
12956     } else if (nCmailGames == 1) {
12957         DisplayError(_("You have not made a move yet"), 0);
12958         return FALSE;
12959     }
12960
12961     return TRUE;
12962 }
12963
12964 void
12965 MailMoveEvent ()
12966 {
12967 #if !WIN32
12968     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12969     FILE *commandOutput;
12970     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12971     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12972     int nBuffers;
12973     int i;
12974     int archived;
12975     char *arcDir;
12976
12977     if (! cmailMsgLoaded) {
12978         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12979         return;
12980     }
12981
12982     if (nCmailGames == nCmailResults) {
12983         DisplayError(_("No unfinished games"), 0);
12984         return;
12985     }
12986
12987 #if CMAIL_PROHIBIT_REMAIL
12988     if (cmailMailedMove) {
12989       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);
12990         DisplayError(msg, 0);
12991         return;
12992     }
12993 #endif
12994
12995     if (! (cmailMailedMove || RegisterMove())) return;
12996
12997     if (   cmailMailedMove
12998         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12999       snprintf(string, MSG_SIZ, partCommandString,
13000                appData.debugMode ? " -v" : "", appData.cmailGameName);
13001         commandOutput = popen(string, "r");
13002
13003         if (commandOutput == NULL) {
13004             DisplayError(_("Failed to invoke cmail"), 0);
13005         } else {
13006             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13007                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13008             }
13009             if (nBuffers > 1) {
13010                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13011                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13012                 nBytes = MSG_SIZ - 1;
13013             } else {
13014                 (void) memcpy(msg, buffer, nBytes);
13015             }
13016             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13017
13018             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13019                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13020
13021                 archived = TRUE;
13022                 for (i = 0; i < nCmailGames; i ++) {
13023                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13024                         archived = FALSE;
13025                     }
13026                 }
13027                 if (   archived
13028                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13029                         != NULL)) {
13030                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13031                            arcDir,
13032                            appData.cmailGameName,
13033                            gameInfo.date);
13034                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13035                     cmailMsgLoaded = FALSE;
13036                 }
13037             }
13038
13039             DisplayInformation(msg);
13040             pclose(commandOutput);
13041         }
13042     } else {
13043         if ((*cmailMsg) != '\0') {
13044             DisplayInformation(cmailMsg);
13045         }
13046     }
13047
13048     return;
13049 #endif /* !WIN32 */
13050 }
13051
13052 char *
13053 CmailMsg ()
13054 {
13055 #if WIN32
13056     return NULL;
13057 #else
13058     int  prependComma = 0;
13059     char number[5];
13060     char string[MSG_SIZ];       /* Space for game-list */
13061     int  i;
13062
13063     if (!cmailMsgLoaded) return "";
13064
13065     if (cmailMailedMove) {
13066       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13067     } else {
13068         /* Create a list of games left */
13069       snprintf(string, MSG_SIZ, "[");
13070         for (i = 0; i < nCmailGames; i ++) {
13071             if (! (   cmailMoveRegistered[i]
13072                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13073                 if (prependComma) {
13074                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13075                 } else {
13076                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13077                     prependComma = 1;
13078                 }
13079
13080                 strcat(string, number);
13081             }
13082         }
13083         strcat(string, "]");
13084
13085         if (nCmailMovesRegistered + nCmailResults == 0) {
13086             switch (nCmailGames) {
13087               case 1:
13088                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13089                 break;
13090
13091               case 2:
13092                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13093                 break;
13094
13095               default:
13096                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13097                          nCmailGames);
13098                 break;
13099             }
13100         } else {
13101             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13102               case 1:
13103                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13104                          string);
13105                 break;
13106
13107               case 0:
13108                 if (nCmailResults == nCmailGames) {
13109                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13110                 } else {
13111                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13112                 }
13113                 break;
13114
13115               default:
13116                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13117                          string);
13118             }
13119         }
13120     }
13121     return cmailMsg;
13122 #endif /* WIN32 */
13123 }
13124
13125 void
13126 ResetGameEvent ()
13127 {
13128     if (gameMode == Training)
13129       SetTrainingModeOff();
13130
13131     Reset(TRUE, TRUE);
13132     cmailMsgLoaded = FALSE;
13133     if (appData.icsActive) {
13134       SendToICS(ics_prefix);
13135       SendToICS("refresh\n");
13136     }
13137 }
13138
13139 void
13140 ExitEvent (int status)
13141 {
13142     exiting++;
13143     if (exiting > 2) {
13144       /* Give up on clean exit */
13145       exit(status);
13146     }
13147     if (exiting > 1) {
13148       /* Keep trying for clean exit */
13149       return;
13150     }
13151
13152     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13153
13154     if (telnetISR != NULL) {
13155       RemoveInputSource(telnetISR);
13156     }
13157     if (icsPR != NoProc) {
13158       DestroyChildProcess(icsPR, TRUE);
13159     }
13160
13161     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13162     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13163
13164     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13165     /* make sure this other one finishes before killing it!                  */
13166     if(endingGame) { int count = 0;
13167         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13168         while(endingGame && count++ < 10) DoSleep(1);
13169         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13170     }
13171
13172     /* Kill off chess programs */
13173     if (first.pr != NoProc) {
13174         ExitAnalyzeMode();
13175
13176         DoSleep( appData.delayBeforeQuit );
13177         SendToProgram("quit\n", &first);
13178         DoSleep( appData.delayAfterQuit );
13179         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13180     }
13181     if (second.pr != NoProc) {
13182         DoSleep( appData.delayBeforeQuit );
13183         SendToProgram("quit\n", &second);
13184         DoSleep( appData.delayAfterQuit );
13185         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13186     }
13187     if (first.isr != NULL) {
13188         RemoveInputSource(first.isr);
13189     }
13190     if (second.isr != NULL) {
13191         RemoveInputSource(second.isr);
13192     }
13193
13194     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13195     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13196
13197     ShutDownFrontEnd();
13198     exit(status);
13199 }
13200
13201 void
13202 PauseEvent ()
13203 {
13204     if (appData.debugMode)
13205         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13206     if (pausing) {
13207         pausing = FALSE;
13208         ModeHighlight();
13209         if (gameMode == MachinePlaysWhite ||
13210             gameMode == MachinePlaysBlack) {
13211             StartClocks();
13212         } else {
13213             DisplayBothClocks();
13214         }
13215         if (gameMode == PlayFromGameFile) {
13216             if (appData.timeDelay >= 0)
13217                 AutoPlayGameLoop();
13218         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13219             Reset(FALSE, TRUE);
13220             SendToICS(ics_prefix);
13221             SendToICS("refresh\n");
13222         } else if (currentMove < forwardMostMove) {
13223             ForwardInner(forwardMostMove);
13224         }
13225         pauseExamInvalid = FALSE;
13226     } else {
13227         switch (gameMode) {
13228           default:
13229             return;
13230           case IcsExamining:
13231             pauseExamForwardMostMove = forwardMostMove;
13232             pauseExamInvalid = FALSE;
13233             /* fall through */
13234           case IcsObserving:
13235           case IcsPlayingWhite:
13236           case IcsPlayingBlack:
13237             pausing = TRUE;
13238             ModeHighlight();
13239             return;
13240           case PlayFromGameFile:
13241             (void) StopLoadGameTimer();
13242             pausing = TRUE;
13243             ModeHighlight();
13244             break;
13245           case BeginningOfGame:
13246             if (appData.icsActive) return;
13247             /* else fall through */
13248           case MachinePlaysWhite:
13249           case MachinePlaysBlack:
13250           case TwoMachinesPlay:
13251             if (forwardMostMove == 0)
13252               return;           /* don't pause if no one has moved */
13253             if ((gameMode == MachinePlaysWhite &&
13254                  !WhiteOnMove(forwardMostMove)) ||
13255                 (gameMode == MachinePlaysBlack &&
13256                  WhiteOnMove(forwardMostMove))) {
13257                 StopClocks();
13258             }
13259             pausing = TRUE;
13260             ModeHighlight();
13261             break;
13262         }
13263     }
13264 }
13265
13266 void
13267 EditCommentEvent ()
13268 {
13269     char title[MSG_SIZ];
13270
13271     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13272       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13273     } else {
13274       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13275                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13276                parseList[currentMove - 1]);
13277     }
13278
13279     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13280 }
13281
13282
13283 void
13284 EditTagsEvent ()
13285 {
13286     char *tags = PGNTags(&gameInfo);
13287     bookUp = FALSE;
13288     EditTagsPopUp(tags, NULL);
13289     free(tags);
13290 }
13291
13292 void
13293 AnalyzeModeEvent ()
13294 {
13295     if (appData.noChessProgram || gameMode == AnalyzeMode)
13296       return;
13297
13298     if (gameMode != AnalyzeFile) {
13299         if (!appData.icsEngineAnalyze) {
13300                EditGameEvent();
13301                if (gameMode != EditGame) return;
13302         }
13303         ResurrectChessProgram();
13304         SendToProgram("analyze\n", &first);
13305         first.analyzing = TRUE;
13306         /*first.maybeThinking = TRUE;*/
13307         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13308         EngineOutputPopUp();
13309     }
13310     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13311     pausing = FALSE;
13312     ModeHighlight();
13313     SetGameInfo();
13314
13315     StartAnalysisClock();
13316     GetTimeMark(&lastNodeCountTime);
13317     lastNodeCount = 0;
13318 }
13319
13320 void
13321 AnalyzeFileEvent ()
13322 {
13323     if (appData.noChessProgram || gameMode == AnalyzeFile)
13324       return;
13325
13326     if (gameMode != AnalyzeMode) {
13327         EditGameEvent();
13328         if (gameMode != EditGame) return;
13329         ResurrectChessProgram();
13330         SendToProgram("analyze\n", &first);
13331         first.analyzing = TRUE;
13332         /*first.maybeThinking = TRUE;*/
13333         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13334         EngineOutputPopUp();
13335     }
13336     gameMode = AnalyzeFile;
13337     pausing = FALSE;
13338     ModeHighlight();
13339     SetGameInfo();
13340
13341     StartAnalysisClock();
13342     GetTimeMark(&lastNodeCountTime);
13343     lastNodeCount = 0;
13344     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13345 }
13346
13347 void
13348 MachineWhiteEvent ()
13349 {
13350     char buf[MSG_SIZ];
13351     char *bookHit = NULL;
13352
13353     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13354       return;
13355
13356
13357     if (gameMode == PlayFromGameFile ||
13358         gameMode == TwoMachinesPlay  ||
13359         gameMode == Training         ||
13360         gameMode == AnalyzeMode      ||
13361         gameMode == EndOfGame)
13362         EditGameEvent();
13363
13364     if (gameMode == EditPosition)
13365         EditPositionDone(TRUE);
13366
13367     if (!WhiteOnMove(currentMove)) {
13368         DisplayError(_("It is not White's turn"), 0);
13369         return;
13370     }
13371
13372     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13373       ExitAnalyzeMode();
13374
13375     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13376         gameMode == AnalyzeFile)
13377         TruncateGame();
13378
13379     ResurrectChessProgram();    /* in case it isn't running */
13380     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13381         gameMode = MachinePlaysWhite;
13382         ResetClocks();
13383     } else
13384     gameMode = MachinePlaysWhite;
13385     pausing = FALSE;
13386     ModeHighlight();
13387     SetGameInfo();
13388     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13389     DisplayTitle(buf);
13390     if (first.sendName) {
13391       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13392       SendToProgram(buf, &first);
13393     }
13394     if (first.sendTime) {
13395       if (first.useColors) {
13396         SendToProgram("black\n", &first); /*gnu kludge*/
13397       }
13398       SendTimeRemaining(&first, TRUE);
13399     }
13400     if (first.useColors) {
13401       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13402     }
13403     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13404     SetMachineThinkingEnables();
13405     first.maybeThinking = TRUE;
13406     StartClocks();
13407     firstMove = FALSE;
13408
13409     if (appData.autoFlipView && !flipView) {
13410       flipView = !flipView;
13411       DrawPosition(FALSE, NULL);
13412       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13413     }
13414
13415     if(bookHit) { // [HGM] book: simulate book reply
13416         static char bookMove[MSG_SIZ]; // a bit generous?
13417
13418         programStats.nodes = programStats.depth = programStats.time =
13419         programStats.score = programStats.got_only_move = 0;
13420         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13421
13422         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13423         strcat(bookMove, bookHit);
13424         HandleMachineMove(bookMove, &first);
13425     }
13426 }
13427
13428 void
13429 MachineBlackEvent ()
13430 {
13431   char buf[MSG_SIZ];
13432   char *bookHit = NULL;
13433
13434     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13435         return;
13436
13437
13438     if (gameMode == PlayFromGameFile ||
13439         gameMode == TwoMachinesPlay  ||
13440         gameMode == Training         ||
13441         gameMode == AnalyzeMode      ||
13442         gameMode == EndOfGame)
13443         EditGameEvent();
13444
13445     if (gameMode == EditPosition)
13446         EditPositionDone(TRUE);
13447
13448     if (WhiteOnMove(currentMove)) {
13449         DisplayError(_("It is not Black's turn"), 0);
13450         return;
13451     }
13452
13453     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13454       ExitAnalyzeMode();
13455
13456     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13457         gameMode == AnalyzeFile)
13458         TruncateGame();
13459
13460     ResurrectChessProgram();    /* in case it isn't running */
13461     gameMode = MachinePlaysBlack;
13462     pausing = FALSE;
13463     ModeHighlight();
13464     SetGameInfo();
13465     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13466     DisplayTitle(buf);
13467     if (first.sendName) {
13468       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13469       SendToProgram(buf, &first);
13470     }
13471     if (first.sendTime) {
13472       if (first.useColors) {
13473         SendToProgram("white\n", &first); /*gnu kludge*/
13474       }
13475       SendTimeRemaining(&first, FALSE);
13476     }
13477     if (first.useColors) {
13478       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13479     }
13480     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13481     SetMachineThinkingEnables();
13482     first.maybeThinking = TRUE;
13483     StartClocks();
13484
13485     if (appData.autoFlipView && flipView) {
13486       flipView = !flipView;
13487       DrawPosition(FALSE, NULL);
13488       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13489     }
13490     if(bookHit) { // [HGM] book: simulate book reply
13491         static char bookMove[MSG_SIZ]; // a bit generous?
13492
13493         programStats.nodes = programStats.depth = programStats.time =
13494         programStats.score = programStats.got_only_move = 0;
13495         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13496
13497         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13498         strcat(bookMove, bookHit);
13499         HandleMachineMove(bookMove, &first);
13500     }
13501 }
13502
13503
13504 void
13505 DisplayTwoMachinesTitle ()
13506 {
13507     char buf[MSG_SIZ];
13508     if (appData.matchGames > 0) {
13509         if(appData.tourneyFile[0]) {
13510           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13511                    gameInfo.white, _("vs."), gameInfo.black,
13512                    nextGame+1, appData.matchGames+1,
13513                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13514         } else 
13515         if (first.twoMachinesColor[0] == 'w') {
13516           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13517                    gameInfo.white, _("vs."),  gameInfo.black,
13518                    first.matchWins, second.matchWins,
13519                    matchGame - 1 - (first.matchWins + second.matchWins));
13520         } else {
13521           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13522                    gameInfo.white, _("vs."), gameInfo.black,
13523                    second.matchWins, first.matchWins,
13524                    matchGame - 1 - (first.matchWins + second.matchWins));
13525         }
13526     } else {
13527       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13528     }
13529     DisplayTitle(buf);
13530 }
13531
13532 void
13533 SettingsMenuIfReady ()
13534 {
13535   if (second.lastPing != second.lastPong) {
13536     DisplayMessage("", _("Waiting for second chess program"));
13537     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13538     return;
13539   }
13540   ThawUI();
13541   DisplayMessage("", "");
13542   SettingsPopUp(&second);
13543 }
13544
13545 int
13546 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13547 {
13548     char buf[MSG_SIZ];
13549     if (cps->pr == NoProc) {
13550         StartChessProgram(cps);
13551         if (cps->protocolVersion == 1) {
13552           retry();
13553         } else {
13554           /* kludge: allow timeout for initial "feature" command */
13555           FreezeUI();
13556           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13557           DisplayMessage("", buf);
13558           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13559         }
13560         return 1;
13561     }
13562     return 0;
13563 }
13564
13565 void
13566 TwoMachinesEvent P((void))
13567 {
13568     int i;
13569     char buf[MSG_SIZ];
13570     ChessProgramState *onmove;
13571     char *bookHit = NULL;
13572     static int stalling = 0;
13573     TimeMark now;
13574     long wait;
13575
13576     if (appData.noChessProgram) return;
13577
13578     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13579         DisplayError("second engine does not play this", 0);
13580         return;
13581     }
13582
13583     switch (gameMode) {
13584       case TwoMachinesPlay:
13585         return;
13586       case MachinePlaysWhite:
13587       case MachinePlaysBlack:
13588         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13589             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13590             return;
13591         }
13592         /* fall through */
13593       case BeginningOfGame:
13594       case PlayFromGameFile:
13595       case EndOfGame:
13596         EditGameEvent();
13597         if (gameMode != EditGame) return;
13598         break;
13599       case EditPosition:
13600         EditPositionDone(TRUE);
13601         break;
13602       case AnalyzeMode:
13603       case AnalyzeFile:
13604         ExitAnalyzeMode();
13605         break;
13606       case EditGame:
13607       default:
13608         break;
13609     }
13610
13611 //    forwardMostMove = currentMove;
13612     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13613
13614     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13615
13616     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13617     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13618       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13619       return;
13620     }
13621     if(!stalling) {
13622       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13623       SendToProgram("force\n", &second);
13624       stalling = 1;
13625       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13626       return;
13627     }
13628     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13629     if(appData.matchPause>10000 || appData.matchPause<10)
13630                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13631     wait = SubtractTimeMarks(&now, &pauseStart);
13632     if(wait < appData.matchPause) {
13633         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13634         return;
13635     }
13636     // we are now committed to starting the game
13637     stalling = 0;
13638     DisplayMessage("", "");
13639     if (startedFromSetupPosition) {
13640         SendBoard(&second, backwardMostMove);
13641     if (appData.debugMode) {
13642         fprintf(debugFP, "Two Machines\n");
13643     }
13644     }
13645     for (i = backwardMostMove; i < forwardMostMove; i++) {
13646         SendMoveToProgram(i, &second);
13647     }
13648
13649     gameMode = TwoMachinesPlay;
13650     pausing = FALSE;
13651     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13652     SetGameInfo();
13653     DisplayTwoMachinesTitle();
13654     firstMove = TRUE;
13655     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13656         onmove = &first;
13657     } else {
13658         onmove = &second;
13659     }
13660     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13661     SendToProgram(first.computerString, &first);
13662     if (first.sendName) {
13663       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13664       SendToProgram(buf, &first);
13665     }
13666     SendToProgram(second.computerString, &second);
13667     if (second.sendName) {
13668       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13669       SendToProgram(buf, &second);
13670     }
13671
13672     ResetClocks();
13673     if (!first.sendTime || !second.sendTime) {
13674         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13675         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13676     }
13677     if (onmove->sendTime) {
13678       if (onmove->useColors) {
13679         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13680       }
13681       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13682     }
13683     if (onmove->useColors) {
13684       SendToProgram(onmove->twoMachinesColor, onmove);
13685     }
13686     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13687 //    SendToProgram("go\n", onmove);
13688     onmove->maybeThinking = TRUE;
13689     SetMachineThinkingEnables();
13690
13691     StartClocks();
13692
13693     if(bookHit) { // [HGM] book: simulate book reply
13694         static char bookMove[MSG_SIZ]; // a bit generous?
13695
13696         programStats.nodes = programStats.depth = programStats.time =
13697         programStats.score = programStats.got_only_move = 0;
13698         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13699
13700         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13701         strcat(bookMove, bookHit);
13702         savedMessage = bookMove; // args for deferred call
13703         savedState = onmove;
13704         ScheduleDelayedEvent(DeferredBookMove, 1);
13705     }
13706 }
13707
13708 void
13709 TrainingEvent ()
13710 {
13711     if (gameMode == Training) {
13712       SetTrainingModeOff();
13713       gameMode = PlayFromGameFile;
13714       DisplayMessage("", _("Training mode off"));
13715     } else {
13716       gameMode = Training;
13717       animateTraining = appData.animate;
13718
13719       /* make sure we are not already at the end of the game */
13720       if (currentMove < forwardMostMove) {
13721         SetTrainingModeOn();
13722         DisplayMessage("", _("Training mode on"));
13723       } else {
13724         gameMode = PlayFromGameFile;
13725         DisplayError(_("Already at end of game"), 0);
13726       }
13727     }
13728     ModeHighlight();
13729 }
13730
13731 void
13732 IcsClientEvent ()
13733 {
13734     if (!appData.icsActive) return;
13735     switch (gameMode) {
13736       case IcsPlayingWhite:
13737       case IcsPlayingBlack:
13738       case IcsObserving:
13739       case IcsIdle:
13740       case BeginningOfGame:
13741       case IcsExamining:
13742         return;
13743
13744       case EditGame:
13745         break;
13746
13747       case EditPosition:
13748         EditPositionDone(TRUE);
13749         break;
13750
13751       case AnalyzeMode:
13752       case AnalyzeFile:
13753         ExitAnalyzeMode();
13754         break;
13755
13756       default:
13757         EditGameEvent();
13758         break;
13759     }
13760
13761     gameMode = IcsIdle;
13762     ModeHighlight();
13763     return;
13764 }
13765
13766 void
13767 EditGameEvent ()
13768 {
13769     int i;
13770
13771     switch (gameMode) {
13772       case Training:
13773         SetTrainingModeOff();
13774         break;
13775       case MachinePlaysWhite:
13776       case MachinePlaysBlack:
13777       case BeginningOfGame:
13778         SendToProgram("force\n", &first);
13779         SetUserThinkingEnables();
13780         break;
13781       case PlayFromGameFile:
13782         (void) StopLoadGameTimer();
13783         if (gameFileFP != NULL) {
13784             gameFileFP = NULL;
13785         }
13786         break;
13787       case EditPosition:
13788         EditPositionDone(TRUE);
13789         break;
13790       case AnalyzeMode:
13791       case AnalyzeFile:
13792         ExitAnalyzeMode();
13793         SendToProgram("force\n", &first);
13794         break;
13795       case TwoMachinesPlay:
13796         GameEnds(EndOfFile, NULL, GE_PLAYER);
13797         ResurrectChessProgram();
13798         SetUserThinkingEnables();
13799         break;
13800       case EndOfGame:
13801         ResurrectChessProgram();
13802         break;
13803       case IcsPlayingBlack:
13804       case IcsPlayingWhite:
13805         DisplayError(_("Warning: You are still playing a game"), 0);
13806         break;
13807       case IcsObserving:
13808         DisplayError(_("Warning: You are still observing a game"), 0);
13809         break;
13810       case IcsExamining:
13811         DisplayError(_("Warning: You are still examining a game"), 0);
13812         break;
13813       case IcsIdle:
13814         break;
13815       case EditGame:
13816       default:
13817         return;
13818     }
13819
13820     pausing = FALSE;
13821     StopClocks();
13822     first.offeredDraw = second.offeredDraw = 0;
13823
13824     if (gameMode == PlayFromGameFile) {
13825         whiteTimeRemaining = timeRemaining[0][currentMove];
13826         blackTimeRemaining = timeRemaining[1][currentMove];
13827         DisplayTitle("");
13828     }
13829
13830     if (gameMode == MachinePlaysWhite ||
13831         gameMode == MachinePlaysBlack ||
13832         gameMode == TwoMachinesPlay ||
13833         gameMode == EndOfGame) {
13834         i = forwardMostMove;
13835         while (i > currentMove) {
13836             SendToProgram("undo\n", &first);
13837             i--;
13838         }
13839         if(!adjustedClock) {
13840         whiteTimeRemaining = timeRemaining[0][currentMove];
13841         blackTimeRemaining = timeRemaining[1][currentMove];
13842         DisplayBothClocks();
13843         }
13844         if (whiteFlag || blackFlag) {
13845             whiteFlag = blackFlag = 0;
13846         }
13847         DisplayTitle("");
13848     }
13849
13850     gameMode = EditGame;
13851     ModeHighlight();
13852     SetGameInfo();
13853 }
13854
13855
13856 void
13857 EditPositionEvent ()
13858 {
13859     if (gameMode == EditPosition) {
13860         EditGameEvent();
13861         return;
13862     }
13863
13864     EditGameEvent();
13865     if (gameMode != EditGame) return;
13866
13867     gameMode = EditPosition;
13868     ModeHighlight();
13869     SetGameInfo();
13870     if (currentMove > 0)
13871       CopyBoard(boards[0], boards[currentMove]);
13872
13873     blackPlaysFirst = !WhiteOnMove(currentMove);
13874     ResetClocks();
13875     currentMove = forwardMostMove = backwardMostMove = 0;
13876     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13877     DisplayMove(-1);
13878     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13879 }
13880
13881 void
13882 ExitAnalyzeMode ()
13883 {
13884     /* [DM] icsEngineAnalyze - possible call from other functions */
13885     if (appData.icsEngineAnalyze) {
13886         appData.icsEngineAnalyze = FALSE;
13887
13888         DisplayMessage("",_("Close ICS engine analyze..."));
13889     }
13890     if (first.analysisSupport && first.analyzing) {
13891       SendToProgram("exit\n", &first);
13892       first.analyzing = FALSE;
13893     }
13894     thinkOutput[0] = NULLCHAR;
13895 }
13896
13897 void
13898 EditPositionDone (Boolean fakeRights)
13899 {
13900     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13901
13902     startedFromSetupPosition = TRUE;
13903     InitChessProgram(&first, FALSE);
13904     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13905       boards[0][EP_STATUS] = EP_NONE;
13906       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13907     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13908         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13909         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13910       } else boards[0][CASTLING][2] = NoRights;
13911     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13912         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13913         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13914       } else boards[0][CASTLING][5] = NoRights;
13915     }
13916     SendToProgram("force\n", &first);
13917     if (blackPlaysFirst) {
13918         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13919         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13920         currentMove = forwardMostMove = backwardMostMove = 1;
13921         CopyBoard(boards[1], boards[0]);
13922     } else {
13923         currentMove = forwardMostMove = backwardMostMove = 0;
13924     }
13925     SendBoard(&first, forwardMostMove);
13926     if (appData.debugMode) {
13927         fprintf(debugFP, "EditPosDone\n");
13928     }
13929     DisplayTitle("");
13930     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13931     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13932     gameMode = EditGame;
13933     ModeHighlight();
13934     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13935     ClearHighlights(); /* [AS] */
13936 }
13937
13938 /* Pause for `ms' milliseconds */
13939 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13940 void
13941 TimeDelay (long ms)
13942 {
13943     TimeMark m1, m2;
13944
13945     GetTimeMark(&m1);
13946     do {
13947         GetTimeMark(&m2);
13948     } while (SubtractTimeMarks(&m2, &m1) < ms);
13949 }
13950
13951 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13952 void
13953 SendMultiLineToICS (char *buf)
13954 {
13955     char temp[MSG_SIZ+1], *p;
13956     int len;
13957
13958     len = strlen(buf);
13959     if (len > MSG_SIZ)
13960       len = MSG_SIZ;
13961
13962     strncpy(temp, buf, len);
13963     temp[len] = 0;
13964
13965     p = temp;
13966     while (*p) {
13967         if (*p == '\n' || *p == '\r')
13968           *p = ' ';
13969         ++p;
13970     }
13971
13972     strcat(temp, "\n");
13973     SendToICS(temp);
13974     SendToPlayer(temp, strlen(temp));
13975 }
13976
13977 void
13978 SetWhiteToPlayEvent ()
13979 {
13980     if (gameMode == EditPosition) {
13981         blackPlaysFirst = FALSE;
13982         DisplayBothClocks();    /* works because currentMove is 0 */
13983     } else if (gameMode == IcsExamining) {
13984         SendToICS(ics_prefix);
13985         SendToICS("tomove white\n");
13986     }
13987 }
13988
13989 void
13990 SetBlackToPlayEvent ()
13991 {
13992     if (gameMode == EditPosition) {
13993         blackPlaysFirst = TRUE;
13994         currentMove = 1;        /* kludge */
13995         DisplayBothClocks();
13996         currentMove = 0;
13997     } else if (gameMode == IcsExamining) {
13998         SendToICS(ics_prefix);
13999         SendToICS("tomove black\n");
14000     }
14001 }
14002
14003 void
14004 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14005 {
14006     char buf[MSG_SIZ];
14007     ChessSquare piece = boards[0][y][x];
14008
14009     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14010
14011     switch (selection) {
14012       case ClearBoard:
14013         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14014             SendToICS(ics_prefix);
14015             SendToICS("bsetup clear\n");
14016         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14017             SendToICS(ics_prefix);
14018             SendToICS("clearboard\n");
14019         } else {
14020             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14021                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14022                 for (y = 0; y < BOARD_HEIGHT; y++) {
14023                     if (gameMode == IcsExamining) {
14024                         if (boards[currentMove][y][x] != EmptySquare) {
14025                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14026                                     AAA + x, ONE + y);
14027                             SendToICS(buf);
14028                         }
14029                     } else {
14030                         boards[0][y][x] = p;
14031                     }
14032                 }
14033             }
14034         }
14035         if (gameMode == EditPosition) {
14036             DrawPosition(FALSE, boards[0]);
14037         }
14038         break;
14039
14040       case WhitePlay:
14041         SetWhiteToPlayEvent();
14042         break;
14043
14044       case BlackPlay:
14045         SetBlackToPlayEvent();
14046         break;
14047
14048       case EmptySquare:
14049         if (gameMode == IcsExamining) {
14050             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14051             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14052             SendToICS(buf);
14053         } else {
14054             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14055                 if(x == BOARD_LEFT-2) {
14056                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14057                     boards[0][y][1] = 0;
14058                 } else
14059                 if(x == BOARD_RGHT+1) {
14060                     if(y >= gameInfo.holdingsSize) break;
14061                     boards[0][y][BOARD_WIDTH-2] = 0;
14062                 } else break;
14063             }
14064             boards[0][y][x] = EmptySquare;
14065             DrawPosition(FALSE, boards[0]);
14066         }
14067         break;
14068
14069       case PromotePiece:
14070         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14071            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14072             selection = (ChessSquare) (PROMOTED piece);
14073         } else if(piece == EmptySquare) selection = WhiteSilver;
14074         else selection = (ChessSquare)((int)piece - 1);
14075         goto defaultlabel;
14076
14077       case DemotePiece:
14078         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14079            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14080             selection = (ChessSquare) (DEMOTED piece);
14081         } else if(piece == EmptySquare) selection = BlackSilver;
14082         else selection = (ChessSquare)((int)piece + 1);
14083         goto defaultlabel;
14084
14085       case WhiteQueen:
14086       case BlackQueen:
14087         if(gameInfo.variant == VariantShatranj ||
14088            gameInfo.variant == VariantXiangqi  ||
14089            gameInfo.variant == VariantCourier  ||
14090            gameInfo.variant == VariantMakruk     )
14091             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14092         goto defaultlabel;
14093
14094       case WhiteKing:
14095       case BlackKing:
14096         if(gameInfo.variant == VariantXiangqi)
14097             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14098         if(gameInfo.variant == VariantKnightmate)
14099             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14100       default:
14101         defaultlabel:
14102         if (gameMode == IcsExamining) {
14103             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14104             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14105                      PieceToChar(selection), AAA + x, ONE + y);
14106             SendToICS(buf);
14107         } else {
14108             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14109                 int n;
14110                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14111                     n = PieceToNumber(selection - BlackPawn);
14112                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14113                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14114                     boards[0][BOARD_HEIGHT-1-n][1]++;
14115                 } else
14116                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14117                     n = PieceToNumber(selection);
14118                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14119                     boards[0][n][BOARD_WIDTH-1] = selection;
14120                     boards[0][n][BOARD_WIDTH-2]++;
14121                 }
14122             } else
14123             boards[0][y][x] = selection;
14124             DrawPosition(TRUE, boards[0]);
14125             ClearHighlights();
14126             fromX = fromY = -1;
14127         }
14128         break;
14129     }
14130 }
14131
14132
14133 void
14134 DropMenuEvent (ChessSquare selection, int x, int y)
14135 {
14136     ChessMove moveType;
14137
14138     switch (gameMode) {
14139       case IcsPlayingWhite:
14140       case MachinePlaysBlack:
14141         if (!WhiteOnMove(currentMove)) {
14142             DisplayMoveError(_("It is Black's turn"));
14143             return;
14144         }
14145         moveType = WhiteDrop;
14146         break;
14147       case IcsPlayingBlack:
14148       case MachinePlaysWhite:
14149         if (WhiteOnMove(currentMove)) {
14150             DisplayMoveError(_("It is White's turn"));
14151             return;
14152         }
14153         moveType = BlackDrop;
14154         break;
14155       case EditGame:
14156         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14157         break;
14158       default:
14159         return;
14160     }
14161
14162     if (moveType == BlackDrop && selection < BlackPawn) {
14163       selection = (ChessSquare) ((int) selection
14164                                  + (int) BlackPawn - (int) WhitePawn);
14165     }
14166     if (boards[currentMove][y][x] != EmptySquare) {
14167         DisplayMoveError(_("That square is occupied"));
14168         return;
14169     }
14170
14171     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14172 }
14173
14174 void
14175 AcceptEvent ()
14176 {
14177     /* Accept a pending offer of any kind from opponent */
14178
14179     if (appData.icsActive) {
14180         SendToICS(ics_prefix);
14181         SendToICS("accept\n");
14182     } else if (cmailMsgLoaded) {
14183         if (currentMove == cmailOldMove &&
14184             commentList[cmailOldMove] != NULL &&
14185             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14186                    "Black offers a draw" : "White offers a draw")) {
14187             TruncateGame();
14188             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14189             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14190         } else {
14191             DisplayError(_("There is no pending offer on this move"), 0);
14192             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14193         }
14194     } else {
14195         /* Not used for offers from chess program */
14196     }
14197 }
14198
14199 void
14200 DeclineEvent ()
14201 {
14202     /* Decline a pending offer of any kind from opponent */
14203
14204     if (appData.icsActive) {
14205         SendToICS(ics_prefix);
14206         SendToICS("decline\n");
14207     } else if (cmailMsgLoaded) {
14208         if (currentMove == cmailOldMove &&
14209             commentList[cmailOldMove] != NULL &&
14210             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14211                    "Black offers a draw" : "White offers a draw")) {
14212 #ifdef NOTDEF
14213             AppendComment(cmailOldMove, "Draw declined", TRUE);
14214             DisplayComment(cmailOldMove - 1, "Draw declined");
14215 #endif /*NOTDEF*/
14216         } else {
14217             DisplayError(_("There is no pending offer on this move"), 0);
14218         }
14219     } else {
14220         /* Not used for offers from chess program */
14221     }
14222 }
14223
14224 void
14225 RematchEvent ()
14226 {
14227     /* Issue ICS rematch command */
14228     if (appData.icsActive) {
14229         SendToICS(ics_prefix);
14230         SendToICS("rematch\n");
14231     }
14232 }
14233
14234 void
14235 CallFlagEvent ()
14236 {
14237     /* Call your opponent's flag (claim a win on time) */
14238     if (appData.icsActive) {
14239         SendToICS(ics_prefix);
14240         SendToICS("flag\n");
14241     } else {
14242         switch (gameMode) {
14243           default:
14244             return;
14245           case MachinePlaysWhite:
14246             if (whiteFlag) {
14247                 if (blackFlag)
14248                   GameEnds(GameIsDrawn, "Both players ran out of time",
14249                            GE_PLAYER);
14250                 else
14251                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14252             } else {
14253                 DisplayError(_("Your opponent is not out of time"), 0);
14254             }
14255             break;
14256           case MachinePlaysBlack:
14257             if (blackFlag) {
14258                 if (whiteFlag)
14259                   GameEnds(GameIsDrawn, "Both players ran out of time",
14260                            GE_PLAYER);
14261                 else
14262                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14263             } else {
14264                 DisplayError(_("Your opponent is not out of time"), 0);
14265             }
14266             break;
14267         }
14268     }
14269 }
14270
14271 void
14272 ClockClick (int which)
14273 {       // [HGM] code moved to back-end from winboard.c
14274         if(which) { // black clock
14275           if (gameMode == EditPosition || gameMode == IcsExamining) {
14276             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14277             SetBlackToPlayEvent();
14278           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14279           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14280           } else if (shiftKey) {
14281             AdjustClock(which, -1);
14282           } else if (gameMode == IcsPlayingWhite ||
14283                      gameMode == MachinePlaysBlack) {
14284             CallFlagEvent();
14285           }
14286         } else { // white clock
14287           if (gameMode == EditPosition || gameMode == IcsExamining) {
14288             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14289             SetWhiteToPlayEvent();
14290           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14291           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14292           } else if (shiftKey) {
14293             AdjustClock(which, -1);
14294           } else if (gameMode == IcsPlayingBlack ||
14295                    gameMode == MachinePlaysWhite) {
14296             CallFlagEvent();
14297           }
14298         }
14299 }
14300
14301 void
14302 DrawEvent ()
14303 {
14304     /* Offer draw or accept pending draw offer from opponent */
14305
14306     if (appData.icsActive) {
14307         /* Note: tournament rules require draw offers to be
14308            made after you make your move but before you punch
14309            your clock.  Currently ICS doesn't let you do that;
14310            instead, you immediately punch your clock after making
14311            a move, but you can offer a draw at any time. */
14312
14313         SendToICS(ics_prefix);
14314         SendToICS("draw\n");
14315         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14316     } else if (cmailMsgLoaded) {
14317         if (currentMove == cmailOldMove &&
14318             commentList[cmailOldMove] != NULL &&
14319             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14320                    "Black offers a draw" : "White offers a draw")) {
14321             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14322             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14323         } else if (currentMove == cmailOldMove + 1) {
14324             char *offer = WhiteOnMove(cmailOldMove) ?
14325               "White offers a draw" : "Black offers a draw";
14326             AppendComment(currentMove, offer, TRUE);
14327             DisplayComment(currentMove - 1, offer);
14328             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14329         } else {
14330             DisplayError(_("You must make your move before offering a draw"), 0);
14331             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14332         }
14333     } else if (first.offeredDraw) {
14334         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14335     } else {
14336         if (first.sendDrawOffers) {
14337             SendToProgram("draw\n", &first);
14338             userOfferedDraw = TRUE;
14339         }
14340     }
14341 }
14342
14343 void
14344 AdjournEvent ()
14345 {
14346     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14347
14348     if (appData.icsActive) {
14349         SendToICS(ics_prefix);
14350         SendToICS("adjourn\n");
14351     } else {
14352         /* Currently GNU Chess doesn't offer or accept Adjourns */
14353     }
14354 }
14355
14356
14357 void
14358 AbortEvent ()
14359 {
14360     /* Offer Abort or accept pending Abort offer from opponent */
14361
14362     if (appData.icsActive) {
14363         SendToICS(ics_prefix);
14364         SendToICS("abort\n");
14365     } else {
14366         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14367     }
14368 }
14369
14370 void
14371 ResignEvent ()
14372 {
14373     /* Resign.  You can do this even if it's not your turn. */
14374
14375     if (appData.icsActive) {
14376         SendToICS(ics_prefix);
14377         SendToICS("resign\n");
14378     } else {
14379         switch (gameMode) {
14380           case MachinePlaysWhite:
14381             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14382             break;
14383           case MachinePlaysBlack:
14384             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14385             break;
14386           case EditGame:
14387             if (cmailMsgLoaded) {
14388                 TruncateGame();
14389                 if (WhiteOnMove(cmailOldMove)) {
14390                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14391                 } else {
14392                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14393                 }
14394                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14395             }
14396             break;
14397           default:
14398             break;
14399         }
14400     }
14401 }
14402
14403
14404 void
14405 StopObservingEvent ()
14406 {
14407     /* Stop observing current games */
14408     SendToICS(ics_prefix);
14409     SendToICS("unobserve\n");
14410 }
14411
14412 void
14413 StopExaminingEvent ()
14414 {
14415     /* Stop observing current game */
14416     SendToICS(ics_prefix);
14417     SendToICS("unexamine\n");
14418 }
14419
14420 void
14421 ForwardInner (int target)
14422 {
14423     int limit; int oldSeekGraphUp = seekGraphUp;
14424
14425     if (appData.debugMode)
14426         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14427                 target, currentMove, forwardMostMove);
14428
14429     if (gameMode == EditPosition)
14430       return;
14431
14432     seekGraphUp = FALSE;
14433     MarkTargetSquares(1);
14434
14435     if (gameMode == PlayFromGameFile && !pausing)
14436       PauseEvent();
14437
14438     if (gameMode == IcsExamining && pausing)
14439       limit = pauseExamForwardMostMove;
14440     else
14441       limit = forwardMostMove;
14442
14443     if (target > limit) target = limit;
14444
14445     if (target > 0 && moveList[target - 1][0]) {
14446         int fromX, fromY, toX, toY;
14447         toX = moveList[target - 1][2] - AAA;
14448         toY = moveList[target - 1][3] - ONE;
14449         if (moveList[target - 1][1] == '@') {
14450             if (appData.highlightLastMove) {
14451                 SetHighlights(-1, -1, toX, toY);
14452             }
14453         } else {
14454             fromX = moveList[target - 1][0] - AAA;
14455             fromY = moveList[target - 1][1] - ONE;
14456             if (target == currentMove + 1) {
14457                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14458             }
14459             if (appData.highlightLastMove) {
14460                 SetHighlights(fromX, fromY, toX, toY);
14461             }
14462         }
14463     }
14464     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14465         gameMode == Training || gameMode == PlayFromGameFile ||
14466         gameMode == AnalyzeFile) {
14467         while (currentMove < target) {
14468             SendMoveToProgram(currentMove++, &first);
14469         }
14470     } else {
14471         currentMove = target;
14472     }
14473
14474     if (gameMode == EditGame || gameMode == EndOfGame) {
14475         whiteTimeRemaining = timeRemaining[0][currentMove];
14476         blackTimeRemaining = timeRemaining[1][currentMove];
14477     }
14478     DisplayBothClocks();
14479     DisplayMove(currentMove - 1);
14480     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14481     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14482     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14483         DisplayComment(currentMove - 1, commentList[currentMove]);
14484     }
14485     ClearMap(); // [HGM] exclude: invalidate map
14486 }
14487
14488
14489 void
14490 ForwardEvent ()
14491 {
14492     if (gameMode == IcsExamining && !pausing) {
14493         SendToICS(ics_prefix);
14494         SendToICS("forward\n");
14495     } else {
14496         ForwardInner(currentMove + 1);
14497     }
14498 }
14499
14500 void
14501 ToEndEvent ()
14502 {
14503     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14504         /* to optimze, we temporarily turn off analysis mode while we feed
14505          * the remaining moves to the engine. Otherwise we get analysis output
14506          * after each move.
14507          */
14508         if (first.analysisSupport) {
14509           SendToProgram("exit\nforce\n", &first);
14510           first.analyzing = FALSE;
14511         }
14512     }
14513
14514     if (gameMode == IcsExamining && !pausing) {
14515         SendToICS(ics_prefix);
14516         SendToICS("forward 999999\n");
14517     } else {
14518         ForwardInner(forwardMostMove);
14519     }
14520
14521     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14522         /* we have fed all the moves, so reactivate analysis mode */
14523         SendToProgram("analyze\n", &first);
14524         first.analyzing = TRUE;
14525         /*first.maybeThinking = TRUE;*/
14526         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14527     }
14528 }
14529
14530 void
14531 BackwardInner (int target)
14532 {
14533     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14534
14535     if (appData.debugMode)
14536         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14537                 target, currentMove, forwardMostMove);
14538
14539     if (gameMode == EditPosition) return;
14540     seekGraphUp = FALSE;
14541     MarkTargetSquares(1);
14542     if (currentMove <= backwardMostMove) {
14543         ClearHighlights();
14544         DrawPosition(full_redraw, boards[currentMove]);
14545         return;
14546     }
14547     if (gameMode == PlayFromGameFile && !pausing)
14548       PauseEvent();
14549
14550     if (moveList[target][0]) {
14551         int fromX, fromY, toX, toY;
14552         toX = moveList[target][2] - AAA;
14553         toY = moveList[target][3] - ONE;
14554         if (moveList[target][1] == '@') {
14555             if (appData.highlightLastMove) {
14556                 SetHighlights(-1, -1, toX, toY);
14557             }
14558         } else {
14559             fromX = moveList[target][0] - AAA;
14560             fromY = moveList[target][1] - ONE;
14561             if (target == currentMove - 1) {
14562                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14563             }
14564             if (appData.highlightLastMove) {
14565                 SetHighlights(fromX, fromY, toX, toY);
14566             }
14567         }
14568     }
14569     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14570         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14571         while (currentMove > target) {
14572             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14573                 // null move cannot be undone. Reload program with move history before it.
14574                 int i;
14575                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14576                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14577                 }
14578                 SendBoard(&first, i); 
14579                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14580                 break;
14581             }
14582             SendToProgram("undo\n", &first);
14583             currentMove--;
14584         }
14585     } else {
14586         currentMove = target;
14587     }
14588
14589     if (gameMode == EditGame || gameMode == EndOfGame) {
14590         whiteTimeRemaining = timeRemaining[0][currentMove];
14591         blackTimeRemaining = timeRemaining[1][currentMove];
14592     }
14593     DisplayBothClocks();
14594     DisplayMove(currentMove - 1);
14595     DrawPosition(full_redraw, boards[currentMove]);
14596     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14597     // [HGM] PV info: routine tests if comment empty
14598     DisplayComment(currentMove - 1, commentList[currentMove]);
14599     ClearMap(); // [HGM] exclude: invalidate map
14600 }
14601
14602 void
14603 BackwardEvent ()
14604 {
14605     if (gameMode == IcsExamining && !pausing) {
14606         SendToICS(ics_prefix);
14607         SendToICS("backward\n");
14608     } else {
14609         BackwardInner(currentMove - 1);
14610     }
14611 }
14612
14613 void
14614 ToStartEvent ()
14615 {
14616     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14617         /* to optimize, we temporarily turn off analysis mode while we undo
14618          * all the moves. Otherwise we get analysis output after each undo.
14619          */
14620         if (first.analysisSupport) {
14621           SendToProgram("exit\nforce\n", &first);
14622           first.analyzing = FALSE;
14623         }
14624     }
14625
14626     if (gameMode == IcsExamining && !pausing) {
14627         SendToICS(ics_prefix);
14628         SendToICS("backward 999999\n");
14629     } else {
14630         BackwardInner(backwardMostMove);
14631     }
14632
14633     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14634         /* we have fed all the moves, so reactivate analysis mode */
14635         SendToProgram("analyze\n", &first);
14636         first.analyzing = TRUE;
14637         /*first.maybeThinking = TRUE;*/
14638         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14639     }
14640 }
14641
14642 void
14643 ToNrEvent (int to)
14644 {
14645   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14646   if (to >= forwardMostMove) to = forwardMostMove;
14647   if (to <= backwardMostMove) to = backwardMostMove;
14648   if (to < currentMove) {
14649     BackwardInner(to);
14650   } else {
14651     ForwardInner(to);
14652   }
14653 }
14654
14655 void
14656 RevertEvent (Boolean annotate)
14657 {
14658     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14659         return;
14660     }
14661     if (gameMode != IcsExamining) {
14662         DisplayError(_("You are not examining a game"), 0);
14663         return;
14664     }
14665     if (pausing) {
14666         DisplayError(_("You can't revert while pausing"), 0);
14667         return;
14668     }
14669     SendToICS(ics_prefix);
14670     SendToICS("revert\n");
14671 }
14672
14673 void
14674 RetractMoveEvent ()
14675 {
14676     switch (gameMode) {
14677       case MachinePlaysWhite:
14678       case MachinePlaysBlack:
14679         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14680             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14681             return;
14682         }
14683         if (forwardMostMove < 2) return;
14684         currentMove = forwardMostMove = forwardMostMove - 2;
14685         whiteTimeRemaining = timeRemaining[0][currentMove];
14686         blackTimeRemaining = timeRemaining[1][currentMove];
14687         DisplayBothClocks();
14688         DisplayMove(currentMove - 1);
14689         ClearHighlights();/*!! could figure this out*/
14690         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14691         SendToProgram("remove\n", &first);
14692         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14693         break;
14694
14695       case BeginningOfGame:
14696       default:
14697         break;
14698
14699       case IcsPlayingWhite:
14700       case IcsPlayingBlack:
14701         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14702             SendToICS(ics_prefix);
14703             SendToICS("takeback 2\n");
14704         } else {
14705             SendToICS(ics_prefix);
14706             SendToICS("takeback 1\n");
14707         }
14708         break;
14709     }
14710 }
14711
14712 void
14713 MoveNowEvent ()
14714 {
14715     ChessProgramState *cps;
14716
14717     switch (gameMode) {
14718       case MachinePlaysWhite:
14719         if (!WhiteOnMove(forwardMostMove)) {
14720             DisplayError(_("It is your turn"), 0);
14721             return;
14722         }
14723         cps = &first;
14724         break;
14725       case MachinePlaysBlack:
14726         if (WhiteOnMove(forwardMostMove)) {
14727             DisplayError(_("It is your turn"), 0);
14728             return;
14729         }
14730         cps = &first;
14731         break;
14732       case TwoMachinesPlay:
14733         if (WhiteOnMove(forwardMostMove) ==
14734             (first.twoMachinesColor[0] == 'w')) {
14735             cps = &first;
14736         } else {
14737             cps = &second;
14738         }
14739         break;
14740       case BeginningOfGame:
14741       default:
14742         return;
14743     }
14744     SendToProgram("?\n", cps);
14745 }
14746
14747 void
14748 TruncateGameEvent ()
14749 {
14750     EditGameEvent();
14751     if (gameMode != EditGame) return;
14752     TruncateGame();
14753 }
14754
14755 void
14756 TruncateGame ()
14757 {
14758     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14759     if (forwardMostMove > currentMove) {
14760         if (gameInfo.resultDetails != NULL) {
14761             free(gameInfo.resultDetails);
14762             gameInfo.resultDetails = NULL;
14763             gameInfo.result = GameUnfinished;
14764         }
14765         forwardMostMove = currentMove;
14766         HistorySet(parseList, backwardMostMove, forwardMostMove,
14767                    currentMove-1);
14768     }
14769 }
14770
14771 void
14772 HintEvent ()
14773 {
14774     if (appData.noChessProgram) return;
14775     switch (gameMode) {
14776       case MachinePlaysWhite:
14777         if (WhiteOnMove(forwardMostMove)) {
14778             DisplayError(_("Wait until your turn"), 0);
14779             return;
14780         }
14781         break;
14782       case BeginningOfGame:
14783       case MachinePlaysBlack:
14784         if (!WhiteOnMove(forwardMostMove)) {
14785             DisplayError(_("Wait until your turn"), 0);
14786             return;
14787         }
14788         break;
14789       default:
14790         DisplayError(_("No hint available"), 0);
14791         return;
14792     }
14793     SendToProgram("hint\n", &first);
14794     hintRequested = TRUE;
14795 }
14796
14797 void
14798 BookEvent ()
14799 {
14800     if (appData.noChessProgram) return;
14801     switch (gameMode) {
14802       case MachinePlaysWhite:
14803         if (WhiteOnMove(forwardMostMove)) {
14804             DisplayError(_("Wait until your turn"), 0);
14805             return;
14806         }
14807         break;
14808       case BeginningOfGame:
14809       case MachinePlaysBlack:
14810         if (!WhiteOnMove(forwardMostMove)) {
14811             DisplayError(_("Wait until your turn"), 0);
14812             return;
14813         }
14814         break;
14815       case EditPosition:
14816         EditPositionDone(TRUE);
14817         break;
14818       case TwoMachinesPlay:
14819         return;
14820       default:
14821         break;
14822     }
14823     SendToProgram("bk\n", &first);
14824     bookOutput[0] = NULLCHAR;
14825     bookRequested = TRUE;
14826 }
14827
14828 void
14829 AboutGameEvent ()
14830 {
14831     char *tags = PGNTags(&gameInfo);
14832     TagsPopUp(tags, CmailMsg());
14833     free(tags);
14834 }
14835
14836 /* end button procedures */
14837
14838 void
14839 PrintPosition (FILE *fp, int move)
14840 {
14841     int i, j;
14842
14843     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14844         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14845             char c = PieceToChar(boards[move][i][j]);
14846             fputc(c == 'x' ? '.' : c, fp);
14847             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14848         }
14849     }
14850     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14851       fprintf(fp, "white to play\n");
14852     else
14853       fprintf(fp, "black to play\n");
14854 }
14855
14856 void
14857 PrintOpponents (FILE *fp)
14858 {
14859     if (gameInfo.white != NULL) {
14860         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14861     } else {
14862         fprintf(fp, "\n");
14863     }
14864 }
14865
14866 /* Find last component of program's own name, using some heuristics */
14867 void
14868 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14869 {
14870     char *p, *q, c;
14871     int local = (strcmp(host, "localhost") == 0);
14872     while (!local && (p = strchr(prog, ';')) != NULL) {
14873         p++;
14874         while (*p == ' ') p++;
14875         prog = p;
14876     }
14877     if (*prog == '"' || *prog == '\'') {
14878         q = strchr(prog + 1, *prog);
14879     } else {
14880         q = strchr(prog, ' ');
14881     }
14882     if (q == NULL) q = prog + strlen(prog);
14883     p = q;
14884     while (p >= prog && *p != '/' && *p != '\\') p--;
14885     p++;
14886     if(p == prog && *p == '"') p++;
14887     c = *q; *q = 0;
14888     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14889     memcpy(buf, p, q - p);
14890     buf[q - p] = NULLCHAR;
14891     if (!local) {
14892         strcat(buf, "@");
14893         strcat(buf, host);
14894     }
14895 }
14896
14897 char *
14898 TimeControlTagValue ()
14899 {
14900     char buf[MSG_SIZ];
14901     if (!appData.clockMode) {
14902       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14903     } else if (movesPerSession > 0) {
14904       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14905     } else if (timeIncrement == 0) {
14906       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14907     } else {
14908       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14909     }
14910     return StrSave(buf);
14911 }
14912
14913 void
14914 SetGameInfo ()
14915 {
14916     /* This routine is used only for certain modes */
14917     VariantClass v = gameInfo.variant;
14918     ChessMove r = GameUnfinished;
14919     char *p = NULL;
14920
14921     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14922         r = gameInfo.result;
14923         p = gameInfo.resultDetails;
14924         gameInfo.resultDetails = NULL;
14925     }
14926     ClearGameInfo(&gameInfo);
14927     gameInfo.variant = v;
14928
14929     switch (gameMode) {
14930       case MachinePlaysWhite:
14931         gameInfo.event = StrSave( appData.pgnEventHeader );
14932         gameInfo.site = StrSave(HostName());
14933         gameInfo.date = PGNDate();
14934         gameInfo.round = StrSave("-");
14935         gameInfo.white = StrSave(first.tidy);
14936         gameInfo.black = StrSave(UserName());
14937         gameInfo.timeControl = TimeControlTagValue();
14938         break;
14939
14940       case MachinePlaysBlack:
14941         gameInfo.event = StrSave( appData.pgnEventHeader );
14942         gameInfo.site = StrSave(HostName());
14943         gameInfo.date = PGNDate();
14944         gameInfo.round = StrSave("-");
14945         gameInfo.white = StrSave(UserName());
14946         gameInfo.black = StrSave(first.tidy);
14947         gameInfo.timeControl = TimeControlTagValue();
14948         break;
14949
14950       case TwoMachinesPlay:
14951         gameInfo.event = StrSave( appData.pgnEventHeader );
14952         gameInfo.site = StrSave(HostName());
14953         gameInfo.date = PGNDate();
14954         if (roundNr > 0) {
14955             char buf[MSG_SIZ];
14956             snprintf(buf, MSG_SIZ, "%d", roundNr);
14957             gameInfo.round = StrSave(buf);
14958         } else {
14959             gameInfo.round = StrSave("-");
14960         }
14961         if (first.twoMachinesColor[0] == 'w') {
14962             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14963             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14964         } else {
14965             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14966             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14967         }
14968         gameInfo.timeControl = TimeControlTagValue();
14969         break;
14970
14971       case EditGame:
14972         gameInfo.event = StrSave("Edited game");
14973         gameInfo.site = StrSave(HostName());
14974         gameInfo.date = PGNDate();
14975         gameInfo.round = StrSave("-");
14976         gameInfo.white = StrSave("-");
14977         gameInfo.black = StrSave("-");
14978         gameInfo.result = r;
14979         gameInfo.resultDetails = p;
14980         break;
14981
14982       case EditPosition:
14983         gameInfo.event = StrSave("Edited position");
14984         gameInfo.site = StrSave(HostName());
14985         gameInfo.date = PGNDate();
14986         gameInfo.round = StrSave("-");
14987         gameInfo.white = StrSave("-");
14988         gameInfo.black = StrSave("-");
14989         break;
14990
14991       case IcsPlayingWhite:
14992       case IcsPlayingBlack:
14993       case IcsObserving:
14994       case IcsExamining:
14995         break;
14996
14997       case PlayFromGameFile:
14998         gameInfo.event = StrSave("Game from non-PGN file");
14999         gameInfo.site = StrSave(HostName());
15000         gameInfo.date = PGNDate();
15001         gameInfo.round = StrSave("-");
15002         gameInfo.white = StrSave("?");
15003         gameInfo.black = StrSave("?");
15004         break;
15005
15006       default:
15007         break;
15008     }
15009 }
15010
15011 void
15012 ReplaceComment (int index, char *text)
15013 {
15014     int len;
15015     char *p;
15016     float score;
15017
15018     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15019        pvInfoList[index-1].depth == len &&
15020        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15021        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15022     while (*text == '\n') text++;
15023     len = strlen(text);
15024     while (len > 0 && text[len - 1] == '\n') len--;
15025
15026     if (commentList[index] != NULL)
15027       free(commentList[index]);
15028
15029     if (len == 0) {
15030         commentList[index] = NULL;
15031         return;
15032     }
15033   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15034       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15035       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15036     commentList[index] = (char *) malloc(len + 2);
15037     strncpy(commentList[index], text, len);
15038     commentList[index][len] = '\n';
15039     commentList[index][len + 1] = NULLCHAR;
15040   } else {
15041     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15042     char *p;
15043     commentList[index] = (char *) malloc(len + 7);
15044     safeStrCpy(commentList[index], "{\n", 3);
15045     safeStrCpy(commentList[index]+2, text, len+1);
15046     commentList[index][len+2] = NULLCHAR;
15047     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15048     strcat(commentList[index], "\n}\n");
15049   }
15050 }
15051
15052 void
15053 CrushCRs (char *text)
15054 {
15055   char *p = text;
15056   char *q = text;
15057   char ch;
15058
15059   do {
15060     ch = *p++;
15061     if (ch == '\r') continue;
15062     *q++ = ch;
15063   } while (ch != '\0');
15064 }
15065
15066 void
15067 AppendComment (int index, char *text, Boolean addBraces)
15068 /* addBraces  tells if we should add {} */
15069 {
15070     int oldlen, len;
15071     char *old;
15072
15073 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15074     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15075
15076     CrushCRs(text);
15077     while (*text == '\n') text++;
15078     len = strlen(text);
15079     while (len > 0 && text[len - 1] == '\n') len--;
15080     text[len] = NULLCHAR;
15081
15082     if (len == 0) return;
15083
15084     if (commentList[index] != NULL) {
15085       Boolean addClosingBrace = addBraces;
15086         old = commentList[index];
15087         oldlen = strlen(old);
15088         while(commentList[index][oldlen-1] ==  '\n')
15089           commentList[index][--oldlen] = NULLCHAR;
15090         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15091         safeStrCpy(commentList[index], old, oldlen + len + 6);
15092         free(old);
15093         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15094         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15095           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15096           while (*text == '\n') { text++; len--; }
15097           commentList[index][--oldlen] = NULLCHAR;
15098       }
15099         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15100         else          strcat(commentList[index], "\n");
15101         strcat(commentList[index], text);
15102         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15103         else          strcat(commentList[index], "\n");
15104     } else {
15105         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15106         if(addBraces)
15107           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15108         else commentList[index][0] = NULLCHAR;
15109         strcat(commentList[index], text);
15110         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15111         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15112     }
15113 }
15114
15115 static char *
15116 FindStr (char * text, char * sub_text)
15117 {
15118     char * result = strstr( text, sub_text );
15119
15120     if( result != NULL ) {
15121         result += strlen( sub_text );
15122     }
15123
15124     return result;
15125 }
15126
15127 /* [AS] Try to extract PV info from PGN comment */
15128 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15129 char *
15130 GetInfoFromComment (int index, char * text)
15131 {
15132     char * sep = text, *p;
15133
15134     if( text != NULL && index > 0 ) {
15135         int score = 0;
15136         int depth = 0;
15137         int time = -1, sec = 0, deci;
15138         char * s_eval = FindStr( text, "[%eval " );
15139         char * s_emt = FindStr( text, "[%emt " );
15140
15141         if( s_eval != NULL || s_emt != NULL ) {
15142             /* New style */
15143             char delim;
15144
15145             if( s_eval != NULL ) {
15146                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15147                     return text;
15148                 }
15149
15150                 if( delim != ']' ) {
15151                     return text;
15152                 }
15153             }
15154
15155             if( s_emt != NULL ) {
15156             }
15157                 return text;
15158         }
15159         else {
15160             /* We expect something like: [+|-]nnn.nn/dd */
15161             int score_lo = 0;
15162
15163             if(*text != '{') return text; // [HGM] braces: must be normal comment
15164
15165             sep = strchr( text, '/' );
15166             if( sep == NULL || sep < (text+4) ) {
15167                 return text;
15168             }
15169
15170             p = text;
15171             if(p[1] == '(') { // comment starts with PV
15172                p = strchr(p, ')'); // locate end of PV
15173                if(p == NULL || sep < p+5) return text;
15174                // at this point we have something like "{(.*) +0.23/6 ..."
15175                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15176                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15177                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15178             }
15179             time = -1; sec = -1; deci = -1;
15180             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15181                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15182                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15183                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15184                 return text;
15185             }
15186
15187             if( score_lo < 0 || score_lo >= 100 ) {
15188                 return text;
15189             }
15190
15191             if(sec >= 0) time = 600*time + 10*sec; else
15192             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15193
15194             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15195
15196             /* [HGM] PV time: now locate end of PV info */
15197             while( *++sep >= '0' && *sep <= '9'); // strip depth
15198             if(time >= 0)
15199             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15200             if(sec >= 0)
15201             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15202             if(deci >= 0)
15203             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15204             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15205         }
15206
15207         if( depth <= 0 ) {
15208             return text;
15209         }
15210
15211         if( time < 0 ) {
15212             time = -1;
15213         }
15214
15215         pvInfoList[index-1].depth = depth;
15216         pvInfoList[index-1].score = score;
15217         pvInfoList[index-1].time  = 10*time; // centi-sec
15218         if(*sep == '}') *sep = 0; else *--sep = '{';
15219         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15220     }
15221     return sep;
15222 }
15223
15224 void
15225 SendToProgram (char *message, ChessProgramState *cps)
15226 {
15227     int count, outCount, error;
15228     char buf[MSG_SIZ];
15229
15230     if (cps->pr == NoProc) return;
15231     Attention(cps);
15232
15233     if (appData.debugMode) {
15234         TimeMark now;
15235         GetTimeMark(&now);
15236         fprintf(debugFP, "%ld >%-6s: %s",
15237                 SubtractTimeMarks(&now, &programStartTime),
15238                 cps->which, message);
15239         if(serverFP)
15240             fprintf(serverFP, "%ld >%-6s: %s",
15241                 SubtractTimeMarks(&now, &programStartTime),
15242                 cps->which, message), fflush(serverFP);
15243     }
15244
15245     count = strlen(message);
15246     outCount = OutputToProcess(cps->pr, message, count, &error);
15247     if (outCount < count && !exiting
15248                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15249       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15250       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15251         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15252             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15253                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15254                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15255                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15256             } else {
15257                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15258                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15259                 gameInfo.result = res;
15260             }
15261             gameInfo.resultDetails = StrSave(buf);
15262         }
15263         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15264         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15265     }
15266 }
15267
15268 void
15269 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15270 {
15271     char *end_str;
15272     char buf[MSG_SIZ];
15273     ChessProgramState *cps = (ChessProgramState *)closure;
15274
15275     if (isr != cps->isr) return; /* Killed intentionally */
15276     if (count <= 0) {
15277         if (count == 0) {
15278             RemoveInputSource(cps->isr);
15279             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15280                     _(cps->which), cps->program);
15281             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15282             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15283                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15284                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15285                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15286                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15287                 } else {
15288                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15289                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15290                     gameInfo.result = res;
15291                 }
15292                 gameInfo.resultDetails = StrSave(buf);
15293             }
15294             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15295             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15296         } else {
15297             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15298                     _(cps->which), cps->program);
15299             RemoveInputSource(cps->isr);
15300
15301             /* [AS] Program is misbehaving badly... kill it */
15302             if( count == -2 ) {
15303                 DestroyChildProcess( cps->pr, 9 );
15304                 cps->pr = NoProc;
15305             }
15306
15307             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15308         }
15309         return;
15310     }
15311
15312     if ((end_str = strchr(message, '\r')) != NULL)
15313       *end_str = NULLCHAR;
15314     if ((end_str = strchr(message, '\n')) != NULL)
15315       *end_str = NULLCHAR;
15316
15317     if (appData.debugMode) {
15318         TimeMark now; int print = 1;
15319         char *quote = ""; char c; int i;
15320
15321         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15322                 char start = message[0];
15323                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15324                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15325                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15326                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15327                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15328                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15329                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15330                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15331                    sscanf(message, "hint: %c", &c)!=1 && 
15332                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15333                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15334                     print = (appData.engineComments >= 2);
15335                 }
15336                 message[0] = start; // restore original message
15337         }
15338         if(print) {
15339                 GetTimeMark(&now);
15340                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15341                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15342                         quote,
15343                         message);
15344                 if(serverFP)
15345                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15346                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15347                         quote,
15348                         message), fflush(serverFP);
15349         }
15350     }
15351
15352     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15353     if (appData.icsEngineAnalyze) {
15354         if (strstr(message, "whisper") != NULL ||
15355              strstr(message, "kibitz") != NULL ||
15356             strstr(message, "tellics") != NULL) return;
15357     }
15358
15359     HandleMachineMove(message, cps);
15360 }
15361
15362
15363 void
15364 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15365 {
15366     char buf[MSG_SIZ];
15367     int seconds;
15368
15369     if( timeControl_2 > 0 ) {
15370         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15371             tc = timeControl_2;
15372         }
15373     }
15374     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15375     inc /= cps->timeOdds;
15376     st  /= cps->timeOdds;
15377
15378     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15379
15380     if (st > 0) {
15381       /* Set exact time per move, normally using st command */
15382       if (cps->stKludge) {
15383         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15384         seconds = st % 60;
15385         if (seconds == 0) {
15386           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15387         } else {
15388           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15389         }
15390       } else {
15391         snprintf(buf, MSG_SIZ, "st %d\n", st);
15392       }
15393     } else {
15394       /* Set conventional or incremental time control, using level command */
15395       if (seconds == 0) {
15396         /* Note old gnuchess bug -- minutes:seconds used to not work.
15397            Fixed in later versions, but still avoid :seconds
15398            when seconds is 0. */
15399         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15400       } else {
15401         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15402                  seconds, inc/1000.);
15403       }
15404     }
15405     SendToProgram(buf, cps);
15406
15407     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15408     /* Orthogonally, limit search to given depth */
15409     if (sd > 0) {
15410       if (cps->sdKludge) {
15411         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15412       } else {
15413         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15414       }
15415       SendToProgram(buf, cps);
15416     }
15417
15418     if(cps->nps >= 0) { /* [HGM] nps */
15419         if(cps->supportsNPS == FALSE)
15420           cps->nps = -1; // don't use if engine explicitly says not supported!
15421         else {
15422           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15423           SendToProgram(buf, cps);
15424         }
15425     }
15426 }
15427
15428 ChessProgramState *
15429 WhitePlayer ()
15430 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15431 {
15432     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15433        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15434         return &second;
15435     return &first;
15436 }
15437
15438 void
15439 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15440 {
15441     char message[MSG_SIZ];
15442     long time, otime;
15443
15444     /* Note: this routine must be called when the clocks are stopped
15445        or when they have *just* been set or switched; otherwise
15446        it will be off by the time since the current tick started.
15447     */
15448     if (machineWhite) {
15449         time = whiteTimeRemaining / 10;
15450         otime = blackTimeRemaining / 10;
15451     } else {
15452         time = blackTimeRemaining / 10;
15453         otime = whiteTimeRemaining / 10;
15454     }
15455     /* [HGM] translate opponent's time by time-odds factor */
15456     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15457
15458     if (time <= 0) time = 1;
15459     if (otime <= 0) otime = 1;
15460
15461     snprintf(message, MSG_SIZ, "time %ld\n", time);
15462     SendToProgram(message, cps);
15463
15464     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15465     SendToProgram(message, cps);
15466 }
15467
15468 int
15469 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15470 {
15471   char buf[MSG_SIZ];
15472   int len = strlen(name);
15473   int val;
15474
15475   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15476     (*p) += len + 1;
15477     sscanf(*p, "%d", &val);
15478     *loc = (val != 0);
15479     while (**p && **p != ' ')
15480       (*p)++;
15481     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15482     SendToProgram(buf, cps);
15483     return TRUE;
15484   }
15485   return FALSE;
15486 }
15487
15488 int
15489 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15490 {
15491   char buf[MSG_SIZ];
15492   int len = strlen(name);
15493   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15494     (*p) += len + 1;
15495     sscanf(*p, "%d", loc);
15496     while (**p && **p != ' ') (*p)++;
15497     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15498     SendToProgram(buf, cps);
15499     return TRUE;
15500   }
15501   return FALSE;
15502 }
15503
15504 int
15505 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15506 {
15507   char buf[MSG_SIZ];
15508   int len = strlen(name);
15509   if (strncmp((*p), name, len) == 0
15510       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15511     (*p) += len + 2;
15512     sscanf(*p, "%[^\"]", loc);
15513     while (**p && **p != '\"') (*p)++;
15514     if (**p == '\"') (*p)++;
15515     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15516     SendToProgram(buf, cps);
15517     return TRUE;
15518   }
15519   return FALSE;
15520 }
15521
15522 int
15523 ParseOption (Option *opt, ChessProgramState *cps)
15524 // [HGM] options: process the string that defines an engine option, and determine
15525 // name, type, default value, and allowed value range
15526 {
15527         char *p, *q, buf[MSG_SIZ];
15528         int n, min = (-1)<<31, max = 1<<31, def;
15529
15530         if(p = strstr(opt->name, " -spin ")) {
15531             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15532             if(max < min) max = min; // enforce consistency
15533             if(def < min) def = min;
15534             if(def > max) def = max;
15535             opt->value = def;
15536             opt->min = min;
15537             opt->max = max;
15538             opt->type = Spin;
15539         } else if((p = strstr(opt->name, " -slider "))) {
15540             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15541             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15542             if(max < min) max = min; // enforce consistency
15543             if(def < min) def = min;
15544             if(def > max) def = max;
15545             opt->value = def;
15546             opt->min = min;
15547             opt->max = max;
15548             opt->type = Spin; // Slider;
15549         } else if((p = strstr(opt->name, " -string "))) {
15550             opt->textValue = p+9;
15551             opt->type = TextBox;
15552         } else if((p = strstr(opt->name, " -file "))) {
15553             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15554             opt->textValue = p+7;
15555             opt->type = FileName; // FileName;
15556         } else if((p = strstr(opt->name, " -path "))) {
15557             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15558             opt->textValue = p+7;
15559             opt->type = PathName; // PathName;
15560         } else if(p = strstr(opt->name, " -check ")) {
15561             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15562             opt->value = (def != 0);
15563             opt->type = CheckBox;
15564         } else if(p = strstr(opt->name, " -combo ")) {
15565             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15566             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15567             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15568             opt->value = n = 0;
15569             while(q = StrStr(q, " /// ")) {
15570                 n++; *q = 0;    // count choices, and null-terminate each of them
15571                 q += 5;
15572                 if(*q == '*') { // remember default, which is marked with * prefix
15573                     q++;
15574                     opt->value = n;
15575                 }
15576                 cps->comboList[cps->comboCnt++] = q;
15577             }
15578             cps->comboList[cps->comboCnt++] = NULL;
15579             opt->max = n + 1;
15580             opt->type = ComboBox;
15581         } else if(p = strstr(opt->name, " -button")) {
15582             opt->type = Button;
15583         } else if(p = strstr(opt->name, " -save")) {
15584             opt->type = SaveButton;
15585         } else return FALSE;
15586         *p = 0; // terminate option name
15587         // now look if the command-line options define a setting for this engine option.
15588         if(cps->optionSettings && cps->optionSettings[0])
15589             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15590         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15591           snprintf(buf, MSG_SIZ, "option %s", p);
15592                 if(p = strstr(buf, ",")) *p = 0;
15593                 if(q = strchr(buf, '=')) switch(opt->type) {
15594                     case ComboBox:
15595                         for(n=0; n<opt->max; n++)
15596                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15597                         break;
15598                     case TextBox:
15599                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15600                         break;
15601                     case Spin:
15602                     case CheckBox:
15603                         opt->value = atoi(q+1);
15604                     default:
15605                         break;
15606                 }
15607                 strcat(buf, "\n");
15608                 SendToProgram(buf, cps);
15609         }
15610         return TRUE;
15611 }
15612
15613 void
15614 FeatureDone (ChessProgramState *cps, int val)
15615 {
15616   DelayedEventCallback cb = GetDelayedEvent();
15617   if ((cb == InitBackEnd3 && cps == &first) ||
15618       (cb == SettingsMenuIfReady && cps == &second) ||
15619       (cb == LoadEngine) ||
15620       (cb == TwoMachinesEventIfReady)) {
15621     CancelDelayedEvent();
15622     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15623   }
15624   cps->initDone = val;
15625 }
15626
15627 /* Parse feature command from engine */
15628 void
15629 ParseFeatures (char *args, ChessProgramState *cps)
15630 {
15631   char *p = args;
15632   char *q;
15633   int val;
15634   char buf[MSG_SIZ];
15635
15636   for (;;) {
15637     while (*p == ' ') p++;
15638     if (*p == NULLCHAR) return;
15639
15640     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15641     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15642     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15643     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15644     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15645     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15646     if (BoolFeature(&p, "reuse", &val, cps)) {
15647       /* Engine can disable reuse, but can't enable it if user said no */
15648       if (!val) cps->reuse = FALSE;
15649       continue;
15650     }
15651     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15652     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15653       if (gameMode == TwoMachinesPlay) {
15654         DisplayTwoMachinesTitle();
15655       } else {
15656         DisplayTitle("");
15657       }
15658       continue;
15659     }
15660     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15661     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15662     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15663     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15664     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15665     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15666     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15667     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15668     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15669     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15670     if (IntFeature(&p, "done", &val, cps)) {
15671       FeatureDone(cps, val);
15672       continue;
15673     }
15674     /* Added by Tord: */
15675     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15676     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15677     /* End of additions by Tord */
15678
15679     /* [HGM] added features: */
15680     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15681     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15682     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15683     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15684     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15685     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15686     if (StringFeature(&p, "option", buf, cps)) {
15687         FREE(cps->option[cps->nrOptions].name);
15688         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15689         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15690         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15691           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15692             SendToProgram(buf, cps);
15693             continue;
15694         }
15695         if(cps->nrOptions >= MAX_OPTIONS) {
15696             cps->nrOptions--;
15697             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15698             DisplayError(buf, 0);
15699         }
15700         continue;
15701     }
15702     /* End of additions by HGM */
15703
15704     /* unknown feature: complain and skip */
15705     q = p;
15706     while (*q && *q != '=') q++;
15707     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15708     SendToProgram(buf, cps);
15709     p = q;
15710     if (*p == '=') {
15711       p++;
15712       if (*p == '\"') {
15713         p++;
15714         while (*p && *p != '\"') p++;
15715         if (*p == '\"') p++;
15716       } else {
15717         while (*p && *p != ' ') p++;
15718       }
15719     }
15720   }
15721
15722 }
15723
15724 void
15725 PeriodicUpdatesEvent (int newState)
15726 {
15727     if (newState == appData.periodicUpdates)
15728       return;
15729
15730     appData.periodicUpdates=newState;
15731
15732     /* Display type changes, so update it now */
15733 //    DisplayAnalysis();
15734
15735     /* Get the ball rolling again... */
15736     if (newState) {
15737         AnalysisPeriodicEvent(1);
15738         StartAnalysisClock();
15739     }
15740 }
15741
15742 void
15743 PonderNextMoveEvent (int newState)
15744 {
15745     if (newState == appData.ponderNextMove) return;
15746     if (gameMode == EditPosition) EditPositionDone(TRUE);
15747     if (newState) {
15748         SendToProgram("hard\n", &first);
15749         if (gameMode == TwoMachinesPlay) {
15750             SendToProgram("hard\n", &second);
15751         }
15752     } else {
15753         SendToProgram("easy\n", &first);
15754         thinkOutput[0] = NULLCHAR;
15755         if (gameMode == TwoMachinesPlay) {
15756             SendToProgram("easy\n", &second);
15757         }
15758     }
15759     appData.ponderNextMove = newState;
15760 }
15761
15762 void
15763 NewSettingEvent (int option, int *feature, char *command, int value)
15764 {
15765     char buf[MSG_SIZ];
15766
15767     if (gameMode == EditPosition) EditPositionDone(TRUE);
15768     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15769     if(feature == NULL || *feature) SendToProgram(buf, &first);
15770     if (gameMode == TwoMachinesPlay) {
15771         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15772     }
15773 }
15774
15775 void
15776 ShowThinkingEvent ()
15777 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15778 {
15779     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15780     int newState = appData.showThinking
15781         // [HGM] thinking: other features now need thinking output as well
15782         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15783
15784     if (oldState == newState) return;
15785     oldState = newState;
15786     if (gameMode == EditPosition) EditPositionDone(TRUE);
15787     if (oldState) {
15788         SendToProgram("post\n", &first);
15789         if (gameMode == TwoMachinesPlay) {
15790             SendToProgram("post\n", &second);
15791         }
15792     } else {
15793         SendToProgram("nopost\n", &first);
15794         thinkOutput[0] = NULLCHAR;
15795         if (gameMode == TwoMachinesPlay) {
15796             SendToProgram("nopost\n", &second);
15797         }
15798     }
15799 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15800 }
15801
15802 void
15803 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15804 {
15805   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15806   if (pr == NoProc) return;
15807   AskQuestion(title, question, replyPrefix, pr);
15808 }
15809
15810 void
15811 TypeInEvent (char firstChar)
15812 {
15813     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15814         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15815         gameMode == AnalyzeMode || gameMode == EditGame || 
15816         gameMode == EditPosition || gameMode == IcsExamining ||
15817         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15818         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15819                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15820                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15821         gameMode == Training) PopUpMoveDialog(firstChar);
15822 }
15823
15824 void
15825 TypeInDoneEvent (char *move)
15826 {
15827         Board board;
15828         int n, fromX, fromY, toX, toY;
15829         char promoChar;
15830         ChessMove moveType;
15831
15832         // [HGM] FENedit
15833         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15834                 EditPositionPasteFEN(move);
15835                 return;
15836         }
15837         // [HGM] movenum: allow move number to be typed in any mode
15838         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15839           ToNrEvent(2*n-1);
15840           return;
15841         }
15842         // undocumented kludge: allow command-line option to be typed in!
15843         // (potentially fatal, and does not implement the effect of the option.)
15844         // should only be used for options that are values on which future decisions will be made,
15845         // and definitely not on options that would be used during initialization.
15846         if(strstr(move, "!!! -") == move) {
15847             ParseArgsFromString(move+4);
15848             return;
15849         }
15850
15851       if (gameMode != EditGame && currentMove != forwardMostMove && 
15852         gameMode != Training) {
15853         DisplayMoveError(_("Displayed move is not current"));
15854       } else {
15855         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15856           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15857         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15858         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15859           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15860           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15861         } else {
15862           DisplayMoveError(_("Could not parse move"));
15863         }
15864       }
15865 }
15866
15867 void
15868 DisplayMove (int moveNumber)
15869 {
15870     char message[MSG_SIZ];
15871     char res[MSG_SIZ];
15872     char cpThinkOutput[MSG_SIZ];
15873
15874     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15875
15876     if (moveNumber == forwardMostMove - 1 ||
15877         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15878
15879         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15880
15881         if (strchr(cpThinkOutput, '\n')) {
15882             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15883         }
15884     } else {
15885         *cpThinkOutput = NULLCHAR;
15886     }
15887
15888     /* [AS] Hide thinking from human user */
15889     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15890         *cpThinkOutput = NULLCHAR;
15891         if( thinkOutput[0] != NULLCHAR ) {
15892             int i;
15893
15894             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15895                 cpThinkOutput[i] = '.';
15896             }
15897             cpThinkOutput[i] = NULLCHAR;
15898             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15899         }
15900     }
15901
15902     if (moveNumber == forwardMostMove - 1 &&
15903         gameInfo.resultDetails != NULL) {
15904         if (gameInfo.resultDetails[0] == NULLCHAR) {
15905           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15906         } else {
15907           snprintf(res, MSG_SIZ, " {%s} %s",
15908                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15909         }
15910     } else {
15911         res[0] = NULLCHAR;
15912     }
15913
15914     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15915         DisplayMessage(res, cpThinkOutput);
15916     } else {
15917       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15918                 WhiteOnMove(moveNumber) ? " " : ".. ",
15919                 parseList[moveNumber], res);
15920         DisplayMessage(message, cpThinkOutput);
15921     }
15922 }
15923
15924 void
15925 DisplayComment (int moveNumber, char *text)
15926 {
15927     char title[MSG_SIZ];
15928
15929     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15930       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15931     } else {
15932       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15933               WhiteOnMove(moveNumber) ? " " : ".. ",
15934               parseList[moveNumber]);
15935     }
15936     if (text != NULL && (appData.autoDisplayComment || commentUp))
15937         CommentPopUp(title, text);
15938 }
15939
15940 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15941  * might be busy thinking or pondering.  It can be omitted if your
15942  * gnuchess is configured to stop thinking immediately on any user
15943  * input.  However, that gnuchess feature depends on the FIONREAD
15944  * ioctl, which does not work properly on some flavors of Unix.
15945  */
15946 void
15947 Attention (ChessProgramState *cps)
15948 {
15949 #if ATTENTION
15950     if (!cps->useSigint) return;
15951     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15952     switch (gameMode) {
15953       case MachinePlaysWhite:
15954       case MachinePlaysBlack:
15955       case TwoMachinesPlay:
15956       case IcsPlayingWhite:
15957       case IcsPlayingBlack:
15958       case AnalyzeMode:
15959       case AnalyzeFile:
15960         /* Skip if we know it isn't thinking */
15961         if (!cps->maybeThinking) return;
15962         if (appData.debugMode)
15963           fprintf(debugFP, "Interrupting %s\n", cps->which);
15964         InterruptChildProcess(cps->pr);
15965         cps->maybeThinking = FALSE;
15966         break;
15967       default:
15968         break;
15969     }
15970 #endif /*ATTENTION*/
15971 }
15972
15973 int
15974 CheckFlags ()
15975 {
15976     if (whiteTimeRemaining <= 0) {
15977         if (!whiteFlag) {
15978             whiteFlag = TRUE;
15979             if (appData.icsActive) {
15980                 if (appData.autoCallFlag &&
15981                     gameMode == IcsPlayingBlack && !blackFlag) {
15982                   SendToICS(ics_prefix);
15983                   SendToICS("flag\n");
15984                 }
15985             } else {
15986                 if (blackFlag) {
15987                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15988                 } else {
15989                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15990                     if (appData.autoCallFlag) {
15991                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15992                         return TRUE;
15993                     }
15994                 }
15995             }
15996         }
15997     }
15998     if (blackTimeRemaining <= 0) {
15999         if (!blackFlag) {
16000             blackFlag = TRUE;
16001             if (appData.icsActive) {
16002                 if (appData.autoCallFlag &&
16003                     gameMode == IcsPlayingWhite && !whiteFlag) {
16004                   SendToICS(ics_prefix);
16005                   SendToICS("flag\n");
16006                 }
16007             } else {
16008                 if (whiteFlag) {
16009                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16010                 } else {
16011                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16012                     if (appData.autoCallFlag) {
16013                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16014                         return TRUE;
16015                     }
16016                 }
16017             }
16018         }
16019     }
16020     return FALSE;
16021 }
16022
16023 void
16024 CheckTimeControl ()
16025 {
16026     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16027         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16028
16029     /*
16030      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16031      */
16032     if ( !WhiteOnMove(forwardMostMove) ) {
16033         /* White made time control */
16034         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16035         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16036         /* [HGM] time odds: correct new time quota for time odds! */
16037                                             / WhitePlayer()->timeOdds;
16038         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16039     } else {
16040         lastBlack -= blackTimeRemaining;
16041         /* Black made time control */
16042         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16043                                             / WhitePlayer()->other->timeOdds;
16044         lastWhite = whiteTimeRemaining;
16045     }
16046 }
16047
16048 void
16049 DisplayBothClocks ()
16050 {
16051     int wom = gameMode == EditPosition ?
16052       !blackPlaysFirst : WhiteOnMove(currentMove);
16053     DisplayWhiteClock(whiteTimeRemaining, wom);
16054     DisplayBlackClock(blackTimeRemaining, !wom);
16055 }
16056
16057
16058 /* Timekeeping seems to be a portability nightmare.  I think everyone
16059    has ftime(), but I'm really not sure, so I'm including some ifdefs
16060    to use other calls if you don't.  Clocks will be less accurate if
16061    you have neither ftime nor gettimeofday.
16062 */
16063
16064 /* VS 2008 requires the #include outside of the function */
16065 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16066 #include <sys/timeb.h>
16067 #endif
16068
16069 /* Get the current time as a TimeMark */
16070 void
16071 GetTimeMark (TimeMark *tm)
16072 {
16073 #if HAVE_GETTIMEOFDAY
16074
16075     struct timeval timeVal;
16076     struct timezone timeZone;
16077
16078     gettimeofday(&timeVal, &timeZone);
16079     tm->sec = (long) timeVal.tv_sec;
16080     tm->ms = (int) (timeVal.tv_usec / 1000L);
16081
16082 #else /*!HAVE_GETTIMEOFDAY*/
16083 #if HAVE_FTIME
16084
16085 // include <sys/timeb.h> / moved to just above start of function
16086     struct timeb timeB;
16087
16088     ftime(&timeB);
16089     tm->sec = (long) timeB.time;
16090     tm->ms = (int) timeB.millitm;
16091
16092 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16093     tm->sec = (long) time(NULL);
16094     tm->ms = 0;
16095 #endif
16096 #endif
16097 }
16098
16099 /* Return the difference in milliseconds between two
16100    time marks.  We assume the difference will fit in a long!
16101 */
16102 long
16103 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16104 {
16105     return 1000L*(tm2->sec - tm1->sec) +
16106            (long) (tm2->ms - tm1->ms);
16107 }
16108
16109
16110 /*
16111  * Code to manage the game clocks.
16112  *
16113  * In tournament play, black starts the clock and then white makes a move.
16114  * We give the human user a slight advantage if he is playing white---the
16115  * clocks don't run until he makes his first move, so it takes zero time.
16116  * Also, we don't account for network lag, so we could get out of sync
16117  * with GNU Chess's clock -- but then, referees are always right.
16118  */
16119
16120 static TimeMark tickStartTM;
16121 static long intendedTickLength;
16122
16123 long
16124 NextTickLength (long timeRemaining)
16125 {
16126     long nominalTickLength, nextTickLength;
16127
16128     if (timeRemaining > 0L && timeRemaining <= 10000L)
16129       nominalTickLength = 100L;
16130     else
16131       nominalTickLength = 1000L;
16132     nextTickLength = timeRemaining % nominalTickLength;
16133     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16134
16135     return nextTickLength;
16136 }
16137
16138 /* Adjust clock one minute up or down */
16139 void
16140 AdjustClock (Boolean which, int dir)
16141 {
16142     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16143     if(which) blackTimeRemaining += 60000*dir;
16144     else      whiteTimeRemaining += 60000*dir;
16145     DisplayBothClocks();
16146     adjustedClock = TRUE;
16147 }
16148
16149 /* Stop clocks and reset to a fresh time control */
16150 void
16151 ResetClocks ()
16152 {
16153     (void) StopClockTimer();
16154     if (appData.icsActive) {
16155         whiteTimeRemaining = blackTimeRemaining = 0;
16156     } else if (searchTime) {
16157         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16158         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16159     } else { /* [HGM] correct new time quote for time odds */
16160         whiteTC = blackTC = fullTimeControlString;
16161         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16162         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16163     }
16164     if (whiteFlag || blackFlag) {
16165         DisplayTitle("");
16166         whiteFlag = blackFlag = FALSE;
16167     }
16168     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16169     DisplayBothClocks();
16170     adjustedClock = FALSE;
16171 }
16172
16173 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16174
16175 /* Decrement running clock by amount of time that has passed */
16176 void
16177 DecrementClocks ()
16178 {
16179     long timeRemaining;
16180     long lastTickLength, fudge;
16181     TimeMark now;
16182
16183     if (!appData.clockMode) return;
16184     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16185
16186     GetTimeMark(&now);
16187
16188     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16189
16190     /* Fudge if we woke up a little too soon */
16191     fudge = intendedTickLength - lastTickLength;
16192     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16193
16194     if (WhiteOnMove(forwardMostMove)) {
16195         if(whiteNPS >= 0) lastTickLength = 0;
16196         timeRemaining = whiteTimeRemaining -= lastTickLength;
16197         if(timeRemaining < 0 && !appData.icsActive) {
16198             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16199             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16200                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16201                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16202             }
16203         }
16204         DisplayWhiteClock(whiteTimeRemaining - fudge,
16205                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16206     } else {
16207         if(blackNPS >= 0) lastTickLength = 0;
16208         timeRemaining = blackTimeRemaining -= lastTickLength;
16209         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16210             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16211             if(suddenDeath) {
16212                 blackStartMove = forwardMostMove;
16213                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16214             }
16215         }
16216         DisplayBlackClock(blackTimeRemaining - fudge,
16217                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16218     }
16219     if (CheckFlags()) return;
16220
16221     if(twoBoards) { // count down secondary board's clocks as well
16222         activePartnerTime -= lastTickLength;
16223         partnerUp = 1;
16224         if(activePartner == 'W')
16225             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16226         else
16227             DisplayBlackClock(activePartnerTime, TRUE);
16228         partnerUp = 0;
16229     }
16230
16231     tickStartTM = now;
16232     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16233     StartClockTimer(intendedTickLength);
16234
16235     /* if the time remaining has fallen below the alarm threshold, sound the
16236      * alarm. if the alarm has sounded and (due to a takeback or time control
16237      * with increment) the time remaining has increased to a level above the
16238      * threshold, reset the alarm so it can sound again.
16239      */
16240
16241     if (appData.icsActive && appData.icsAlarm) {
16242
16243         /* make sure we are dealing with the user's clock */
16244         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16245                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16246            )) return;
16247
16248         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16249             alarmSounded = FALSE;
16250         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16251             PlayAlarmSound();
16252             alarmSounded = TRUE;
16253         }
16254     }
16255 }
16256
16257
16258 /* A player has just moved, so stop the previously running
16259    clock and (if in clock mode) start the other one.
16260    We redisplay both clocks in case we're in ICS mode, because
16261    ICS gives us an update to both clocks after every move.
16262    Note that this routine is called *after* forwardMostMove
16263    is updated, so the last fractional tick must be subtracted
16264    from the color that is *not* on move now.
16265 */
16266 void
16267 SwitchClocks (int newMoveNr)
16268 {
16269     long lastTickLength;
16270     TimeMark now;
16271     int flagged = FALSE;
16272
16273     GetTimeMark(&now);
16274
16275     if (StopClockTimer() && appData.clockMode) {
16276         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16277         if (!WhiteOnMove(forwardMostMove)) {
16278             if(blackNPS >= 0) lastTickLength = 0;
16279             blackTimeRemaining -= lastTickLength;
16280            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16281 //         if(pvInfoList[forwardMostMove].time == -1)
16282                  pvInfoList[forwardMostMove].time =               // use GUI time
16283                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16284         } else {
16285            if(whiteNPS >= 0) lastTickLength = 0;
16286            whiteTimeRemaining -= lastTickLength;
16287            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16288 //         if(pvInfoList[forwardMostMove].time == -1)
16289                  pvInfoList[forwardMostMove].time =
16290                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16291         }
16292         flagged = CheckFlags();
16293     }
16294     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16295     CheckTimeControl();
16296
16297     if (flagged || !appData.clockMode) return;
16298
16299     switch (gameMode) {
16300       case MachinePlaysBlack:
16301       case MachinePlaysWhite:
16302       case BeginningOfGame:
16303         if (pausing) return;
16304         break;
16305
16306       case EditGame:
16307       case PlayFromGameFile:
16308       case IcsExamining:
16309         return;
16310
16311       default:
16312         break;
16313     }
16314
16315     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16316         if(WhiteOnMove(forwardMostMove))
16317              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16318         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16319     }
16320
16321     tickStartTM = now;
16322     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16323       whiteTimeRemaining : blackTimeRemaining);
16324     StartClockTimer(intendedTickLength);
16325 }
16326
16327
16328 /* Stop both clocks */
16329 void
16330 StopClocks ()
16331 {
16332     long lastTickLength;
16333     TimeMark now;
16334
16335     if (!StopClockTimer()) return;
16336     if (!appData.clockMode) return;
16337
16338     GetTimeMark(&now);
16339
16340     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16341     if (WhiteOnMove(forwardMostMove)) {
16342         if(whiteNPS >= 0) lastTickLength = 0;
16343         whiteTimeRemaining -= lastTickLength;
16344         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16345     } else {
16346         if(blackNPS >= 0) lastTickLength = 0;
16347         blackTimeRemaining -= lastTickLength;
16348         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16349     }
16350     CheckFlags();
16351 }
16352
16353 /* Start clock of player on move.  Time may have been reset, so
16354    if clock is already running, stop and restart it. */
16355 void
16356 StartClocks ()
16357 {
16358     (void) StopClockTimer(); /* in case it was running already */
16359     DisplayBothClocks();
16360     if (CheckFlags()) return;
16361
16362     if (!appData.clockMode) return;
16363     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16364
16365     GetTimeMark(&tickStartTM);
16366     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16367       whiteTimeRemaining : blackTimeRemaining);
16368
16369    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16370     whiteNPS = blackNPS = -1;
16371     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16372        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16373         whiteNPS = first.nps;
16374     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16375        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16376         blackNPS = first.nps;
16377     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16378         whiteNPS = second.nps;
16379     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16380         blackNPS = second.nps;
16381     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16382
16383     StartClockTimer(intendedTickLength);
16384 }
16385
16386 char *
16387 TimeString (long ms)
16388 {
16389     long second, minute, hour, day;
16390     char *sign = "";
16391     static char buf[32];
16392
16393     if (ms > 0 && ms <= 9900) {
16394       /* convert milliseconds to tenths, rounding up */
16395       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16396
16397       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16398       return buf;
16399     }
16400
16401     /* convert milliseconds to seconds, rounding up */
16402     /* use floating point to avoid strangeness of integer division
16403        with negative dividends on many machines */
16404     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16405
16406     if (second < 0) {
16407         sign = "-";
16408         second = -second;
16409     }
16410
16411     day = second / (60 * 60 * 24);
16412     second = second % (60 * 60 * 24);
16413     hour = second / (60 * 60);
16414     second = second % (60 * 60);
16415     minute = second / 60;
16416     second = second % 60;
16417
16418     if (day > 0)
16419       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16420               sign, day, hour, minute, second);
16421     else if (hour > 0)
16422       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16423     else
16424       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16425
16426     return buf;
16427 }
16428
16429
16430 /*
16431  * This is necessary because some C libraries aren't ANSI C compliant yet.
16432  */
16433 char *
16434 StrStr (char *string, char *match)
16435 {
16436     int i, length;
16437
16438     length = strlen(match);
16439
16440     for (i = strlen(string) - length; i >= 0; i--, string++)
16441       if (!strncmp(match, string, length))
16442         return string;
16443
16444     return NULL;
16445 }
16446
16447 char *
16448 StrCaseStr (char *string, char *match)
16449 {
16450     int i, j, length;
16451
16452     length = strlen(match);
16453
16454     for (i = strlen(string) - length; i >= 0; i--, string++) {
16455         for (j = 0; j < length; j++) {
16456             if (ToLower(match[j]) != ToLower(string[j]))
16457               break;
16458         }
16459         if (j == length) return string;
16460     }
16461
16462     return NULL;
16463 }
16464
16465 #ifndef _amigados
16466 int
16467 StrCaseCmp (char *s1, char *s2)
16468 {
16469     char c1, c2;
16470
16471     for (;;) {
16472         c1 = ToLower(*s1++);
16473         c2 = ToLower(*s2++);
16474         if (c1 > c2) return 1;
16475         if (c1 < c2) return -1;
16476         if (c1 == NULLCHAR) return 0;
16477     }
16478 }
16479
16480
16481 int
16482 ToLower (int c)
16483 {
16484     return isupper(c) ? tolower(c) : c;
16485 }
16486
16487
16488 int
16489 ToUpper (int c)
16490 {
16491     return islower(c) ? toupper(c) : c;
16492 }
16493 #endif /* !_amigados    */
16494
16495 char *
16496 StrSave (char *s)
16497 {
16498   char *ret;
16499
16500   if ((ret = (char *) malloc(strlen(s) + 1)))
16501     {
16502       safeStrCpy(ret, s, strlen(s)+1);
16503     }
16504   return ret;
16505 }
16506
16507 char *
16508 StrSavePtr (char *s, char **savePtr)
16509 {
16510     if (*savePtr) {
16511         free(*savePtr);
16512     }
16513     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16514       safeStrCpy(*savePtr, s, strlen(s)+1);
16515     }
16516     return(*savePtr);
16517 }
16518
16519 char *
16520 PGNDate ()
16521 {
16522     time_t clock;
16523     struct tm *tm;
16524     char buf[MSG_SIZ];
16525
16526     clock = time((time_t *)NULL);
16527     tm = localtime(&clock);
16528     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16529             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16530     return StrSave(buf);
16531 }
16532
16533
16534 char *
16535 PositionToFEN (int move, char *overrideCastling)
16536 {
16537     int i, j, fromX, fromY, toX, toY;
16538     int whiteToPlay;
16539     char buf[MSG_SIZ];
16540     char *p, *q;
16541     int emptycount;
16542     ChessSquare piece;
16543
16544     whiteToPlay = (gameMode == EditPosition) ?
16545       !blackPlaysFirst : (move % 2 == 0);
16546     p = buf;
16547
16548     /* Piece placement data */
16549     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16550         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16551         emptycount = 0;
16552         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16553             if (boards[move][i][j] == EmptySquare) {
16554                 emptycount++;
16555             } else { ChessSquare piece = boards[move][i][j];
16556                 if (emptycount > 0) {
16557                     if(emptycount<10) /* [HGM] can be >= 10 */
16558                         *p++ = '0' + emptycount;
16559                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16560                     emptycount = 0;
16561                 }
16562                 if(PieceToChar(piece) == '+') {
16563                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16564                     *p++ = '+';
16565                     piece = (ChessSquare)(DEMOTED piece);
16566                 }
16567                 *p++ = PieceToChar(piece);
16568                 if(p[-1] == '~') {
16569                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16570                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16571                     *p++ = '~';
16572                 }
16573             }
16574         }
16575         if (emptycount > 0) {
16576             if(emptycount<10) /* [HGM] can be >= 10 */
16577                 *p++ = '0' + emptycount;
16578             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16579             emptycount = 0;
16580         }
16581         *p++ = '/';
16582     }
16583     *(p - 1) = ' ';
16584
16585     /* [HGM] print Crazyhouse or Shogi holdings */
16586     if( gameInfo.holdingsWidth ) {
16587         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16588         q = p;
16589         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16590             piece = boards[move][i][BOARD_WIDTH-1];
16591             if( piece != EmptySquare )
16592               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16593                   *p++ = PieceToChar(piece);
16594         }
16595         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16596             piece = boards[move][BOARD_HEIGHT-i-1][0];
16597             if( piece != EmptySquare )
16598               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16599                   *p++ = PieceToChar(piece);
16600         }
16601
16602         if( q == p ) *p++ = '-';
16603         *p++ = ']';
16604         *p++ = ' ';
16605     }
16606
16607     /* Active color */
16608     *p++ = whiteToPlay ? 'w' : 'b';
16609     *p++ = ' ';
16610
16611   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16612     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16613   } else {
16614   if(nrCastlingRights) {
16615      q = p;
16616      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16617        /* [HGM] write directly from rights */
16618            if(boards[move][CASTLING][2] != NoRights &&
16619               boards[move][CASTLING][0] != NoRights   )
16620                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16621            if(boards[move][CASTLING][2] != NoRights &&
16622               boards[move][CASTLING][1] != NoRights   )
16623                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16624            if(boards[move][CASTLING][5] != NoRights &&
16625               boards[move][CASTLING][3] != NoRights   )
16626                 *p++ = boards[move][CASTLING][3] + AAA;
16627            if(boards[move][CASTLING][5] != NoRights &&
16628               boards[move][CASTLING][4] != NoRights   )
16629                 *p++ = boards[move][CASTLING][4] + AAA;
16630      } else {
16631
16632         /* [HGM] write true castling rights */
16633         if( nrCastlingRights == 6 ) {
16634             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16635                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16636             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16637                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16638             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16639                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16640             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16641                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16642         }
16643      }
16644      if (q == p) *p++ = '-'; /* No castling rights */
16645      *p++ = ' ';
16646   }
16647
16648   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16649      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16650     /* En passant target square */
16651     if (move > backwardMostMove) {
16652         fromX = moveList[move - 1][0] - AAA;
16653         fromY = moveList[move - 1][1] - ONE;
16654         toX = moveList[move - 1][2] - AAA;
16655         toY = moveList[move - 1][3] - ONE;
16656         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16657             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16658             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16659             fromX == toX) {
16660             /* 2-square pawn move just happened */
16661             *p++ = toX + AAA;
16662             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16663         } else {
16664             *p++ = '-';
16665         }
16666     } else if(move == backwardMostMove) {
16667         // [HGM] perhaps we should always do it like this, and forget the above?
16668         if((signed char)boards[move][EP_STATUS] >= 0) {
16669             *p++ = boards[move][EP_STATUS] + AAA;
16670             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16671         } else {
16672             *p++ = '-';
16673         }
16674     } else {
16675         *p++ = '-';
16676     }
16677     *p++ = ' ';
16678   }
16679   }
16680
16681     /* [HGM] find reversible plies */
16682     {   int i = 0, j=move;
16683
16684         if (appData.debugMode) { int k;
16685             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16686             for(k=backwardMostMove; k<=forwardMostMove; k++)
16687                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16688
16689         }
16690
16691         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16692         if( j == backwardMostMove ) i += initialRulePlies;
16693         sprintf(p, "%d ", i);
16694         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16695     }
16696     /* Fullmove number */
16697     sprintf(p, "%d", (move / 2) + 1);
16698
16699     return StrSave(buf);
16700 }
16701
16702 Boolean
16703 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16704 {
16705     int i, j;
16706     char *p, c;
16707     int emptycount;
16708     ChessSquare piece;
16709
16710     p = fen;
16711
16712     /* [HGM] by default clear Crazyhouse holdings, if present */
16713     if(gameInfo.holdingsWidth) {
16714        for(i=0; i<BOARD_HEIGHT; i++) {
16715            board[i][0]             = EmptySquare; /* black holdings */
16716            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16717            board[i][1]             = (ChessSquare) 0; /* black counts */
16718            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16719        }
16720     }
16721
16722     /* Piece placement data */
16723     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16724         j = 0;
16725         for (;;) {
16726             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16727                 if (*p == '/') p++;
16728                 emptycount = gameInfo.boardWidth - j;
16729                 while (emptycount--)
16730                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16731                 break;
16732 #if(BOARD_FILES >= 10)
16733             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16734                 p++; emptycount=10;
16735                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16736                 while (emptycount--)
16737                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16738 #endif
16739             } else if (isdigit(*p)) {
16740                 emptycount = *p++ - '0';
16741                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16742                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16743                 while (emptycount--)
16744                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16745             } else if (*p == '+' || isalpha(*p)) {
16746                 if (j >= gameInfo.boardWidth) return FALSE;
16747                 if(*p=='+') {
16748                     piece = CharToPiece(*++p);
16749                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16750                     piece = (ChessSquare) (PROMOTED piece ); p++;
16751                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16752                 } else piece = CharToPiece(*p++);
16753
16754                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16755                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16756                     piece = (ChessSquare) (PROMOTED piece);
16757                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16758                     p++;
16759                 }
16760                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16761             } else {
16762                 return FALSE;
16763             }
16764         }
16765     }
16766     while (*p == '/' || *p == ' ') p++;
16767
16768     /* [HGM] look for Crazyhouse holdings here */
16769     while(*p==' ') p++;
16770     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16771         if(*p == '[') p++;
16772         if(*p == '-' ) p++; /* empty holdings */ else {
16773             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16774             /* if we would allow FEN reading to set board size, we would   */
16775             /* have to add holdings and shift the board read so far here   */
16776             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16777                 p++;
16778                 if((int) piece >= (int) BlackPawn ) {
16779                     i = (int)piece - (int)BlackPawn;
16780                     i = PieceToNumber((ChessSquare)i);
16781                     if( i >= gameInfo.holdingsSize ) return FALSE;
16782                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16783                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16784                 } else {
16785                     i = (int)piece - (int)WhitePawn;
16786                     i = PieceToNumber((ChessSquare)i);
16787                     if( i >= gameInfo.holdingsSize ) return FALSE;
16788                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16789                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16790                 }
16791             }
16792         }
16793         if(*p == ']') p++;
16794     }
16795
16796     while(*p == ' ') p++;
16797
16798     /* Active color */
16799     c = *p++;
16800     if(appData.colorNickNames) {
16801       if( c == appData.colorNickNames[0] ) c = 'w'; else
16802       if( c == appData.colorNickNames[1] ) c = 'b';
16803     }
16804     switch (c) {
16805       case 'w':
16806         *blackPlaysFirst = FALSE;
16807         break;
16808       case 'b':
16809         *blackPlaysFirst = TRUE;
16810         break;
16811       default:
16812         return FALSE;
16813     }
16814
16815     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16816     /* return the extra info in global variiables             */
16817
16818     /* set defaults in case FEN is incomplete */
16819     board[EP_STATUS] = EP_UNKNOWN;
16820     for(i=0; i<nrCastlingRights; i++ ) {
16821         board[CASTLING][i] =
16822             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16823     }   /* assume possible unless obviously impossible */
16824     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16825     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16826     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16827                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16828     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16829     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16830     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16831                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16832     FENrulePlies = 0;
16833
16834     while(*p==' ') p++;
16835     if(nrCastlingRights) {
16836       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16837           /* castling indicator present, so default becomes no castlings */
16838           for(i=0; i<nrCastlingRights; i++ ) {
16839                  board[CASTLING][i] = NoRights;
16840           }
16841       }
16842       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16843              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16844              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16845              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16846         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16847
16848         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16849             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16850             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16851         }
16852         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16853             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16854         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16855                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16856         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16857                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16858         switch(c) {
16859           case'K':
16860               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16861               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16862               board[CASTLING][2] = whiteKingFile;
16863               break;
16864           case'Q':
16865               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16866               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16867               board[CASTLING][2] = whiteKingFile;
16868               break;
16869           case'k':
16870               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16871               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16872               board[CASTLING][5] = blackKingFile;
16873               break;
16874           case'q':
16875               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16876               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16877               board[CASTLING][5] = blackKingFile;
16878           case '-':
16879               break;
16880           default: /* FRC castlings */
16881               if(c >= 'a') { /* black rights */
16882                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16883                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16884                   if(i == BOARD_RGHT) break;
16885                   board[CASTLING][5] = i;
16886                   c -= AAA;
16887                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16888                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16889                   if(c > i)
16890                       board[CASTLING][3] = c;
16891                   else
16892                       board[CASTLING][4] = c;
16893               } else { /* white rights */
16894                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16895                     if(board[0][i] == WhiteKing) break;
16896                   if(i == BOARD_RGHT) break;
16897                   board[CASTLING][2] = i;
16898                   c -= AAA - 'a' + 'A';
16899                   if(board[0][c] >= WhiteKing) break;
16900                   if(c > i)
16901                       board[CASTLING][0] = c;
16902                   else
16903                       board[CASTLING][1] = c;
16904               }
16905         }
16906       }
16907       for(i=0; i<nrCastlingRights; i++)
16908         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16909     if (appData.debugMode) {
16910         fprintf(debugFP, "FEN castling rights:");
16911         for(i=0; i<nrCastlingRights; i++)
16912         fprintf(debugFP, " %d", board[CASTLING][i]);
16913         fprintf(debugFP, "\n");
16914     }
16915
16916       while(*p==' ') p++;
16917     }
16918
16919     /* read e.p. field in games that know e.p. capture */
16920     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16921        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16922       if(*p=='-') {
16923         p++; board[EP_STATUS] = EP_NONE;
16924       } else {
16925          char c = *p++ - AAA;
16926
16927          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16928          if(*p >= '0' && *p <='9') p++;
16929          board[EP_STATUS] = c;
16930       }
16931     }
16932
16933
16934     if(sscanf(p, "%d", &i) == 1) {
16935         FENrulePlies = i; /* 50-move ply counter */
16936         /* (The move number is still ignored)    */
16937     }
16938
16939     return TRUE;
16940 }
16941
16942 void
16943 EditPositionPasteFEN (char *fen)
16944 {
16945   if (fen != NULL) {
16946     Board initial_position;
16947
16948     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16949       DisplayError(_("Bad FEN position in clipboard"), 0);
16950       return ;
16951     } else {
16952       int savedBlackPlaysFirst = blackPlaysFirst;
16953       EditPositionEvent();
16954       blackPlaysFirst = savedBlackPlaysFirst;
16955       CopyBoard(boards[0], initial_position);
16956       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16957       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16958       DisplayBothClocks();
16959       DrawPosition(FALSE, boards[currentMove]);
16960     }
16961   }
16962 }
16963
16964 static char cseq[12] = "\\   ";
16965
16966 Boolean
16967 set_cont_sequence (char *new_seq)
16968 {
16969     int len;
16970     Boolean ret;
16971
16972     // handle bad attempts to set the sequence
16973         if (!new_seq)
16974                 return 0; // acceptable error - no debug
16975
16976     len = strlen(new_seq);
16977     ret = (len > 0) && (len < sizeof(cseq));
16978     if (ret)
16979       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16980     else if (appData.debugMode)
16981       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16982     return ret;
16983 }
16984
16985 /*
16986     reformat a source message so words don't cross the width boundary.  internal
16987     newlines are not removed.  returns the wrapped size (no null character unless
16988     included in source message).  If dest is NULL, only calculate the size required
16989     for the dest buffer.  lp argument indicats line position upon entry, and it's
16990     passed back upon exit.
16991 */
16992 int
16993 wrap (char *dest, char *src, int count, int width, int *lp)
16994 {
16995     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16996
16997     cseq_len = strlen(cseq);
16998     old_line = line = *lp;
16999     ansi = len = clen = 0;
17000
17001     for (i=0; i < count; i++)
17002     {
17003         if (src[i] == '\033')
17004             ansi = 1;
17005
17006         // if we hit the width, back up
17007         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17008         {
17009             // store i & len in case the word is too long
17010             old_i = i, old_len = len;
17011
17012             // find the end of the last word
17013             while (i && src[i] != ' ' && src[i] != '\n')
17014             {
17015                 i--;
17016                 len--;
17017             }
17018
17019             // word too long?  restore i & len before splitting it
17020             if ((old_i-i+clen) >= width)
17021             {
17022                 i = old_i;
17023                 len = old_len;
17024             }
17025
17026             // extra space?
17027             if (i && src[i-1] == ' ')
17028                 len--;
17029
17030             if (src[i] != ' ' && src[i] != '\n')
17031             {
17032                 i--;
17033                 if (len)
17034                     len--;
17035             }
17036
17037             // now append the newline and continuation sequence
17038             if (dest)
17039                 dest[len] = '\n';
17040             len++;
17041             if (dest)
17042                 strncpy(dest+len, cseq, cseq_len);
17043             len += cseq_len;
17044             line = cseq_len;
17045             clen = cseq_len;
17046             continue;
17047         }
17048
17049         if (dest)
17050             dest[len] = src[i];
17051         len++;
17052         if (!ansi)
17053             line++;
17054         if (src[i] == '\n')
17055             line = 0;
17056         if (src[i] == 'm')
17057             ansi = 0;
17058     }
17059     if (dest && appData.debugMode)
17060     {
17061         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17062             count, width, line, len, *lp);
17063         show_bytes(debugFP, src, count);
17064         fprintf(debugFP, "\ndest: ");
17065         show_bytes(debugFP, dest, len);
17066         fprintf(debugFP, "\n");
17067     }
17068     *lp = dest ? line : old_line;
17069
17070     return len;
17071 }
17072
17073 // [HGM] vari: routines for shelving variations
17074 Boolean modeRestore = FALSE;
17075
17076 void
17077 PushInner (int firstMove, int lastMove)
17078 {
17079         int i, j, nrMoves = lastMove - firstMove;
17080
17081         // push current tail of game on stack
17082         savedResult[storedGames] = gameInfo.result;
17083         savedDetails[storedGames] = gameInfo.resultDetails;
17084         gameInfo.resultDetails = NULL;
17085         savedFirst[storedGames] = firstMove;
17086         savedLast [storedGames] = lastMove;
17087         savedFramePtr[storedGames] = framePtr;
17088         framePtr -= nrMoves; // reserve space for the boards
17089         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17090             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17091             for(j=0; j<MOVE_LEN; j++)
17092                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17093             for(j=0; j<2*MOVE_LEN; j++)
17094                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17095             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17096             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17097             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17098             pvInfoList[firstMove+i-1].depth = 0;
17099             commentList[framePtr+i] = commentList[firstMove+i];
17100             commentList[firstMove+i] = NULL;
17101         }
17102
17103         storedGames++;
17104         forwardMostMove = firstMove; // truncate game so we can start variation
17105 }
17106
17107 void
17108 PushTail (int firstMove, int lastMove)
17109 {
17110         if(appData.icsActive) { // only in local mode
17111                 forwardMostMove = currentMove; // mimic old ICS behavior
17112                 return;
17113         }
17114         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17115
17116         PushInner(firstMove, lastMove);
17117         if(storedGames == 1) GreyRevert(FALSE);
17118         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17119 }
17120
17121 void
17122 PopInner (Boolean annotate)
17123 {
17124         int i, j, nrMoves;
17125         char buf[8000], moveBuf[20];
17126
17127         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17128         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17129         nrMoves = savedLast[storedGames] - currentMove;
17130         if(annotate) {
17131                 int cnt = 10;
17132                 if(!WhiteOnMove(currentMove))
17133                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17134                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17135                 for(i=currentMove; i<forwardMostMove; i++) {
17136                         if(WhiteOnMove(i))
17137                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17138                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17139                         strcat(buf, moveBuf);
17140                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17141                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17142                 }
17143                 strcat(buf, ")");
17144         }
17145         for(i=1; i<=nrMoves; i++) { // copy last variation back
17146             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17147             for(j=0; j<MOVE_LEN; j++)
17148                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17149             for(j=0; j<2*MOVE_LEN; j++)
17150                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17151             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17152             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17153             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17154             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17155             commentList[currentMove+i] = commentList[framePtr+i];
17156             commentList[framePtr+i] = NULL;
17157         }
17158         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17159         framePtr = savedFramePtr[storedGames];
17160         gameInfo.result = savedResult[storedGames];
17161         if(gameInfo.resultDetails != NULL) {
17162             free(gameInfo.resultDetails);
17163       }
17164         gameInfo.resultDetails = savedDetails[storedGames];
17165         forwardMostMove = currentMove + nrMoves;
17166 }
17167
17168 Boolean
17169 PopTail (Boolean annotate)
17170 {
17171         if(appData.icsActive) return FALSE; // only in local mode
17172         if(!storedGames) return FALSE; // sanity
17173         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17174
17175         PopInner(annotate);
17176         if(currentMove < forwardMostMove) ForwardEvent(); else
17177         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17178
17179         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17180         return TRUE;
17181 }
17182
17183 void
17184 CleanupTail ()
17185 {       // remove all shelved variations
17186         int i;
17187         for(i=0; i<storedGames; i++) {
17188             if(savedDetails[i])
17189                 free(savedDetails[i]);
17190             savedDetails[i] = NULL;
17191         }
17192         for(i=framePtr; i<MAX_MOVES; i++) {
17193                 if(commentList[i]) free(commentList[i]);
17194                 commentList[i] = NULL;
17195         }
17196         framePtr = MAX_MOVES-1;
17197         storedGames = 0;
17198 }
17199
17200 void
17201 LoadVariation (int index, char *text)
17202 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17203         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17204         int level = 0, move;
17205
17206         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17207         // first find outermost bracketing variation
17208         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17209             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17210                 if(*p == '{') wait = '}'; else
17211                 if(*p == '[') wait = ']'; else
17212                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17213                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17214             }
17215             if(*p == wait) wait = NULLCHAR; // closing ]} found
17216             p++;
17217         }
17218         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17219         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17220         end[1] = NULLCHAR; // clip off comment beyond variation
17221         ToNrEvent(currentMove-1);
17222         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17223         // kludge: use ParsePV() to append variation to game
17224         move = currentMove;
17225         ParsePV(start, TRUE, TRUE);
17226         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17227         ClearPremoveHighlights();
17228         CommentPopDown();
17229         ToNrEvent(currentMove+1);
17230 }
17231