e110e38f59f24f7f1e336233365d649b5f36f155
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "gettext.h"
133
134 #ifdef ENABLE_NLS
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
138 #else
139 # ifdef WIN32
140 #   define _(s) T_(s)
141 #   define N_(s) s
142 # else
143 #   define _(s) (s)
144 #   define N_(s) s
145 #   define T_(s) s
146 # endif
147 #endif
148
149
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152                          char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154                       char *buf, int count, int error));
155 void ics_printf P((char *format, ...));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226
227 #ifdef WIN32
228        extern void ConsoleCreate();
229 #endif
230
231 ChessProgramState *WhitePlayer();
232 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
233 int VerifyDisplayMode P(());
234
235 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
236 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
237 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
238 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
239 void ics_update_width P((int new_width));
240 extern char installDir[MSG_SIZ];
241 VariantClass startVariant; /* [HGM] nicks: initial variant */
242 Boolean abortMatch;
243
244 extern int tinyLayout, smallLayout;
245 ChessProgramStats programStats;
246 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
247 int endPV = -1;
248 static int exiting = 0; /* [HGM] moved to top */
249 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
250 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
251 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
252 int partnerHighlight[2];
253 Boolean partnerBoardValid = 0;
254 char partnerStatus[MSG_SIZ];
255 Boolean partnerUp;
256 Boolean originalFlip;
257 Boolean twoBoards = 0;
258 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
259 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
260 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
261 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
262 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
263 int opponentKibitzes;
264 int lastSavedGame; /* [HGM] save: ID of game */
265 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
266 extern int chatCount;
267 int chattingPartner;
268 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
269 char lastMsg[MSG_SIZ];
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
272 int promoDefaultAltered;
273
274 /* States for ics_getting_history */
275 #define H_FALSE 0
276 #define H_REQUESTED 1
277 #define H_GOT_REQ_HEADER 2
278 #define H_GOT_UNREQ_HEADER 3
279 #define H_GETTING_MOVES 4
280 #define H_GOT_UNWANTED_HEADER 5
281
282 /* whosays values for GameEnds */
283 #define GE_ICS 0
284 #define GE_ENGINE 1
285 #define GE_PLAYER 2
286 #define GE_FILE 3
287 #define GE_XBOARD 4
288 #define GE_ENGINE1 5
289 #define GE_ENGINE2 6
290
291 /* Maximum number of games in a cmail message */
292 #define CMAIL_MAX_GAMES 20
293
294 /* Different types of move when calling RegisterMove */
295 #define CMAIL_MOVE   0
296 #define CMAIL_RESIGN 1
297 #define CMAIL_DRAW   2
298 #define CMAIL_ACCEPT 3
299
300 /* Different types of result to remember for each game */
301 #define CMAIL_NOT_RESULT 0
302 #define CMAIL_OLD_RESULT 1
303 #define CMAIL_NEW_RESULT 2
304
305 /* Telnet protocol constants */
306 #define TN_WILL 0373
307 #define TN_WONT 0374
308 #define TN_DO   0375
309 #define TN_DONT 0376
310 #define TN_IAC  0377
311 #define TN_ECHO 0001
312 #define TN_SGA  0003
313 #define TN_PORT 23
314
315 char*
316 safeStrCpy (char *dst, const char *src, size_t count)
317 { // [HGM] made safe
318   int i;
319   assert( dst != NULL );
320   assert( src != NULL );
321   assert( count > 0 );
322
323   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
324   if(  i == count && dst[count-1] != NULLCHAR)
325     {
326       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
327       if(appData.debugMode)
328       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
329     }
330
331   return dst;
332 }
333
334 /* Some compiler can't cast u64 to double
335  * This function do the job for us:
336
337  * We use the highest bit for cast, this only
338  * works if the highest bit is not
339  * in use (This should not happen)
340  *
341  * We used this for all compiler
342  */
343 double
344 u64ToDouble (u64 value)
345 {
346   double r;
347   u64 tmp = value & u64Const(0x7fffffffffffffff);
348   r = (double)(s64)tmp;
349   if (value & u64Const(0x8000000000000000))
350        r +=  9.2233720368547758080e18; /* 2^63 */
351  return r;
352 }
353
354 /* Fake up flags for now, as we aren't keeping track of castling
355    availability yet. [HGM] Change of logic: the flag now only
356    indicates the type of castlings allowed by the rule of the game.
357    The actual rights themselves are maintained in the array
358    castlingRights, as part of the game history, and are not probed
359    by this function.
360  */
361 int
362 PosFlags (index)
363 {
364   int flags = F_ALL_CASTLE_OK;
365   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
366   switch (gameInfo.variant) {
367   case VariantSuicide:
368     flags &= ~F_ALL_CASTLE_OK;
369   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
370     flags |= F_IGNORE_CHECK;
371   case VariantLosers:
372     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
373     break;
374   case VariantAtomic:
375     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
376     break;
377   case VariantKriegspiel:
378     flags |= F_KRIEGSPIEL_CAPTURE;
379     break;
380   case VariantCapaRandom:
381   case VariantFischeRandom:
382     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
383   case VariantNoCastle:
384   case VariantShatranj:
385   case VariantCourier:
386   case VariantMakruk:
387   case VariantGrand:
388     flags &= ~F_ALL_CASTLE_OK;
389     break;
390   default:
391     break;
392   }
393   return flags;
394 }
395
396 FILE *gameFileFP, *debugFP, *serverFP;
397 char *currentDebugFile; // [HGM] debug split: to remember name
398
399 /*
400     [AS] Note: sometimes, the sscanf() function is used to parse the input
401     into a fixed-size buffer. Because of this, we must be prepared to
402     receive strings as long as the size of the input buffer, which is currently
403     set to 4K for Windows and 8K for the rest.
404     So, we must either allocate sufficiently large buffers here, or
405     reduce the size of the input buffer in the input reading part.
406 */
407
408 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
409 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
410 char thinkOutput1[MSG_SIZ*10];
411
412 ChessProgramState first, second, pairing;
413
414 /* premove variables */
415 int premoveToX = 0;
416 int premoveToY = 0;
417 int premoveFromX = 0;
418 int premoveFromY = 0;
419 int premovePromoChar = 0;
420 int gotPremove = 0;
421 Boolean alarmSounded;
422 /* end premove variables */
423
424 char *ics_prefix = "$";
425 int ics_type = ICS_GENERIC;
426
427 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
428 int pauseExamForwardMostMove = 0;
429 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
430 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
431 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
432 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
433 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
434 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
435 int whiteFlag = FALSE, blackFlag = FALSE;
436 int userOfferedDraw = FALSE;
437 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
438 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
439 int cmailMoveType[CMAIL_MAX_GAMES];
440 long ics_clock_paused = 0;
441 ProcRef icsPR = NoProc, cmailPR = NoProc;
442 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
443 GameMode gameMode = BeginningOfGame;
444 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
445 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
446 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
447 int hiddenThinkOutputState = 0; /* [AS] */
448 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
449 int adjudicateLossPlies = 6;
450 char white_holding[64], black_holding[64];
451 TimeMark lastNodeCountTime;
452 long lastNodeCount=0;
453 int shiftKey, controlKey; // [HGM] set by mouse handler
454
455 int have_sent_ICS_logon = 0;
456 int movesPerSession;
457 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
458 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
459 Boolean adjustedClock;
460 long timeControl_2; /* [AS] Allow separate time controls */
461 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
462 long timeRemaining[2][MAX_MOVES];
463 int matchGame = 0, nextGame = 0, roundNr = 0;
464 Boolean waitingForGame = FALSE;
465 TimeMark programStartTime, pauseStart;
466 char ics_handle[MSG_SIZ];
467 int have_set_title = 0;
468
469 /* animateTraining preserves the state of appData.animate
470  * when Training mode is activated. This allows the
471  * response to be animated when appData.animate == TRUE and
472  * appData.animateDragging == TRUE.
473  */
474 Boolean animateTraining;
475
476 GameInfo gameInfo;
477
478 AppData appData;
479
480 Board boards[MAX_MOVES];
481 /* [HGM] Following 7 needed for accurate legality tests: */
482 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
483 signed char  initialRights[BOARD_FILES];
484 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
485 int   initialRulePlies, FENrulePlies;
486 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
487 int loadFlag = 0;
488 Boolean shuffleOpenings;
489 int mute; // mute all sounds
490
491 // [HGM] vari: next 12 to save and restore variations
492 #define MAX_VARIATIONS 10
493 int framePtr = MAX_MOVES-1; // points to free stack entry
494 int storedGames = 0;
495 int savedFirst[MAX_VARIATIONS];
496 int savedLast[MAX_VARIATIONS];
497 int savedFramePtr[MAX_VARIATIONS];
498 char *savedDetails[MAX_VARIATIONS];
499 ChessMove savedResult[MAX_VARIATIONS];
500
501 void PushTail P((int firstMove, int lastMove));
502 Boolean PopTail P((Boolean annotate));
503 void PushInner P((int firstMove, int lastMove));
504 void PopInner P((Boolean annotate));
505 void CleanupTail P((void));
506
507 ChessSquare  FIDEArray[2][BOARD_FILES] = {
508     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
509         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
510     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
511         BlackKing, BlackBishop, BlackKnight, BlackRook }
512 };
513
514 ChessSquare twoKingsArray[2][BOARD_FILES] = {
515     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
516         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
517     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
518         BlackKing, BlackKing, BlackKnight, BlackRook }
519 };
520
521 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
522     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
523         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
524     { BlackRook, BlackMan, BlackBishop, BlackQueen,
525         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
526 };
527
528 ChessSquare SpartanArray[2][BOARD_FILES] = {
529     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
530         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
531     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
532         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
533 };
534
535 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
536     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
537         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
538     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
539         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
540 };
541
542 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
543     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
544         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
545     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
546         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
547 };
548
549 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
550     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
551         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
552     { BlackRook, BlackKnight, BlackMan, BlackFerz,
553         BlackKing, BlackMan, BlackKnight, BlackRook }
554 };
555
556
557 #if (BOARD_FILES>=10)
558 ChessSquare ShogiArray[2][BOARD_FILES] = {
559     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
560         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
561     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
562         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
563 };
564
565 ChessSquare XiangqiArray[2][BOARD_FILES] = {
566     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
567         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
568     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
569         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
570 };
571
572 ChessSquare CapablancaArray[2][BOARD_FILES] = {
573     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
574         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
575     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
576         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
577 };
578
579 ChessSquare GreatArray[2][BOARD_FILES] = {
580     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
581         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
582     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
583         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
584 };
585
586 ChessSquare JanusArray[2][BOARD_FILES] = {
587     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
588         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
589     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
590         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
591 };
592
593 ChessSquare GrandArray[2][BOARD_FILES] = {
594     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
595         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
596     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
597         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
598 };
599
600 #ifdef GOTHIC
601 ChessSquare GothicArray[2][BOARD_FILES] = {
602     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
603         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
604     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
605         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
606 };
607 #else // !GOTHIC
608 #define GothicArray CapablancaArray
609 #endif // !GOTHIC
610
611 #ifdef FALCON
612 ChessSquare FalconArray[2][BOARD_FILES] = {
613     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
614         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
615     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
616         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
617 };
618 #else // !FALCON
619 #define FalconArray CapablancaArray
620 #endif // !FALCON
621
622 #else // !(BOARD_FILES>=10)
623 #define XiangqiPosition FIDEArray
624 #define CapablancaArray FIDEArray
625 #define GothicArray FIDEArray
626 #define GreatArray FIDEArray
627 #endif // !(BOARD_FILES>=10)
628
629 #if (BOARD_FILES>=12)
630 ChessSquare CourierArray[2][BOARD_FILES] = {
631     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
632         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
633     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
634         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
635 };
636 #else // !(BOARD_FILES>=12)
637 #define CourierArray CapablancaArray
638 #endif // !(BOARD_FILES>=12)
639
640
641 Board initialPosition;
642
643
644 /* Convert str to a rating. Checks for special cases of "----",
645
646    "++++", etc. Also strips ()'s */
647 int
648 string_to_rating (char *str)
649 {
650   while(*str && !isdigit(*str)) ++str;
651   if (!*str)
652     return 0;   /* One of the special "no rating" cases */
653   else
654     return atoi(str);
655 }
656
657 void
658 ClearProgramStats ()
659 {
660     /* Init programStats */
661     programStats.movelist[0] = 0;
662     programStats.depth = 0;
663     programStats.nr_moves = 0;
664     programStats.moves_left = 0;
665     programStats.nodes = 0;
666     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
667     programStats.score = 0;
668     programStats.got_only_move = 0;
669     programStats.got_fail = 0;
670     programStats.line_is_book = 0;
671 }
672
673 void
674 CommonEngineInit ()
675 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
676     if (appData.firstPlaysBlack) {
677         first.twoMachinesColor = "black\n";
678         second.twoMachinesColor = "white\n";
679     } else {
680         first.twoMachinesColor = "white\n";
681         second.twoMachinesColor = "black\n";
682     }
683
684     first.other = &second;
685     second.other = &first;
686
687     { float norm = 1;
688         if(appData.timeOddsMode) {
689             norm = appData.timeOdds[0];
690             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
691         }
692         first.timeOdds  = appData.timeOdds[0]/norm;
693         second.timeOdds = appData.timeOdds[1]/norm;
694     }
695
696     if(programVersion) free(programVersion);
697     if (appData.noChessProgram) {
698         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
699         sprintf(programVersion, "%s", PACKAGE_STRING);
700     } else {
701       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
702       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
703       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
704     }
705 }
706
707 void
708 UnloadEngine (ChessProgramState *cps)
709 {
710         /* Kill off first chess program */
711         if (cps->isr != NULL)
712           RemoveInputSource(cps->isr);
713         cps->isr = NULL;
714
715         if (cps->pr != NoProc) {
716             ExitAnalyzeMode();
717             DoSleep( appData.delayBeforeQuit );
718             SendToProgram("quit\n", cps);
719             DoSleep( appData.delayAfterQuit );
720             DestroyChildProcess(cps->pr, cps->useSigterm);
721         }
722         cps->pr = NoProc;
723         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
724 }
725
726 void
727 ClearOptions (ChessProgramState *cps)
728 {
729     int i;
730     cps->nrOptions = cps->comboCnt = 0;
731     for(i=0; i<MAX_OPTIONS; i++) {
732         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
733         cps->option[i].textValue = 0;
734     }
735 }
736
737 char *engineNames[] = {
738   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
739      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
740 N_("first"),
741   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
742      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
743 N_("second")
744 };
745
746 void
747 InitEngine (ChessProgramState *cps, int n)
748 {   // [HGM] all engine initialiation put in a function that does one engine
749
750     ClearOptions(cps);
751
752     cps->which = engineNames[n];
753     cps->maybeThinking = FALSE;
754     cps->pr = NoProc;
755     cps->isr = NULL;
756     cps->sendTime = 2;
757     cps->sendDrawOffers = 1;
758
759     cps->program = appData.chessProgram[n];
760     cps->host = appData.host[n];
761     cps->dir = appData.directory[n];
762     cps->initString = appData.engInitString[n];
763     cps->computerString = appData.computerString[n];
764     cps->useSigint  = TRUE;
765     cps->useSigterm = TRUE;
766     cps->reuse = appData.reuse[n];
767     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
768     cps->useSetboard = FALSE;
769     cps->useSAN = FALSE;
770     cps->usePing = FALSE;
771     cps->lastPing = 0;
772     cps->lastPong = 0;
773     cps->usePlayother = FALSE;
774     cps->useColors = TRUE;
775     cps->useUsermove = FALSE;
776     cps->sendICS = FALSE;
777     cps->sendName = appData.icsActive;
778     cps->sdKludge = FALSE;
779     cps->stKludge = FALSE;
780     TidyProgramName(cps->program, cps->host, cps->tidy);
781     cps->matchWins = 0;
782     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
783     cps->analysisSupport = 2; /* detect */
784     cps->analyzing = FALSE;
785     cps->initDone = FALSE;
786
787     /* New features added by Tord: */
788     cps->useFEN960 = FALSE;
789     cps->useOOCastle = TRUE;
790     /* End of new features added by Tord. */
791     cps->fenOverride  = appData.fenOverride[n];
792
793     /* [HGM] time odds: set factor for each machine */
794     cps->timeOdds  = appData.timeOdds[n];
795
796     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
797     cps->accumulateTC = appData.accumulateTC[n];
798     cps->maxNrOfSessions = 1;
799
800     /* [HGM] debug */
801     cps->debug = FALSE;
802
803     cps->supportsNPS = UNKNOWN;
804     cps->memSize = FALSE;
805     cps->maxCores = FALSE;
806     cps->egtFormats[0] = NULLCHAR;
807
808     /* [HGM] options */
809     cps->optionSettings  = appData.engOptions[n];
810
811     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
812     cps->isUCI = appData.isUCI[n]; /* [AS] */
813     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
814
815     if (appData.protocolVersion[n] > PROTOVER
816         || appData.protocolVersion[n] < 1)
817       {
818         char buf[MSG_SIZ];
819         int len;
820
821         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
822                        appData.protocolVersion[n]);
823         if( (len >= MSG_SIZ) && appData.debugMode )
824           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
825
826         DisplayFatalError(buf, 0, 2);
827       }
828     else
829       {
830         cps->protocolVersion = appData.protocolVersion[n];
831       }
832
833     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
834     ParseFeatures(appData.featureDefaults, cps);
835 }
836
837 ChessProgramState *savCps;
838
839 void
840 LoadEngine ()
841 {
842     int i;
843     if(WaitForEngine(savCps, LoadEngine)) return;
844     CommonEngineInit(); // recalculate time odds
845     if(gameInfo.variant != StringToVariant(appData.variant)) {
846         // we changed variant when loading the engine; this forces us to reset
847         Reset(TRUE, savCps != &first);
848         EditGameEvent(); // for consistency with other path, as Reset changes mode
849     }
850     InitChessProgram(savCps, FALSE);
851     SendToProgram("force\n", savCps);
852     DisplayMessage("", "");
853     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
854     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
855     ThawUI();
856     SetGNUMode();
857 }
858
859 void
860 ReplaceEngine (ChessProgramState *cps, int n)
861 {
862     EditGameEvent();
863     UnloadEngine(cps);
864     appData.noChessProgram = FALSE;
865     appData.clockMode = TRUE;
866     InitEngine(cps, n);
867     UpdateLogos(TRUE);
868     if(n) return; // only startup first engine immediately; second can wait
869     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
870     LoadEngine();
871 }
872
873 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
874 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
875
876 static char resetOptions[] = 
877         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
878         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
879         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
880         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
881
882 void
883 FloatToFront(char **list, char *engineLine)
884 {
885     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
886     int i=0;
887     if(appData.recentEngines <= 0) return;
888     TidyProgramName(engineLine, "localhost", tidy+1);
889     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
890     strncpy(buf+1, *list, MSG_SIZ-50);
891     if(p = strstr(buf, tidy)) { // tidy name appears in list
892         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
893         while(*p++ = *++q); // squeeze out
894     }
895     strcat(tidy, buf+1); // put list behind tidy name
896     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
897     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
898     ASSIGN(*list, tidy+1);
899 }
900
901 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
902
903 void
904 Load (ChessProgramState *cps, int i)
905 {
906     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
907     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
908         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
909         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
910         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
911         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
912         appData.firstProtocolVersion = PROTOVER;
913         ParseArgsFromString(buf);
914         SwapEngines(i);
915         ReplaceEngine(cps, i);
916         FloatToFront(&appData.recentEngineList, engineLine);
917         return;
918     }
919     p = engineName;
920     while(q = strchr(p, SLASH)) p = q+1;
921     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
922     if(engineDir[0] != NULLCHAR) {
923         ASSIGN(appData.directory[i], engineDir); p = engineName;
924     } else if(p != engineName) { // derive directory from engine path, when not given
925         p[-1] = 0;
926         ASSIGN(appData.directory[i], engineName);
927         p[-1] = SLASH;
928         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
929     } else { ASSIGN(appData.directory[i], "."); }
930     if(params[0]) {
931         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
932         snprintf(command, MSG_SIZ, "%s %s", p, params);
933         p = command;
934     }
935     ASSIGN(appData.chessProgram[i], p);
936     appData.isUCI[i] = isUCI;
937     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
938     appData.hasOwnBookUCI[i] = hasBook;
939     if(!nickName[0]) useNick = FALSE;
940     if(useNick) ASSIGN(appData.pgnName[i], nickName);
941     if(addToList) {
942         int len;
943         char quote;
944         q = firstChessProgramNames;
945         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
946         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
947         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
948                         quote, p, quote, appData.directory[i], 
949                         useNick ? " -fn \"" : "",
950                         useNick ? nickName : "",
951                         useNick ? "\"" : "",
952                         v1 ? " -firstProtocolVersion 1" : "",
953                         hasBook ? "" : " -fNoOwnBookUCI",
954                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
955                         storeVariant ? " -variant " : "",
956                         storeVariant ? VariantName(gameInfo.variant) : "");
957         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
958         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
959         if(insert != q) insert[-1] = NULLCHAR;
960         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
961         if(q)   free(q);
962         FloatToFront(&appData.recentEngineList, buf);
963     }
964     ReplaceEngine(cps, i);
965 }
966
967 void
968 InitTimeControls ()
969 {
970     int matched, min, sec;
971     /*
972      * Parse timeControl resource
973      */
974     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
975                           appData.movesPerSession)) {
976         char buf[MSG_SIZ];
977         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
978         DisplayFatalError(buf, 0, 2);
979     }
980
981     /*
982      * Parse searchTime resource
983      */
984     if (*appData.searchTime != NULLCHAR) {
985         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
986         if (matched == 1) {
987             searchTime = min * 60;
988         } else if (matched == 2) {
989             searchTime = min * 60 + sec;
990         } else {
991             char buf[MSG_SIZ];
992             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
993             DisplayFatalError(buf, 0, 2);
994         }
995     }
996 }
997
998 void
999 InitBackEnd1 ()
1000 {
1001
1002     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1003     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1004
1005     GetTimeMark(&programStartTime);
1006     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1007     appData.seedBase = random() + (random()<<15);
1008     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1009
1010     ClearProgramStats();
1011     programStats.ok_to_send = 1;
1012     programStats.seen_stat = 0;
1013
1014     /*
1015      * Initialize game list
1016      */
1017     ListNew(&gameList);
1018
1019
1020     /*
1021      * Internet chess server status
1022      */
1023     if (appData.icsActive) {
1024         appData.matchMode = FALSE;
1025         appData.matchGames = 0;
1026 #if ZIPPY
1027         appData.noChessProgram = !appData.zippyPlay;
1028 #else
1029         appData.zippyPlay = FALSE;
1030         appData.zippyTalk = FALSE;
1031         appData.noChessProgram = TRUE;
1032 #endif
1033         if (*appData.icsHelper != NULLCHAR) {
1034             appData.useTelnet = TRUE;
1035             appData.telnetProgram = appData.icsHelper;
1036         }
1037     } else {
1038         appData.zippyTalk = appData.zippyPlay = FALSE;
1039     }
1040
1041     /* [AS] Initialize pv info list [HGM] and game state */
1042     {
1043         int i, j;
1044
1045         for( i=0; i<=framePtr; i++ ) {
1046             pvInfoList[i].depth = -1;
1047             boards[i][EP_STATUS] = EP_NONE;
1048             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1049         }
1050     }
1051
1052     InitTimeControls();
1053
1054     /* [AS] Adjudication threshold */
1055     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1056
1057     InitEngine(&first, 0);
1058     InitEngine(&second, 1);
1059     CommonEngineInit();
1060
1061     pairing.which = "pairing"; // pairing engine
1062     pairing.pr = NoProc;
1063     pairing.isr = NULL;
1064     pairing.program = appData.pairingEngine;
1065     pairing.host = "localhost";
1066     pairing.dir = ".";
1067
1068     if (appData.icsActive) {
1069         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1070     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1071         appData.clockMode = FALSE;
1072         first.sendTime = second.sendTime = 0;
1073     }
1074
1075 #if ZIPPY
1076     /* Override some settings from environment variables, for backward
1077        compatibility.  Unfortunately it's not feasible to have the env
1078        vars just set defaults, at least in xboard.  Ugh.
1079     */
1080     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1081       ZippyInit();
1082     }
1083 #endif
1084
1085     if (!appData.icsActive) {
1086       char buf[MSG_SIZ];
1087       int len;
1088
1089       /* Check for variants that are supported only in ICS mode,
1090          or not at all.  Some that are accepted here nevertheless
1091          have bugs; see comments below.
1092       */
1093       VariantClass variant = StringToVariant(appData.variant);
1094       switch (variant) {
1095       case VariantBughouse:     /* need four players and two boards */
1096       case VariantKriegspiel:   /* need to hide pieces and move details */
1097         /* case VariantFischeRandom: (Fabien: moved below) */
1098         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1099         if( (len >= MSG_SIZ) && appData.debugMode )
1100           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1101
1102         DisplayFatalError(buf, 0, 2);
1103         return;
1104
1105       case VariantUnknown:
1106       case VariantLoadable:
1107       case Variant29:
1108       case Variant30:
1109       case Variant31:
1110       case Variant32:
1111       case Variant33:
1112       case Variant34:
1113       case Variant35:
1114       case Variant36:
1115       default:
1116         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1117         if( (len >= MSG_SIZ) && appData.debugMode )
1118           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1119
1120         DisplayFatalError(buf, 0, 2);
1121         return;
1122
1123       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1124       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1125       case VariantGothic:     /* [HGM] should work */
1126       case VariantCapablanca: /* [HGM] should work */
1127       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1128       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1129       case VariantKnightmate: /* [HGM] should work */
1130       case VariantCylinder:   /* [HGM] untested */
1131       case VariantFalcon:     /* [HGM] untested */
1132       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1133                                  offboard interposition not understood */
1134       case VariantNormal:     /* definitely works! */
1135       case VariantWildCastle: /* pieces not automatically shuffled */
1136       case VariantNoCastle:   /* pieces not automatically shuffled */
1137       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1138       case VariantLosers:     /* should work except for win condition,
1139                                  and doesn't know captures are mandatory */
1140       case VariantSuicide:    /* should work except for win condition,
1141                                  and doesn't know captures are mandatory */
1142       case VariantGiveaway:   /* should work except for win condition,
1143                                  and doesn't know captures are mandatory */
1144       case VariantTwoKings:   /* should work */
1145       case VariantAtomic:     /* should work except for win condition */
1146       case Variant3Check:     /* should work except for win condition */
1147       case VariantShatranj:   /* should work except for all win conditions */
1148       case VariantMakruk:     /* should work except for draw countdown */
1149       case VariantBerolina:   /* might work if TestLegality is off */
1150       case VariantCapaRandom: /* should work */
1151       case VariantJanus:      /* should work */
1152       case VariantSuper:      /* experimental */
1153       case VariantGreat:      /* experimental, requires legality testing to be off */
1154       case VariantSChess:     /* S-Chess, should work */
1155       case VariantGrand:      /* should work */
1156       case VariantSpartan:    /* should work */
1157         break;
1158       }
1159     }
1160
1161 }
1162
1163 int
1164 NextIntegerFromString (char ** str, long * value)
1165 {
1166     int result = -1;
1167     char * s = *str;
1168
1169     while( *s == ' ' || *s == '\t' ) {
1170         s++;
1171     }
1172
1173     *value = 0;
1174
1175     if( *s >= '0' && *s <= '9' ) {
1176         while( *s >= '0' && *s <= '9' ) {
1177             *value = *value * 10 + (*s - '0');
1178             s++;
1179         }
1180
1181         result = 0;
1182     }
1183
1184     *str = s;
1185
1186     return result;
1187 }
1188
1189 int
1190 NextTimeControlFromString (char ** str, long * value)
1191 {
1192     long temp;
1193     int result = NextIntegerFromString( str, &temp );
1194
1195     if( result == 0 ) {
1196         *value = temp * 60; /* Minutes */
1197         if( **str == ':' ) {
1198             (*str)++;
1199             result = NextIntegerFromString( str, &temp );
1200             *value += temp; /* Seconds */
1201         }
1202     }
1203
1204     return result;
1205 }
1206
1207 int
1208 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1209 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1210     int result = -1, type = 0; long temp, temp2;
1211
1212     if(**str != ':') return -1; // old params remain in force!
1213     (*str)++;
1214     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1215     if( NextIntegerFromString( str, &temp ) ) return -1;
1216     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1217
1218     if(**str != '/') {
1219         /* time only: incremental or sudden-death time control */
1220         if(**str == '+') { /* increment follows; read it */
1221             (*str)++;
1222             if(**str == '!') type = *(*str)++; // Bronstein TC
1223             if(result = NextIntegerFromString( str, &temp2)) return -1;
1224             *inc = temp2 * 1000;
1225             if(**str == '.') { // read fraction of increment
1226                 char *start = ++(*str);
1227                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1228                 temp2 *= 1000;
1229                 while(start++ < *str) temp2 /= 10;
1230                 *inc += temp2;
1231             }
1232         } else *inc = 0;
1233         *moves = 0; *tc = temp * 1000; *incType = type;
1234         return 0;
1235     }
1236
1237     (*str)++; /* classical time control */
1238     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1239
1240     if(result == 0) {
1241         *moves = temp;
1242         *tc    = temp2 * 1000;
1243         *inc   = 0;
1244         *incType = type;
1245     }
1246     return result;
1247 }
1248
1249 int
1250 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1251 {   /* [HGM] get time to add from the multi-session time-control string */
1252     int incType, moves=1; /* kludge to force reading of first session */
1253     long time, increment;
1254     char *s = tcString;
1255
1256     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1257     do {
1258         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1259         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1260         if(movenr == -1) return time;    /* last move before new session     */
1261         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1262         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1263         if(!moves) return increment;     /* current session is incremental   */
1264         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1265     } while(movenr >= -1);               /* try again for next session       */
1266
1267     return 0; // no new time quota on this move
1268 }
1269
1270 int
1271 ParseTimeControl (char *tc, float ti, int mps)
1272 {
1273   long tc1;
1274   long tc2;
1275   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1276   int min, sec=0;
1277
1278   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1279   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1280       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1281   if(ti > 0) {
1282
1283     if(mps)
1284       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1285     else 
1286       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1287   } else {
1288     if(mps)
1289       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1290     else 
1291       snprintf(buf, MSG_SIZ, ":%s", mytc);
1292   }
1293   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1294   
1295   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1296     return FALSE;
1297   }
1298
1299   if( *tc == '/' ) {
1300     /* Parse second time control */
1301     tc++;
1302
1303     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1304       return FALSE;
1305     }
1306
1307     if( tc2 == 0 ) {
1308       return FALSE;
1309     }
1310
1311     timeControl_2 = tc2 * 1000;
1312   }
1313   else {
1314     timeControl_2 = 0;
1315   }
1316
1317   if( tc1 == 0 ) {
1318     return FALSE;
1319   }
1320
1321   timeControl = tc1 * 1000;
1322
1323   if (ti >= 0) {
1324     timeIncrement = ti * 1000;  /* convert to ms */
1325     movesPerSession = 0;
1326   } else {
1327     timeIncrement = 0;
1328     movesPerSession = mps;
1329   }
1330   return TRUE;
1331 }
1332
1333 void
1334 InitBackEnd2 ()
1335 {
1336     if (appData.debugMode) {
1337         fprintf(debugFP, "%s\n", programVersion);
1338     }
1339     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1340
1341     set_cont_sequence(appData.wrapContSeq);
1342     if (appData.matchGames > 0) {
1343         appData.matchMode = TRUE;
1344     } else if (appData.matchMode) {
1345         appData.matchGames = 1;
1346     }
1347     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1348         appData.matchGames = appData.sameColorGames;
1349     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1350         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1351         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1352     }
1353     Reset(TRUE, FALSE);
1354     if (appData.noChessProgram || first.protocolVersion == 1) {
1355       InitBackEnd3();
1356     } else {
1357       /* kludge: allow timeout for initial "feature" commands */
1358       FreezeUI();
1359       DisplayMessage("", _("Starting chess program"));
1360       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1361     }
1362 }
1363
1364 int
1365 CalculateIndex (int index, int gameNr)
1366 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1367     int res;
1368     if(index > 0) return index; // fixed nmber
1369     if(index == 0) return 1;
1370     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1371     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1372     return res;
1373 }
1374
1375 int
1376 LoadGameOrPosition (int gameNr)
1377 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1378     if (*appData.loadGameFile != NULLCHAR) {
1379         if (!LoadGameFromFile(appData.loadGameFile,
1380                 CalculateIndex(appData.loadGameIndex, gameNr),
1381                               appData.loadGameFile, FALSE)) {
1382             DisplayFatalError(_("Bad game file"), 0, 1);
1383             return 0;
1384         }
1385     } else if (*appData.loadPositionFile != NULLCHAR) {
1386         if (!LoadPositionFromFile(appData.loadPositionFile,
1387                 CalculateIndex(appData.loadPositionIndex, gameNr),
1388                                   appData.loadPositionFile)) {
1389             DisplayFatalError(_("Bad position file"), 0, 1);
1390             return 0;
1391         }
1392     }
1393     return 1;
1394 }
1395
1396 void
1397 ReserveGame (int gameNr, char resChar)
1398 {
1399     FILE *tf = fopen(appData.tourneyFile, "r+");
1400     char *p, *q, c, buf[MSG_SIZ];
1401     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1402     safeStrCpy(buf, lastMsg, MSG_SIZ);
1403     DisplayMessage(_("Pick new game"), "");
1404     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1405     ParseArgsFromFile(tf);
1406     p = q = appData.results;
1407     if(appData.debugMode) {
1408       char *r = appData.participants;
1409       fprintf(debugFP, "results = '%s'\n", p);
1410       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1411       fprintf(debugFP, "\n");
1412     }
1413     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1414     nextGame = q - p;
1415     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1416     safeStrCpy(q, p, strlen(p) + 2);
1417     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1418     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1419     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1420         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1421         q[nextGame] = '*';
1422     }
1423     fseek(tf, -(strlen(p)+4), SEEK_END);
1424     c = fgetc(tf);
1425     if(c != '"') // depending on DOS or Unix line endings we can be one off
1426          fseek(tf, -(strlen(p)+2), SEEK_END);
1427     else fseek(tf, -(strlen(p)+3), SEEK_END);
1428     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1429     DisplayMessage(buf, "");
1430     free(p); appData.results = q;
1431     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1432        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1433       int round = appData.defaultMatchGames * appData.tourneyType;
1434       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1435          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1436         UnloadEngine(&first);  // next game belongs to other pairing;
1437         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1438     }
1439     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1440 }
1441
1442 void
1443 MatchEvent (int mode)
1444 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1445         int dummy;
1446         if(matchMode) { // already in match mode: switch it off
1447             abortMatch = TRUE;
1448             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1449             return;
1450         }
1451 //      if(gameMode != BeginningOfGame) {
1452 //          DisplayError(_("You can only start a match from the initial position."), 0);
1453 //          return;
1454 //      }
1455         abortMatch = FALSE;
1456         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1457         /* Set up machine vs. machine match */
1458         nextGame = 0;
1459         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1460         if(appData.tourneyFile[0]) {
1461             ReserveGame(-1, 0);
1462             if(nextGame > appData.matchGames) {
1463                 char buf[MSG_SIZ];
1464                 if(strchr(appData.results, '*') == NULL) {
1465                     FILE *f;
1466                     appData.tourneyCycles++;
1467                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1468                         fclose(f);
1469                         NextTourneyGame(-1, &dummy);
1470                         ReserveGame(-1, 0);
1471                         if(nextGame <= appData.matchGames) {
1472                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1473                             matchMode = mode;
1474                             ScheduleDelayedEvent(NextMatchGame, 10000);
1475                             return;
1476                         }
1477                     }
1478                 }
1479                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1480                 DisplayError(buf, 0);
1481                 appData.tourneyFile[0] = 0;
1482                 return;
1483             }
1484         } else
1485         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1486             DisplayFatalError(_("Can't have a match with no chess programs"),
1487                               0, 2);
1488             return;
1489         }
1490         matchMode = mode;
1491         matchGame = roundNr = 1;
1492         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1493         NextMatchGame();
1494 }
1495
1496 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1497
1498 void
1499 InitBackEnd3 P((void))
1500 {
1501     GameMode initialMode;
1502     char buf[MSG_SIZ];
1503     int err, len;
1504
1505     InitChessProgram(&first, startedFromSetupPosition);
1506
1507     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1508         free(programVersion);
1509         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1510         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1511         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1512     }
1513
1514     if (appData.icsActive) {
1515 #ifdef WIN32
1516         /* [DM] Make a console window if needed [HGM] merged ifs */
1517         ConsoleCreate();
1518 #endif
1519         err = establish();
1520         if (err != 0)
1521           {
1522             if (*appData.icsCommPort != NULLCHAR)
1523               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1524                              appData.icsCommPort);
1525             else
1526               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1527                         appData.icsHost, appData.icsPort);
1528
1529             if( (len >= MSG_SIZ) && appData.debugMode )
1530               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1531
1532             DisplayFatalError(buf, err, 1);
1533             return;
1534         }
1535         SetICSMode();
1536         telnetISR =
1537           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1538         fromUserISR =
1539           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1540         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1541             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1542     } else if (appData.noChessProgram) {
1543         SetNCPMode();
1544     } else {
1545         SetGNUMode();
1546     }
1547
1548     if (*appData.cmailGameName != NULLCHAR) {
1549         SetCmailMode();
1550         OpenLoopback(&cmailPR);
1551         cmailISR =
1552           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1553     }
1554
1555     ThawUI();
1556     DisplayMessage("", "");
1557     if (StrCaseCmp(appData.initialMode, "") == 0) {
1558       initialMode = BeginningOfGame;
1559       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1560         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1561         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1562         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1563         ModeHighlight();
1564       }
1565     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1566       initialMode = TwoMachinesPlay;
1567     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1568       initialMode = AnalyzeFile;
1569     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1570       initialMode = AnalyzeMode;
1571     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1572       initialMode = MachinePlaysWhite;
1573     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1574       initialMode = MachinePlaysBlack;
1575     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1576       initialMode = EditGame;
1577     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1578       initialMode = EditPosition;
1579     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1580       initialMode = Training;
1581     } else {
1582       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1583       if( (len >= MSG_SIZ) && appData.debugMode )
1584         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1585
1586       DisplayFatalError(buf, 0, 2);
1587       return;
1588     }
1589
1590     if (appData.matchMode) {
1591         if(appData.tourneyFile[0]) { // start tourney from command line
1592             FILE *f;
1593             if(f = fopen(appData.tourneyFile, "r")) {
1594                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1595                 fclose(f);
1596                 appData.clockMode = TRUE;
1597                 SetGNUMode();
1598             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1599         }
1600         MatchEvent(TRUE);
1601     } else if (*appData.cmailGameName != NULLCHAR) {
1602         /* Set up cmail mode */
1603         ReloadCmailMsgEvent(TRUE);
1604     } else {
1605         /* Set up other modes */
1606         if (initialMode == AnalyzeFile) {
1607           if (*appData.loadGameFile == NULLCHAR) {
1608             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1609             return;
1610           }
1611         }
1612         if (*appData.loadGameFile != NULLCHAR) {
1613             (void) LoadGameFromFile(appData.loadGameFile,
1614                                     appData.loadGameIndex,
1615                                     appData.loadGameFile, TRUE);
1616         } else if (*appData.loadPositionFile != NULLCHAR) {
1617             (void) LoadPositionFromFile(appData.loadPositionFile,
1618                                         appData.loadPositionIndex,
1619                                         appData.loadPositionFile);
1620             /* [HGM] try to make self-starting even after FEN load */
1621             /* to allow automatic setup of fairy variants with wtm */
1622             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1623                 gameMode = BeginningOfGame;
1624                 setboardSpoiledMachineBlack = 1;
1625             }
1626             /* [HGM] loadPos: make that every new game uses the setup */
1627             /* from file as long as we do not switch variant          */
1628             if(!blackPlaysFirst) {
1629                 startedFromPositionFile = TRUE;
1630                 CopyBoard(filePosition, boards[0]);
1631             }
1632         }
1633         if (initialMode == AnalyzeMode) {
1634           if (appData.noChessProgram) {
1635             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1636             return;
1637           }
1638           if (appData.icsActive) {
1639             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1640             return;
1641           }
1642           AnalyzeModeEvent();
1643         } else if (initialMode == AnalyzeFile) {
1644           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1645           ShowThinkingEvent();
1646           AnalyzeFileEvent();
1647           AnalysisPeriodicEvent(1);
1648         } else if (initialMode == MachinePlaysWhite) {
1649           if (appData.noChessProgram) {
1650             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1651                               0, 2);
1652             return;
1653           }
1654           if (appData.icsActive) {
1655             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1656                               0, 2);
1657             return;
1658           }
1659           MachineWhiteEvent();
1660         } else if (initialMode == MachinePlaysBlack) {
1661           if (appData.noChessProgram) {
1662             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1663                               0, 2);
1664             return;
1665           }
1666           if (appData.icsActive) {
1667             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1668                               0, 2);
1669             return;
1670           }
1671           MachineBlackEvent();
1672         } else if (initialMode == TwoMachinesPlay) {
1673           if (appData.noChessProgram) {
1674             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1675                               0, 2);
1676             return;
1677           }
1678           if (appData.icsActive) {
1679             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1680                               0, 2);
1681             return;
1682           }
1683           TwoMachinesEvent();
1684         } else if (initialMode == EditGame) {
1685           EditGameEvent();
1686         } else if (initialMode == EditPosition) {
1687           EditPositionEvent();
1688         } else if (initialMode == Training) {
1689           if (*appData.loadGameFile == NULLCHAR) {
1690             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1691             return;
1692           }
1693           TrainingEvent();
1694         }
1695     }
1696 }
1697
1698 void
1699 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1700 {
1701     DisplayBook(current+1);
1702
1703     MoveHistorySet( movelist, first, last, current, pvInfoList );
1704
1705     EvalGraphSet( first, last, current, pvInfoList );
1706
1707     MakeEngineOutputTitle();
1708 }
1709
1710 /*
1711  * Establish will establish a contact to a remote host.port.
1712  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1713  *  used to talk to the host.
1714  * Returns 0 if okay, error code if not.
1715  */
1716 int
1717 establish ()
1718 {
1719     char buf[MSG_SIZ];
1720
1721     if (*appData.icsCommPort != NULLCHAR) {
1722         /* Talk to the host through a serial comm port */
1723         return OpenCommPort(appData.icsCommPort, &icsPR);
1724
1725     } else if (*appData.gateway != NULLCHAR) {
1726         if (*appData.remoteShell == NULLCHAR) {
1727             /* Use the rcmd protocol to run telnet program on a gateway host */
1728             snprintf(buf, sizeof(buf), "%s %s %s",
1729                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1730             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1731
1732         } else {
1733             /* Use the rsh program to run telnet program on a gateway host */
1734             if (*appData.remoteUser == NULLCHAR) {
1735                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1736                         appData.gateway, appData.telnetProgram,
1737                         appData.icsHost, appData.icsPort);
1738             } else {
1739                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1740                         appData.remoteShell, appData.gateway,
1741                         appData.remoteUser, appData.telnetProgram,
1742                         appData.icsHost, appData.icsPort);
1743             }
1744             return StartChildProcess(buf, "", &icsPR);
1745
1746         }
1747     } else if (appData.useTelnet) {
1748         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1749
1750     } else {
1751         /* TCP socket interface differs somewhat between
1752            Unix and NT; handle details in the front end.
1753            */
1754         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1755     }
1756 }
1757
1758 void
1759 EscapeExpand (char *p, char *q)
1760 {       // [HGM] initstring: routine to shape up string arguments
1761         while(*p++ = *q++) if(p[-1] == '\\')
1762             switch(*q++) {
1763                 case 'n': p[-1] = '\n'; break;
1764                 case 'r': p[-1] = '\r'; break;
1765                 case 't': p[-1] = '\t'; break;
1766                 case '\\': p[-1] = '\\'; break;
1767                 case 0: *p = 0; return;
1768                 default: p[-1] = q[-1]; break;
1769             }
1770 }
1771
1772 void
1773 show_bytes (FILE *fp, char *buf, int count)
1774 {
1775     while (count--) {
1776         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1777             fprintf(fp, "\\%03o", *buf & 0xff);
1778         } else {
1779             putc(*buf, fp);
1780         }
1781         buf++;
1782     }
1783     fflush(fp);
1784 }
1785
1786 /* Returns an errno value */
1787 int
1788 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1789 {
1790     char buf[8192], *p, *q, *buflim;
1791     int left, newcount, outcount;
1792
1793     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1794         *appData.gateway != NULLCHAR) {
1795         if (appData.debugMode) {
1796             fprintf(debugFP, ">ICS: ");
1797             show_bytes(debugFP, message, count);
1798             fprintf(debugFP, "\n");
1799         }
1800         return OutputToProcess(pr, message, count, outError);
1801     }
1802
1803     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1804     p = message;
1805     q = buf;
1806     left = count;
1807     newcount = 0;
1808     while (left) {
1809         if (q >= buflim) {
1810             if (appData.debugMode) {
1811                 fprintf(debugFP, ">ICS: ");
1812                 show_bytes(debugFP, buf, newcount);
1813                 fprintf(debugFP, "\n");
1814             }
1815             outcount = OutputToProcess(pr, buf, newcount, outError);
1816             if (outcount < newcount) return -1; /* to be sure */
1817             q = buf;
1818             newcount = 0;
1819         }
1820         if (*p == '\n') {
1821             *q++ = '\r';
1822             newcount++;
1823         } else if (((unsigned char) *p) == TN_IAC) {
1824             *q++ = (char) TN_IAC;
1825             newcount ++;
1826         }
1827         *q++ = *p++;
1828         newcount++;
1829         left--;
1830     }
1831     if (appData.debugMode) {
1832         fprintf(debugFP, ">ICS: ");
1833         show_bytes(debugFP, buf, newcount);
1834         fprintf(debugFP, "\n");
1835     }
1836     outcount = OutputToProcess(pr, buf, newcount, outError);
1837     if (outcount < newcount) return -1; /* to be sure */
1838     return count;
1839 }
1840
1841 void
1842 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1843 {
1844     int outError, outCount;
1845     static int gotEof = 0;
1846
1847     /* Pass data read from player on to ICS */
1848     if (count > 0) {
1849         gotEof = 0;
1850         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1851         if (outCount < count) {
1852             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1853         }
1854     } else if (count < 0) {
1855         RemoveInputSource(isr);
1856         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1857     } else if (gotEof++ > 0) {
1858         RemoveInputSource(isr);
1859         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1860     }
1861 }
1862
1863 void
1864 KeepAlive ()
1865 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1866     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1867     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1868     SendToICS("date\n");
1869     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1870 }
1871
1872 /* added routine for printf style output to ics */
1873 void
1874 ics_printf (char *format, ...)
1875 {
1876     char buffer[MSG_SIZ];
1877     va_list args;
1878
1879     va_start(args, format);
1880     vsnprintf(buffer, sizeof(buffer), format, args);
1881     buffer[sizeof(buffer)-1] = '\0';
1882     SendToICS(buffer);
1883     va_end(args);
1884 }
1885
1886 void
1887 SendToICS (char *s)
1888 {
1889     int count, outCount, outError;
1890
1891     if (icsPR == NoProc) return;
1892
1893     count = strlen(s);
1894     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1895     if (outCount < count) {
1896         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1897     }
1898 }
1899
1900 /* This is used for sending logon scripts to the ICS. Sending
1901    without a delay causes problems when using timestamp on ICC
1902    (at least on my machine). */
1903 void
1904 SendToICSDelayed (char *s, long msdelay)
1905 {
1906     int count, outCount, outError;
1907
1908     if (icsPR == NoProc) return;
1909
1910     count = strlen(s);
1911     if (appData.debugMode) {
1912         fprintf(debugFP, ">ICS: ");
1913         show_bytes(debugFP, s, count);
1914         fprintf(debugFP, "\n");
1915     }
1916     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1917                                       msdelay);
1918     if (outCount < count) {
1919         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1920     }
1921 }
1922
1923
1924 /* Remove all highlighting escape sequences in s
1925    Also deletes any suffix starting with '('
1926    */
1927 char *
1928 StripHighlightAndTitle (char *s)
1929 {
1930     static char retbuf[MSG_SIZ];
1931     char *p = retbuf;
1932
1933     while (*s != NULLCHAR) {
1934         while (*s == '\033') {
1935             while (*s != NULLCHAR && !isalpha(*s)) s++;
1936             if (*s != NULLCHAR) s++;
1937         }
1938         while (*s != NULLCHAR && *s != '\033') {
1939             if (*s == '(' || *s == '[') {
1940                 *p = NULLCHAR;
1941                 return retbuf;
1942             }
1943             *p++ = *s++;
1944         }
1945     }
1946     *p = NULLCHAR;
1947     return retbuf;
1948 }
1949
1950 /* Remove all highlighting escape sequences in s */
1951 char *
1952 StripHighlight (char *s)
1953 {
1954     static char retbuf[MSG_SIZ];
1955     char *p = retbuf;
1956
1957     while (*s != NULLCHAR) {
1958         while (*s == '\033') {
1959             while (*s != NULLCHAR && !isalpha(*s)) s++;
1960             if (*s != NULLCHAR) s++;
1961         }
1962         while (*s != NULLCHAR && *s != '\033') {
1963             *p++ = *s++;
1964         }
1965     }
1966     *p = NULLCHAR;
1967     return retbuf;
1968 }
1969
1970 char *variantNames[] = VARIANT_NAMES;
1971 char *
1972 VariantName (VariantClass v)
1973 {
1974     return variantNames[v];
1975 }
1976
1977
1978 /* Identify a variant from the strings the chess servers use or the
1979    PGN Variant tag names we use. */
1980 VariantClass
1981 StringToVariant (char *e)
1982 {
1983     char *p;
1984     int wnum = -1;
1985     VariantClass v = VariantNormal;
1986     int i, found = FALSE;
1987     char buf[MSG_SIZ];
1988     int len;
1989
1990     if (!e) return v;
1991
1992     /* [HGM] skip over optional board-size prefixes */
1993     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1994         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1995         while( *e++ != '_');
1996     }
1997
1998     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1999         v = VariantNormal;
2000         found = TRUE;
2001     } else
2002     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2003       if (StrCaseStr(e, variantNames[i])) {
2004         v = (VariantClass) i;
2005         found = TRUE;
2006         break;
2007       }
2008     }
2009
2010     if (!found) {
2011       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2012           || StrCaseStr(e, "wild/fr")
2013           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2014         v = VariantFischeRandom;
2015       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2016                  (i = 1, p = StrCaseStr(e, "w"))) {
2017         p += i;
2018         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2019         if (isdigit(*p)) {
2020           wnum = atoi(p);
2021         } else {
2022           wnum = -1;
2023         }
2024         switch (wnum) {
2025         case 0: /* FICS only, actually */
2026         case 1:
2027           /* Castling legal even if K starts on d-file */
2028           v = VariantWildCastle;
2029           break;
2030         case 2:
2031         case 3:
2032         case 4:
2033           /* Castling illegal even if K & R happen to start in
2034              normal positions. */
2035           v = VariantNoCastle;
2036           break;
2037         case 5:
2038         case 7:
2039         case 8:
2040         case 10:
2041         case 11:
2042         case 12:
2043         case 13:
2044         case 14:
2045         case 15:
2046         case 18:
2047         case 19:
2048           /* Castling legal iff K & R start in normal positions */
2049           v = VariantNormal;
2050           break;
2051         case 6:
2052         case 20:
2053         case 21:
2054           /* Special wilds for position setup; unclear what to do here */
2055           v = VariantLoadable;
2056           break;
2057         case 9:
2058           /* Bizarre ICC game */
2059           v = VariantTwoKings;
2060           break;
2061         case 16:
2062           v = VariantKriegspiel;
2063           break;
2064         case 17:
2065           v = VariantLosers;
2066           break;
2067         case 22:
2068           v = VariantFischeRandom;
2069           break;
2070         case 23:
2071           v = VariantCrazyhouse;
2072           break;
2073         case 24:
2074           v = VariantBughouse;
2075           break;
2076         case 25:
2077           v = Variant3Check;
2078           break;
2079         case 26:
2080           /* Not quite the same as FICS suicide! */
2081           v = VariantGiveaway;
2082           break;
2083         case 27:
2084           v = VariantAtomic;
2085           break;
2086         case 28:
2087           v = VariantShatranj;
2088           break;
2089
2090         /* Temporary names for future ICC types.  The name *will* change in
2091            the next xboard/WinBoard release after ICC defines it. */
2092         case 29:
2093           v = Variant29;
2094           break;
2095         case 30:
2096           v = Variant30;
2097           break;
2098         case 31:
2099           v = Variant31;
2100           break;
2101         case 32:
2102           v = Variant32;
2103           break;
2104         case 33:
2105           v = Variant33;
2106           break;
2107         case 34:
2108           v = Variant34;
2109           break;
2110         case 35:
2111           v = Variant35;
2112           break;
2113         case 36:
2114           v = Variant36;
2115           break;
2116         case 37:
2117           v = VariantShogi;
2118           break;
2119         case 38:
2120           v = VariantXiangqi;
2121           break;
2122         case 39:
2123           v = VariantCourier;
2124           break;
2125         case 40:
2126           v = VariantGothic;
2127           break;
2128         case 41:
2129           v = VariantCapablanca;
2130           break;
2131         case 42:
2132           v = VariantKnightmate;
2133           break;
2134         case 43:
2135           v = VariantFairy;
2136           break;
2137         case 44:
2138           v = VariantCylinder;
2139           break;
2140         case 45:
2141           v = VariantFalcon;
2142           break;
2143         case 46:
2144           v = VariantCapaRandom;
2145           break;
2146         case 47:
2147           v = VariantBerolina;
2148           break;
2149         case 48:
2150           v = VariantJanus;
2151           break;
2152         case 49:
2153           v = VariantSuper;
2154           break;
2155         case 50:
2156           v = VariantGreat;
2157           break;
2158         case -1:
2159           /* Found "wild" or "w" in the string but no number;
2160              must assume it's normal chess. */
2161           v = VariantNormal;
2162           break;
2163         default:
2164           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2165           if( (len >= MSG_SIZ) && appData.debugMode )
2166             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2167
2168           DisplayError(buf, 0);
2169           v = VariantUnknown;
2170           break;
2171         }
2172       }
2173     }
2174     if (appData.debugMode) {
2175       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2176               e, wnum, VariantName(v));
2177     }
2178     return v;
2179 }
2180
2181 static int leftover_start = 0, leftover_len = 0;
2182 char star_match[STAR_MATCH_N][MSG_SIZ];
2183
2184 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2185    advance *index beyond it, and set leftover_start to the new value of
2186    *index; else return FALSE.  If pattern contains the character '*', it
2187    matches any sequence of characters not containing '\r', '\n', or the
2188    character following the '*' (if any), and the matched sequence(s) are
2189    copied into star_match.
2190    */
2191 int
2192 looking_at ( char *buf, int *index, char *pattern)
2193 {
2194     char *bufp = &buf[*index], *patternp = pattern;
2195     int star_count = 0;
2196     char *matchp = star_match[0];
2197
2198     for (;;) {
2199         if (*patternp == NULLCHAR) {
2200             *index = leftover_start = bufp - buf;
2201             *matchp = NULLCHAR;
2202             return TRUE;
2203         }
2204         if (*bufp == NULLCHAR) return FALSE;
2205         if (*patternp == '*') {
2206             if (*bufp == *(patternp + 1)) {
2207                 *matchp = NULLCHAR;
2208                 matchp = star_match[++star_count];
2209                 patternp += 2;
2210                 bufp++;
2211                 continue;
2212             } else if (*bufp == '\n' || *bufp == '\r') {
2213                 patternp++;
2214                 if (*patternp == NULLCHAR)
2215                   continue;
2216                 else
2217                   return FALSE;
2218             } else {
2219                 *matchp++ = *bufp++;
2220                 continue;
2221             }
2222         }
2223         if (*patternp != *bufp) return FALSE;
2224         patternp++;
2225         bufp++;
2226     }
2227 }
2228
2229 void
2230 SendToPlayer (char *data, int length)
2231 {
2232     int error, outCount;
2233     outCount = OutputToProcess(NoProc, data, length, &error);
2234     if (outCount < length) {
2235         DisplayFatalError(_("Error writing to display"), error, 1);
2236     }
2237 }
2238
2239 void
2240 PackHolding (char packed[], char *holding)
2241 {
2242     char *p = holding;
2243     char *q = packed;
2244     int runlength = 0;
2245     int curr = 9999;
2246     do {
2247         if (*p == curr) {
2248             runlength++;
2249         } else {
2250             switch (runlength) {
2251               case 0:
2252                 break;
2253               case 1:
2254                 *q++ = curr;
2255                 break;
2256               case 2:
2257                 *q++ = curr;
2258                 *q++ = curr;
2259                 break;
2260               default:
2261                 sprintf(q, "%d", runlength);
2262                 while (*q) q++;
2263                 *q++ = curr;
2264                 break;
2265             }
2266             runlength = 1;
2267             curr = *p;
2268         }
2269     } while (*p++);
2270     *q = NULLCHAR;
2271 }
2272
2273 /* Telnet protocol requests from the front end */
2274 void
2275 TelnetRequest (unsigned char ddww, unsigned char option)
2276 {
2277     unsigned char msg[3];
2278     int outCount, outError;
2279
2280     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2281
2282     if (appData.debugMode) {
2283         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2284         switch (ddww) {
2285           case TN_DO:
2286             ddwwStr = "DO";
2287             break;
2288           case TN_DONT:
2289             ddwwStr = "DONT";
2290             break;
2291           case TN_WILL:
2292             ddwwStr = "WILL";
2293             break;
2294           case TN_WONT:
2295             ddwwStr = "WONT";
2296             break;
2297           default:
2298             ddwwStr = buf1;
2299             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2300             break;
2301         }
2302         switch (option) {
2303           case TN_ECHO:
2304             optionStr = "ECHO";
2305             break;
2306           default:
2307             optionStr = buf2;
2308             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2309             break;
2310         }
2311         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2312     }
2313     msg[0] = TN_IAC;
2314     msg[1] = ddww;
2315     msg[2] = option;
2316     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2317     if (outCount < 3) {
2318         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2319     }
2320 }
2321
2322 void
2323 DoEcho ()
2324 {
2325     if (!appData.icsActive) return;
2326     TelnetRequest(TN_DO, TN_ECHO);
2327 }
2328
2329 void
2330 DontEcho ()
2331 {
2332     if (!appData.icsActive) return;
2333     TelnetRequest(TN_DONT, TN_ECHO);
2334 }
2335
2336 void
2337 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2338 {
2339     /* put the holdings sent to us by the server on the board holdings area */
2340     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2341     char p;
2342     ChessSquare piece;
2343
2344     if(gameInfo.holdingsWidth < 2)  return;
2345     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2346         return; // prevent overwriting by pre-board holdings
2347
2348     if( (int)lowestPiece >= BlackPawn ) {
2349         holdingsColumn = 0;
2350         countsColumn = 1;
2351         holdingsStartRow = BOARD_HEIGHT-1;
2352         direction = -1;
2353     } else {
2354         holdingsColumn = BOARD_WIDTH-1;
2355         countsColumn = BOARD_WIDTH-2;
2356         holdingsStartRow = 0;
2357         direction = 1;
2358     }
2359
2360     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2361         board[i][holdingsColumn] = EmptySquare;
2362         board[i][countsColumn]   = (ChessSquare) 0;
2363     }
2364     while( (p=*holdings++) != NULLCHAR ) {
2365         piece = CharToPiece( ToUpper(p) );
2366         if(piece == EmptySquare) continue;
2367         /*j = (int) piece - (int) WhitePawn;*/
2368         j = PieceToNumber(piece);
2369         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2370         if(j < 0) continue;               /* should not happen */
2371         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2372         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2373         board[holdingsStartRow+j*direction][countsColumn]++;
2374     }
2375 }
2376
2377
2378 void
2379 VariantSwitch (Board board, VariantClass newVariant)
2380 {
2381    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2382    static Board oldBoard;
2383
2384    startedFromPositionFile = FALSE;
2385    if(gameInfo.variant == newVariant) return;
2386
2387    /* [HGM] This routine is called each time an assignment is made to
2388     * gameInfo.variant during a game, to make sure the board sizes
2389     * are set to match the new variant. If that means adding or deleting
2390     * holdings, we shift the playing board accordingly
2391     * This kludge is needed because in ICS observe mode, we get boards
2392     * of an ongoing game without knowing the variant, and learn about the
2393     * latter only later. This can be because of the move list we requested,
2394     * in which case the game history is refilled from the beginning anyway,
2395     * but also when receiving holdings of a crazyhouse game. In the latter
2396     * case we want to add those holdings to the already received position.
2397     */
2398
2399
2400    if (appData.debugMode) {
2401      fprintf(debugFP, "Switch board from %s to %s\n",
2402              VariantName(gameInfo.variant), VariantName(newVariant));
2403      setbuf(debugFP, NULL);
2404    }
2405    shuffleOpenings = 0;       /* [HGM] shuffle */
2406    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2407    switch(newVariant)
2408      {
2409      case VariantShogi:
2410        newWidth = 9;  newHeight = 9;
2411        gameInfo.holdingsSize = 7;
2412      case VariantBughouse:
2413      case VariantCrazyhouse:
2414        newHoldingsWidth = 2; break;
2415      case VariantGreat:
2416        newWidth = 10;
2417      case VariantSuper:
2418        newHoldingsWidth = 2;
2419        gameInfo.holdingsSize = 8;
2420        break;
2421      case VariantGothic:
2422      case VariantCapablanca:
2423      case VariantCapaRandom:
2424        newWidth = 10;
2425      default:
2426        newHoldingsWidth = gameInfo.holdingsSize = 0;
2427      };
2428
2429    if(newWidth  != gameInfo.boardWidth  ||
2430       newHeight != gameInfo.boardHeight ||
2431       newHoldingsWidth != gameInfo.holdingsWidth ) {
2432
2433      /* shift position to new playing area, if needed */
2434      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2435        for(i=0; i<BOARD_HEIGHT; i++)
2436          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2437            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2438              board[i][j];
2439        for(i=0; i<newHeight; i++) {
2440          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2441          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2442        }
2443      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2444        for(i=0; i<BOARD_HEIGHT; i++)
2445          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2446            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2447              board[i][j];
2448      }
2449      board[HOLDINGS_SET] = 0;
2450      gameInfo.boardWidth  = newWidth;
2451      gameInfo.boardHeight = newHeight;
2452      gameInfo.holdingsWidth = newHoldingsWidth;
2453      gameInfo.variant = newVariant;
2454      InitDrawingSizes(-2, 0);
2455    } else gameInfo.variant = newVariant;
2456    CopyBoard(oldBoard, board);   // remember correctly formatted board
2457      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2458    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2459 }
2460
2461 static int loggedOn = FALSE;
2462
2463 /*-- Game start info cache: --*/
2464 int gs_gamenum;
2465 char gs_kind[MSG_SIZ];
2466 static char player1Name[128] = "";
2467 static char player2Name[128] = "";
2468 static char cont_seq[] = "\n\\   ";
2469 static int player1Rating = -1;
2470 static int player2Rating = -1;
2471 /*----------------------------*/
2472
2473 ColorClass curColor = ColorNormal;
2474 int suppressKibitz = 0;
2475
2476 // [HGM] seekgraph
2477 Boolean soughtPending = FALSE;
2478 Boolean seekGraphUp;
2479 #define MAX_SEEK_ADS 200
2480 #define SQUARE 0x80
2481 char *seekAdList[MAX_SEEK_ADS];
2482 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2483 float tcList[MAX_SEEK_ADS];
2484 char colorList[MAX_SEEK_ADS];
2485 int nrOfSeekAds = 0;
2486 int minRating = 1010, maxRating = 2800;
2487 int hMargin = 10, vMargin = 20, h, w;
2488 extern int squareSize, lineGap;
2489
2490 void
2491 PlotSeekAd (int i)
2492 {
2493         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2494         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2495         if(r < minRating+100 && r >=0 ) r = minRating+100;
2496         if(r > maxRating) r = maxRating;
2497         if(tc < 1.f) tc = 1.f;
2498         if(tc > 95.f) tc = 95.f;
2499         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2500         y = ((double)r - minRating)/(maxRating - minRating)
2501             * (h-vMargin-squareSize/8-1) + vMargin;
2502         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2503         if(strstr(seekAdList[i], " u ")) color = 1;
2504         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2505            !strstr(seekAdList[i], "bullet") &&
2506            !strstr(seekAdList[i], "blitz") &&
2507            !strstr(seekAdList[i], "standard") ) color = 2;
2508         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2509         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2510 }
2511
2512 void
2513 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2514 {
2515         char buf[MSG_SIZ], *ext = "";
2516         VariantClass v = StringToVariant(type);
2517         if(strstr(type, "wild")) {
2518             ext = type + 4; // append wild number
2519             if(v == VariantFischeRandom) type = "chess960"; else
2520             if(v == VariantLoadable) type = "setup"; else
2521             type = VariantName(v);
2522         }
2523         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2524         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2525             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2526             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2527             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2528             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2529             seekNrList[nrOfSeekAds] = nr;
2530             zList[nrOfSeekAds] = 0;
2531             seekAdList[nrOfSeekAds++] = StrSave(buf);
2532             if(plot) PlotSeekAd(nrOfSeekAds-1);
2533         }
2534 }
2535
2536 void
2537 EraseSeekDot (int i)
2538 {
2539     int x = xList[i], y = yList[i], d=squareSize/4, k;
2540     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2541     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2542     // now replot every dot that overlapped
2543     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2544         int xx = xList[k], yy = yList[k];
2545         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2546             DrawSeekDot(xx, yy, colorList[k]);
2547     }
2548 }
2549
2550 void
2551 RemoveSeekAd (int nr)
2552 {
2553         int i;
2554         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2555             EraseSeekDot(i);
2556             if(seekAdList[i]) free(seekAdList[i]);
2557             seekAdList[i] = seekAdList[--nrOfSeekAds];
2558             seekNrList[i] = seekNrList[nrOfSeekAds];
2559             ratingList[i] = ratingList[nrOfSeekAds];
2560             colorList[i]  = colorList[nrOfSeekAds];
2561             tcList[i] = tcList[nrOfSeekAds];
2562             xList[i]  = xList[nrOfSeekAds];
2563             yList[i]  = yList[nrOfSeekAds];
2564             zList[i]  = zList[nrOfSeekAds];
2565             seekAdList[nrOfSeekAds] = NULL;
2566             break;
2567         }
2568 }
2569
2570 Boolean
2571 MatchSoughtLine (char *line)
2572 {
2573     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2574     int nr, base, inc, u=0; char dummy;
2575
2576     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2577        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2578        (u=1) &&
2579        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2580         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2581         // match: compact and save the line
2582         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2583         return TRUE;
2584     }
2585     return FALSE;
2586 }
2587
2588 int
2589 DrawSeekGraph ()
2590 {
2591     int i;
2592     if(!seekGraphUp) return FALSE;
2593     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2594     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2595
2596     DrawSeekBackground(0, 0, w, h);
2597     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2598     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2599     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2600         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2601         yy = h-1-yy;
2602         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2603         if(i%500 == 0) {
2604             char buf[MSG_SIZ];
2605             snprintf(buf, MSG_SIZ, "%d", i);
2606             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2607         }
2608     }
2609     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2610     for(i=1; i<100; i+=(i<10?1:5)) {
2611         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2612         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2613         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2614             char buf[MSG_SIZ];
2615             snprintf(buf, MSG_SIZ, "%d", i);
2616             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2617         }
2618     }
2619     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2620     return TRUE;
2621 }
2622
2623 int
2624 SeekGraphClick (ClickType click, int x, int y, int moving)
2625 {
2626     static int lastDown = 0, displayed = 0, lastSecond;
2627     if(y < 0) return FALSE;
2628     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2629         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2630         if(!seekGraphUp) return FALSE;
2631         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2632         DrawPosition(TRUE, NULL);
2633         return TRUE;
2634     }
2635     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2636         if(click == Release || moving) return FALSE;
2637         nrOfSeekAds = 0;
2638         soughtPending = TRUE;
2639         SendToICS(ics_prefix);
2640         SendToICS("sought\n"); // should this be "sought all"?
2641     } else { // issue challenge based on clicked ad
2642         int dist = 10000; int i, closest = 0, second = 0;
2643         for(i=0; i<nrOfSeekAds; i++) {
2644             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2645             if(d < dist) { dist = d; closest = i; }
2646             second += (d - zList[i] < 120); // count in-range ads
2647             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2648         }
2649         if(dist < 120) {
2650             char buf[MSG_SIZ];
2651             second = (second > 1);
2652             if(displayed != closest || second != lastSecond) {
2653                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2654                 lastSecond = second; displayed = closest;
2655             }
2656             if(click == Press) {
2657                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2658                 lastDown = closest;
2659                 return TRUE;
2660             } // on press 'hit', only show info
2661             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2662             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2663             SendToICS(ics_prefix);
2664             SendToICS(buf);
2665             return TRUE; // let incoming board of started game pop down the graph
2666         } else if(click == Release) { // release 'miss' is ignored
2667             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2668             if(moving == 2) { // right up-click
2669                 nrOfSeekAds = 0; // refresh graph
2670                 soughtPending = TRUE;
2671                 SendToICS(ics_prefix);
2672                 SendToICS("sought\n"); // should this be "sought all"?
2673             }
2674             return TRUE;
2675         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2676         // press miss or release hit 'pop down' seek graph
2677         seekGraphUp = FALSE;
2678         DrawPosition(TRUE, NULL);
2679     }
2680     return TRUE;
2681 }
2682
2683 void
2684 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2685 {
2686 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2687 #define STARTED_NONE 0
2688 #define STARTED_MOVES 1
2689 #define STARTED_BOARD 2
2690 #define STARTED_OBSERVE 3
2691 #define STARTED_HOLDINGS 4
2692 #define STARTED_CHATTER 5
2693 #define STARTED_COMMENT 6
2694 #define STARTED_MOVES_NOHIDE 7
2695
2696     static int started = STARTED_NONE;
2697     static char parse[20000];
2698     static int parse_pos = 0;
2699     static char buf[BUF_SIZE + 1];
2700     static int firstTime = TRUE, intfSet = FALSE;
2701     static ColorClass prevColor = ColorNormal;
2702     static int savingComment = FALSE;
2703     static int cmatch = 0; // continuation sequence match
2704     char *bp;
2705     char str[MSG_SIZ];
2706     int i, oldi;
2707     int buf_len;
2708     int next_out;
2709     int tkind;
2710     int backup;    /* [DM] For zippy color lines */
2711     char *p;
2712     char talker[MSG_SIZ]; // [HGM] chat
2713     int channel;
2714
2715     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2716
2717     if (appData.debugMode) {
2718       if (!error) {
2719         fprintf(debugFP, "<ICS: ");
2720         show_bytes(debugFP, data, count);
2721         fprintf(debugFP, "\n");
2722       }
2723     }
2724
2725     if (appData.debugMode) { int f = forwardMostMove;
2726         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2727                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2728                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2729     }
2730     if (count > 0) {
2731         /* If last read ended with a partial line that we couldn't parse,
2732            prepend it to the new read and try again. */
2733         if (leftover_len > 0) {
2734             for (i=0; i<leftover_len; i++)
2735               buf[i] = buf[leftover_start + i];
2736         }
2737
2738     /* copy new characters into the buffer */
2739     bp = buf + leftover_len;
2740     buf_len=leftover_len;
2741     for (i=0; i<count; i++)
2742     {
2743         // ignore these
2744         if (data[i] == '\r')
2745             continue;
2746
2747         // join lines split by ICS?
2748         if (!appData.noJoin)
2749         {
2750             /*
2751                 Joining just consists of finding matches against the
2752                 continuation sequence, and discarding that sequence
2753                 if found instead of copying it.  So, until a match
2754                 fails, there's nothing to do since it might be the
2755                 complete sequence, and thus, something we don't want
2756                 copied.
2757             */
2758             if (data[i] == cont_seq[cmatch])
2759             {
2760                 cmatch++;
2761                 if (cmatch == strlen(cont_seq))
2762                 {
2763                     cmatch = 0; // complete match.  just reset the counter
2764
2765                     /*
2766                         it's possible for the ICS to not include the space
2767                         at the end of the last word, making our [correct]
2768                         join operation fuse two separate words.  the server
2769                         does this when the space occurs at the width setting.
2770                     */
2771                     if (!buf_len || buf[buf_len-1] != ' ')
2772                     {
2773                         *bp++ = ' ';
2774                         buf_len++;
2775                     }
2776                 }
2777                 continue;
2778             }
2779             else if (cmatch)
2780             {
2781                 /*
2782                     match failed, so we have to copy what matched before
2783                     falling through and copying this character.  In reality,
2784                     this will only ever be just the newline character, but
2785                     it doesn't hurt to be precise.
2786                 */
2787                 strncpy(bp, cont_seq, cmatch);
2788                 bp += cmatch;
2789                 buf_len += cmatch;
2790                 cmatch = 0;
2791             }
2792         }
2793
2794         // copy this char
2795         *bp++ = data[i];
2796         buf_len++;
2797     }
2798
2799         buf[buf_len] = NULLCHAR;
2800 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2801         next_out = 0;
2802         leftover_start = 0;
2803
2804         i = 0;
2805         while (i < buf_len) {
2806             /* Deal with part of the TELNET option negotiation
2807                protocol.  We refuse to do anything beyond the
2808                defaults, except that we allow the WILL ECHO option,
2809                which ICS uses to turn off password echoing when we are
2810                directly connected to it.  We reject this option
2811                if localLineEditing mode is on (always on in xboard)
2812                and we are talking to port 23, which might be a real
2813                telnet server that will try to keep WILL ECHO on permanently.
2814              */
2815             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2816                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2817                 unsigned char option;
2818                 oldi = i;
2819                 switch ((unsigned char) buf[++i]) {
2820                   case TN_WILL:
2821                     if (appData.debugMode)
2822                       fprintf(debugFP, "\n<WILL ");
2823                     switch (option = (unsigned char) buf[++i]) {
2824                       case TN_ECHO:
2825                         if (appData.debugMode)
2826                           fprintf(debugFP, "ECHO ");
2827                         /* Reply only if this is a change, according
2828                            to the protocol rules. */
2829                         if (remoteEchoOption) break;
2830                         if (appData.localLineEditing &&
2831                             atoi(appData.icsPort) == TN_PORT) {
2832                             TelnetRequest(TN_DONT, TN_ECHO);
2833                         } else {
2834                             EchoOff();
2835                             TelnetRequest(TN_DO, TN_ECHO);
2836                             remoteEchoOption = TRUE;
2837                         }
2838                         break;
2839                       default:
2840                         if (appData.debugMode)
2841                           fprintf(debugFP, "%d ", option);
2842                         /* Whatever this is, we don't want it. */
2843                         TelnetRequest(TN_DONT, option);
2844                         break;
2845                     }
2846                     break;
2847                   case TN_WONT:
2848                     if (appData.debugMode)
2849                       fprintf(debugFP, "\n<WONT ");
2850                     switch (option = (unsigned char) buf[++i]) {
2851                       case TN_ECHO:
2852                         if (appData.debugMode)
2853                           fprintf(debugFP, "ECHO ");
2854                         /* Reply only if this is a change, according
2855                            to the protocol rules. */
2856                         if (!remoteEchoOption) break;
2857                         EchoOn();
2858                         TelnetRequest(TN_DONT, TN_ECHO);
2859                         remoteEchoOption = FALSE;
2860                         break;
2861                       default:
2862                         if (appData.debugMode)
2863                           fprintf(debugFP, "%d ", (unsigned char) option);
2864                         /* Whatever this is, it must already be turned
2865                            off, because we never agree to turn on
2866                            anything non-default, so according to the
2867                            protocol rules, we don't reply. */
2868                         break;
2869                     }
2870                     break;
2871                   case TN_DO:
2872                     if (appData.debugMode)
2873                       fprintf(debugFP, "\n<DO ");
2874                     switch (option = (unsigned char) buf[++i]) {
2875                       default:
2876                         /* Whatever this is, we refuse to do it. */
2877                         if (appData.debugMode)
2878                           fprintf(debugFP, "%d ", option);
2879                         TelnetRequest(TN_WONT, option);
2880                         break;
2881                     }
2882                     break;
2883                   case TN_DONT:
2884                     if (appData.debugMode)
2885                       fprintf(debugFP, "\n<DONT ");
2886                     switch (option = (unsigned char) buf[++i]) {
2887                       default:
2888                         if (appData.debugMode)
2889                           fprintf(debugFP, "%d ", option);
2890                         /* Whatever this is, we are already not doing
2891                            it, because we never agree to do anything
2892                            non-default, so according to the protocol
2893                            rules, we don't reply. */
2894                         break;
2895                     }
2896                     break;
2897                   case TN_IAC:
2898                     if (appData.debugMode)
2899                       fprintf(debugFP, "\n<IAC ");
2900                     /* Doubled IAC; pass it through */
2901                     i--;
2902                     break;
2903                   default:
2904                     if (appData.debugMode)
2905                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2906                     /* Drop all other telnet commands on the floor */
2907                     break;
2908                 }
2909                 if (oldi > next_out)
2910                   SendToPlayer(&buf[next_out], oldi - next_out);
2911                 if (++i > next_out)
2912                   next_out = i;
2913                 continue;
2914             }
2915
2916             /* OK, this at least will *usually* work */
2917             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2918                 loggedOn = TRUE;
2919             }
2920
2921             if (loggedOn && !intfSet) {
2922                 if (ics_type == ICS_ICC) {
2923                   snprintf(str, MSG_SIZ,
2924                           "/set-quietly interface %s\n/set-quietly style 12\n",
2925                           programVersion);
2926                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2927                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2928                 } else if (ics_type == ICS_CHESSNET) {
2929                   snprintf(str, MSG_SIZ, "/style 12\n");
2930                 } else {
2931                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2932                   strcat(str, programVersion);
2933                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2934                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2935                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2936 #ifdef WIN32
2937                   strcat(str, "$iset nohighlight 1\n");
2938 #endif
2939                   strcat(str, "$iset lock 1\n$style 12\n");
2940                 }
2941                 SendToICS(str);
2942                 NotifyFrontendLogin();
2943                 intfSet = TRUE;
2944             }
2945
2946             if (started == STARTED_COMMENT) {
2947                 /* Accumulate characters in comment */
2948                 parse[parse_pos++] = buf[i];
2949                 if (buf[i] == '\n') {
2950                     parse[parse_pos] = NULLCHAR;
2951                     if(chattingPartner>=0) {
2952                         char mess[MSG_SIZ];
2953                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2954                         OutputChatMessage(chattingPartner, mess);
2955                         chattingPartner = -1;
2956                         next_out = i+1; // [HGM] suppress printing in ICS window
2957                     } else
2958                     if(!suppressKibitz) // [HGM] kibitz
2959                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2960                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2961                         int nrDigit = 0, nrAlph = 0, j;
2962                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2963                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2964                         parse[parse_pos] = NULLCHAR;
2965                         // try to be smart: if it does not look like search info, it should go to
2966                         // ICS interaction window after all, not to engine-output window.
2967                         for(j=0; j<parse_pos; j++) { // count letters and digits
2968                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2969                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2970                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2971                         }
2972                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2973                             int depth=0; float score;
2974                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2975                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2976                                 pvInfoList[forwardMostMove-1].depth = depth;
2977                                 pvInfoList[forwardMostMove-1].score = 100*score;
2978                             }
2979                             OutputKibitz(suppressKibitz, parse);
2980                         } else {
2981                             char tmp[MSG_SIZ];
2982                             if(gameMode == IcsObserving) // restore original ICS messages
2983                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2984                             else
2985                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2986                             SendToPlayer(tmp, strlen(tmp));
2987                         }
2988                         next_out = i+1; // [HGM] suppress printing in ICS window
2989                     }
2990                     started = STARTED_NONE;
2991                 } else {
2992                     /* Don't match patterns against characters in comment */
2993                     i++;
2994                     continue;
2995                 }
2996             }
2997             if (started == STARTED_CHATTER) {
2998                 if (buf[i] != '\n') {
2999                     /* Don't match patterns against characters in chatter */
3000                     i++;
3001                     continue;
3002                 }
3003                 started = STARTED_NONE;
3004                 if(suppressKibitz) next_out = i+1;
3005             }
3006
3007             /* Kludge to deal with rcmd protocol */
3008             if (firstTime && looking_at(buf, &i, "\001*")) {
3009                 DisplayFatalError(&buf[1], 0, 1);
3010                 continue;
3011             } else {
3012                 firstTime = FALSE;
3013             }
3014
3015             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3016                 ics_type = ICS_ICC;
3017                 ics_prefix = "/";
3018                 if (appData.debugMode)
3019                   fprintf(debugFP, "ics_type %d\n", ics_type);
3020                 continue;
3021             }
3022             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3023                 ics_type = ICS_FICS;
3024                 ics_prefix = "$";
3025                 if (appData.debugMode)
3026                   fprintf(debugFP, "ics_type %d\n", ics_type);
3027                 continue;
3028             }
3029             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3030                 ics_type = ICS_CHESSNET;
3031                 ics_prefix = "/";
3032                 if (appData.debugMode)
3033                   fprintf(debugFP, "ics_type %d\n", ics_type);
3034                 continue;
3035             }
3036
3037             if (!loggedOn &&
3038                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3039                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3040                  looking_at(buf, &i, "will be \"*\""))) {
3041               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3042               continue;
3043             }
3044
3045             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3046               char buf[MSG_SIZ];
3047               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3048               DisplayIcsInteractionTitle(buf);
3049               have_set_title = TRUE;
3050             }
3051
3052             /* skip finger notes */
3053             if (started == STARTED_NONE &&
3054                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3055                  (buf[i] == '1' && buf[i+1] == '0')) &&
3056                 buf[i+2] == ':' && buf[i+3] == ' ') {
3057               started = STARTED_CHATTER;
3058               i += 3;
3059               continue;
3060             }
3061
3062             oldi = i;
3063             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3064             if(appData.seekGraph) {
3065                 if(soughtPending && MatchSoughtLine(buf+i)) {
3066                     i = strstr(buf+i, "rated") - buf;
3067                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3068                     next_out = leftover_start = i;
3069                     started = STARTED_CHATTER;
3070                     suppressKibitz = TRUE;
3071                     continue;
3072                 }
3073                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3074                         && looking_at(buf, &i, "* ads displayed")) {
3075                     soughtPending = FALSE;
3076                     seekGraphUp = TRUE;
3077                     DrawSeekGraph();
3078                     continue;
3079                 }
3080                 if(appData.autoRefresh) {
3081                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3082                         int s = (ics_type == ICS_ICC); // ICC format differs
3083                         if(seekGraphUp)
3084                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3085                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3086                         looking_at(buf, &i, "*% "); // eat prompt
3087                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3088                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3089                         next_out = i; // suppress
3090                         continue;
3091                     }
3092                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3093                         char *p = star_match[0];
3094                         while(*p) {
3095                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3096                             while(*p && *p++ != ' '); // next
3097                         }
3098                         looking_at(buf, &i, "*% "); // eat prompt
3099                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3100                         next_out = i;
3101                         continue;
3102                     }
3103                 }
3104             }
3105
3106             /* skip formula vars */
3107             if (started == STARTED_NONE &&
3108                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3109               started = STARTED_CHATTER;
3110               i += 3;
3111               continue;
3112             }
3113
3114             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3115             if (appData.autoKibitz && started == STARTED_NONE &&
3116                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3117                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3118                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3119                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3120                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3121                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3122                         suppressKibitz = TRUE;
3123                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3124                         next_out = i;
3125                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3126                                 && (gameMode == IcsPlayingWhite)) ||
3127                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3128                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3129                             started = STARTED_CHATTER; // own kibitz we simply discard
3130                         else {
3131                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3132                             parse_pos = 0; parse[0] = NULLCHAR;
3133                             savingComment = TRUE;
3134                             suppressKibitz = gameMode != IcsObserving ? 2 :
3135                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3136                         }
3137                         continue;
3138                 } else
3139                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3140                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3141                          && atoi(star_match[0])) {
3142                     // suppress the acknowledgements of our own autoKibitz
3143                     char *p;
3144                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3145                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3146                     SendToPlayer(star_match[0], strlen(star_match[0]));
3147                     if(looking_at(buf, &i, "*% ")) // eat prompt
3148                         suppressKibitz = FALSE;
3149                     next_out = i;
3150                     continue;
3151                 }
3152             } // [HGM] kibitz: end of patch
3153
3154             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3155
3156             // [HGM] chat: intercept tells by users for which we have an open chat window
3157             channel = -1;
3158             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3159                                            looking_at(buf, &i, "* whispers:") ||
3160                                            looking_at(buf, &i, "* kibitzes:") ||
3161                                            looking_at(buf, &i, "* shouts:") ||
3162                                            looking_at(buf, &i, "* c-shouts:") ||
3163                                            looking_at(buf, &i, "--> * ") ||
3164                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3165                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3166                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3167                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3168                 int p;
3169                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3170                 chattingPartner = -1;
3171
3172                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3173                 for(p=0; p<MAX_CHAT; p++) {
3174                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3175                     talker[0] = '['; strcat(talker, "] ");
3176                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3177                     chattingPartner = p; break;
3178                     }
3179                 } else
3180                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3181                 for(p=0; p<MAX_CHAT; p++) {
3182                     if(!strcmp("kibitzes", chatPartner[p])) {
3183                         talker[0] = '['; strcat(talker, "] ");
3184                         chattingPartner = p; break;
3185                     }
3186                 } else
3187                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3188                 for(p=0; p<MAX_CHAT; p++) {
3189                     if(!strcmp("whispers", chatPartner[p])) {
3190                         talker[0] = '['; strcat(talker, "] ");
3191                         chattingPartner = p; break;
3192                     }
3193                 } else
3194                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3195                   if(buf[i-8] == '-' && buf[i-3] == 't')
3196                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3197                     if(!strcmp("c-shouts", chatPartner[p])) {
3198                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3199                         chattingPartner = p; break;
3200                     }
3201                   }
3202                   if(chattingPartner < 0)
3203                   for(p=0; p<MAX_CHAT; p++) {
3204                     if(!strcmp("shouts", chatPartner[p])) {
3205                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3206                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3207                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3208                         chattingPartner = p; break;
3209                     }
3210                   }
3211                 }
3212                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3213                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3214                     talker[0] = 0; Colorize(ColorTell, FALSE);
3215                     chattingPartner = p; break;
3216                 }
3217                 if(chattingPartner<0) i = oldi; else {
3218                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3219                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3220                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3221                     started = STARTED_COMMENT;
3222                     parse_pos = 0; parse[0] = NULLCHAR;
3223                     savingComment = 3 + chattingPartner; // counts as TRUE
3224                     suppressKibitz = TRUE;
3225                     continue;
3226                 }
3227             } // [HGM] chat: end of patch
3228
3229           backup = i;
3230             if (appData.zippyTalk || appData.zippyPlay) {
3231                 /* [DM] Backup address for color zippy lines */
3232 #if ZIPPY
3233                if (loggedOn == TRUE)
3234                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3235                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3236 #endif
3237             } // [DM] 'else { ' deleted
3238                 if (
3239                     /* Regular tells and says */
3240                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3241                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3242                     looking_at(buf, &i, "* says: ") ||
3243                     /* Don't color "message" or "messages" output */
3244                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3245                     looking_at(buf, &i, "*. * at *:*: ") ||
3246                     looking_at(buf, &i, "--* (*:*): ") ||
3247                     /* Message notifications (same color as tells) */
3248                     looking_at(buf, &i, "* has left a message ") ||
3249                     looking_at(buf, &i, "* just sent you a message:\n") ||
3250                     /* Whispers and kibitzes */
3251                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3252                     looking_at(buf, &i, "* kibitzes: ") ||
3253                     /* Channel tells */
3254                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3255
3256                   if (tkind == 1 && strchr(star_match[0], ':')) {
3257                       /* Avoid "tells you:" spoofs in channels */
3258                      tkind = 3;
3259                   }
3260                   if (star_match[0][0] == NULLCHAR ||
3261                       strchr(star_match[0], ' ') ||
3262                       (tkind == 3 && strchr(star_match[1], ' '))) {
3263                     /* Reject bogus matches */
3264                     i = oldi;
3265                   } else {
3266                     if (appData.colorize) {
3267                       if (oldi > next_out) {
3268                         SendToPlayer(&buf[next_out], oldi - next_out);
3269                         next_out = oldi;
3270                       }
3271                       switch (tkind) {
3272                       case 1:
3273                         Colorize(ColorTell, FALSE);
3274                         curColor = ColorTell;
3275                         break;
3276                       case 2:
3277                         Colorize(ColorKibitz, FALSE);
3278                         curColor = ColorKibitz;
3279                         break;
3280                       case 3:
3281                         p = strrchr(star_match[1], '(');
3282                         if (p == NULL) {
3283                           p = star_match[1];
3284                         } else {
3285                           p++;
3286                         }
3287                         if (atoi(p) == 1) {
3288                           Colorize(ColorChannel1, FALSE);
3289                           curColor = ColorChannel1;
3290                         } else {
3291                           Colorize(ColorChannel, FALSE);
3292                           curColor = ColorChannel;
3293                         }
3294                         break;
3295                       case 5:
3296                         curColor = ColorNormal;
3297                         break;
3298                       }
3299                     }
3300                     if (started == STARTED_NONE && appData.autoComment &&
3301                         (gameMode == IcsObserving ||
3302                          gameMode == IcsPlayingWhite ||
3303                          gameMode == IcsPlayingBlack)) {
3304                       parse_pos = i - oldi;
3305                       memcpy(parse, &buf[oldi], parse_pos);
3306                       parse[parse_pos] = NULLCHAR;
3307                       started = STARTED_COMMENT;
3308                       savingComment = TRUE;
3309                     } else {
3310                       started = STARTED_CHATTER;
3311                       savingComment = FALSE;
3312                     }
3313                     loggedOn = TRUE;
3314                     continue;
3315                   }
3316                 }
3317
3318                 if (looking_at(buf, &i, "* s-shouts: ") ||
3319                     looking_at(buf, &i, "* c-shouts: ")) {
3320                     if (appData.colorize) {
3321                         if (oldi > next_out) {
3322                             SendToPlayer(&buf[next_out], oldi - next_out);
3323                             next_out = oldi;
3324                         }
3325                         Colorize(ColorSShout, FALSE);
3326                         curColor = ColorSShout;
3327                     }
3328                     loggedOn = TRUE;
3329                     started = STARTED_CHATTER;
3330                     continue;
3331                 }
3332
3333                 if (looking_at(buf, &i, "--->")) {
3334                     loggedOn = TRUE;
3335                     continue;
3336                 }
3337
3338                 if (looking_at(buf, &i, "* shouts: ") ||
3339                     looking_at(buf, &i, "--> ")) {
3340                     if (appData.colorize) {
3341                         if (oldi > next_out) {
3342                             SendToPlayer(&buf[next_out], oldi - next_out);
3343                             next_out = oldi;
3344                         }
3345                         Colorize(ColorShout, FALSE);
3346                         curColor = ColorShout;
3347                     }
3348                     loggedOn = TRUE;
3349                     started = STARTED_CHATTER;
3350                     continue;
3351                 }
3352
3353                 if (looking_at( buf, &i, "Challenge:")) {
3354                     if (appData.colorize) {
3355                         if (oldi > next_out) {
3356                             SendToPlayer(&buf[next_out], oldi - next_out);
3357                             next_out = oldi;
3358                         }
3359                         Colorize(ColorChallenge, FALSE);
3360                         curColor = ColorChallenge;
3361                     }
3362                     loggedOn = TRUE;
3363                     continue;
3364                 }
3365
3366                 if (looking_at(buf, &i, "* offers you") ||
3367                     looking_at(buf, &i, "* offers to be") ||
3368                     looking_at(buf, &i, "* would like to") ||
3369                     looking_at(buf, &i, "* requests to") ||
3370                     looking_at(buf, &i, "Your opponent offers") ||
3371                     looking_at(buf, &i, "Your opponent requests")) {
3372
3373                     if (appData.colorize) {
3374                         if (oldi > next_out) {
3375                             SendToPlayer(&buf[next_out], oldi - next_out);
3376                             next_out = oldi;
3377                         }
3378                         Colorize(ColorRequest, FALSE);
3379                         curColor = ColorRequest;
3380                     }
3381                     continue;
3382                 }
3383
3384                 if (looking_at(buf, &i, "* (*) seeking")) {
3385                     if (appData.colorize) {
3386                         if (oldi > next_out) {
3387                             SendToPlayer(&buf[next_out], oldi - next_out);
3388                             next_out = oldi;
3389                         }
3390                         Colorize(ColorSeek, FALSE);
3391                         curColor = ColorSeek;
3392                     }
3393                     continue;
3394             }
3395
3396           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3397
3398             if (looking_at(buf, &i, "\\   ")) {
3399                 if (prevColor != ColorNormal) {
3400                     if (oldi > next_out) {
3401                         SendToPlayer(&buf[next_out], oldi - next_out);
3402                         next_out = oldi;
3403                     }
3404                     Colorize(prevColor, TRUE);
3405                     curColor = prevColor;
3406                 }
3407                 if (savingComment) {
3408                     parse_pos = i - oldi;
3409                     memcpy(parse, &buf[oldi], parse_pos);
3410                     parse[parse_pos] = NULLCHAR;
3411                     started = STARTED_COMMENT;
3412                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3413                         chattingPartner = savingComment - 3; // kludge to remember the box
3414                 } else {
3415                     started = STARTED_CHATTER;
3416                 }
3417                 continue;
3418             }
3419
3420             if (looking_at(buf, &i, "Black Strength :") ||
3421                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3422                 looking_at(buf, &i, "<10>") ||
3423                 looking_at(buf, &i, "#@#")) {
3424                 /* Wrong board style */
3425                 loggedOn = TRUE;
3426                 SendToICS(ics_prefix);
3427                 SendToICS("set style 12\n");
3428                 SendToICS(ics_prefix);
3429                 SendToICS("refresh\n");
3430                 continue;
3431             }
3432
3433             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3434                 ICSInitScript();
3435                 have_sent_ICS_logon = 1;
3436                 continue;
3437             }
3438
3439             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3440                 (looking_at(buf, &i, "\n<12> ") ||
3441                  looking_at(buf, &i, "<12> "))) {
3442                 loggedOn = TRUE;
3443                 if (oldi > next_out) {
3444                     SendToPlayer(&buf[next_out], oldi - next_out);
3445                 }
3446                 next_out = i;
3447                 started = STARTED_BOARD;
3448                 parse_pos = 0;
3449                 continue;
3450             }
3451
3452             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3453                 looking_at(buf, &i, "<b1> ")) {
3454                 if (oldi > next_out) {
3455                     SendToPlayer(&buf[next_out], oldi - next_out);
3456                 }
3457                 next_out = i;
3458                 started = STARTED_HOLDINGS;
3459                 parse_pos = 0;
3460                 continue;
3461             }
3462
3463             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3464                 loggedOn = TRUE;
3465                 /* Header for a move list -- first line */
3466
3467                 switch (ics_getting_history) {
3468                   case H_FALSE:
3469                     switch (gameMode) {
3470                       case IcsIdle:
3471                       case BeginningOfGame:
3472                         /* User typed "moves" or "oldmoves" while we
3473                            were idle.  Pretend we asked for these
3474                            moves and soak them up so user can step
3475                            through them and/or save them.
3476                            */
3477                         Reset(FALSE, TRUE);
3478                         gameMode = IcsObserving;
3479                         ModeHighlight();
3480                         ics_gamenum = -1;
3481                         ics_getting_history = H_GOT_UNREQ_HEADER;
3482                         break;
3483                       case EditGame: /*?*/
3484                       case EditPosition: /*?*/
3485                         /* Should above feature work in these modes too? */
3486                         /* For now it doesn't */
3487                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3488                         break;
3489                       default:
3490                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3491                         break;
3492                     }
3493                     break;
3494                   case H_REQUESTED:
3495                     /* Is this the right one? */
3496                     if (gameInfo.white && gameInfo.black &&
3497                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3498                         strcmp(gameInfo.black, star_match[2]) == 0) {
3499                         /* All is well */
3500                         ics_getting_history = H_GOT_REQ_HEADER;
3501                     }
3502                     break;
3503                   case H_GOT_REQ_HEADER:
3504                   case H_GOT_UNREQ_HEADER:
3505                   case H_GOT_UNWANTED_HEADER:
3506                   case H_GETTING_MOVES:
3507                     /* Should not happen */
3508                     DisplayError(_("Error gathering move list: two headers"), 0);
3509                     ics_getting_history = H_FALSE;
3510                     break;
3511                 }
3512
3513                 /* Save player ratings into gameInfo if needed */
3514                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3515                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3516                     (gameInfo.whiteRating == -1 ||
3517                      gameInfo.blackRating == -1)) {
3518
3519                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3520                     gameInfo.blackRating = string_to_rating(star_match[3]);
3521                     if (appData.debugMode)
3522                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3523                               gameInfo.whiteRating, gameInfo.blackRating);
3524                 }
3525                 continue;
3526             }
3527
3528             if (looking_at(buf, &i,
3529               "* * match, initial time: * minute*, increment: * second")) {
3530                 /* Header for a move list -- second line */
3531                 /* Initial board will follow if this is a wild game */
3532                 if (gameInfo.event != NULL) free(gameInfo.event);
3533                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3534                 gameInfo.event = StrSave(str);
3535                 /* [HGM] we switched variant. Translate boards if needed. */
3536                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3537                 continue;
3538             }
3539
3540             if (looking_at(buf, &i, "Move  ")) {
3541                 /* Beginning of a move list */
3542                 switch (ics_getting_history) {
3543                   case H_FALSE:
3544                     /* Normally should not happen */
3545                     /* Maybe user hit reset while we were parsing */
3546                     break;
3547                   case H_REQUESTED:
3548                     /* Happens if we are ignoring a move list that is not
3549                      * the one we just requested.  Common if the user
3550                      * tries to observe two games without turning off
3551                      * getMoveList */
3552                     break;
3553                   case H_GETTING_MOVES:
3554                     /* Should not happen */
3555                     DisplayError(_("Error gathering move list: nested"), 0);
3556                     ics_getting_history = H_FALSE;
3557                     break;
3558                   case H_GOT_REQ_HEADER:
3559                     ics_getting_history = H_GETTING_MOVES;
3560                     started = STARTED_MOVES;
3561                     parse_pos = 0;
3562                     if (oldi > next_out) {
3563                         SendToPlayer(&buf[next_out], oldi - next_out);
3564                     }
3565                     break;
3566                   case H_GOT_UNREQ_HEADER:
3567                     ics_getting_history = H_GETTING_MOVES;
3568                     started = STARTED_MOVES_NOHIDE;
3569                     parse_pos = 0;
3570                     break;
3571                   case H_GOT_UNWANTED_HEADER:
3572                     ics_getting_history = H_FALSE;
3573                     break;
3574                 }
3575                 continue;
3576             }
3577
3578             if (looking_at(buf, &i, "% ") ||
3579                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3580                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3581                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3582                     soughtPending = FALSE;
3583                     seekGraphUp = TRUE;
3584                     DrawSeekGraph();
3585                 }
3586                 if(suppressKibitz) next_out = i;
3587                 savingComment = FALSE;
3588                 suppressKibitz = 0;
3589                 switch (started) {
3590                   case STARTED_MOVES:
3591                   case STARTED_MOVES_NOHIDE:
3592                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3593                     parse[parse_pos + i - oldi] = NULLCHAR;
3594                     ParseGameHistory(parse);
3595 #if ZIPPY
3596                     if (appData.zippyPlay && first.initDone) {
3597                         FeedMovesToProgram(&first, forwardMostMove);
3598                         if (gameMode == IcsPlayingWhite) {
3599                             if (WhiteOnMove(forwardMostMove)) {
3600                                 if (first.sendTime) {
3601                                   if (first.useColors) {
3602                                     SendToProgram("black\n", &first);
3603                                   }
3604                                   SendTimeRemaining(&first, TRUE);
3605                                 }
3606                                 if (first.useColors) {
3607                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3608                                 }
3609                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3610                                 first.maybeThinking = TRUE;
3611                             } else {
3612                                 if (first.usePlayother) {
3613                                   if (first.sendTime) {
3614                                     SendTimeRemaining(&first, TRUE);
3615                                   }
3616                                   SendToProgram("playother\n", &first);
3617                                   firstMove = FALSE;
3618                                 } else {
3619                                   firstMove = TRUE;
3620                                 }
3621                             }
3622                         } else if (gameMode == IcsPlayingBlack) {
3623                             if (!WhiteOnMove(forwardMostMove)) {
3624                                 if (first.sendTime) {
3625                                   if (first.useColors) {
3626                                     SendToProgram("white\n", &first);
3627                                   }
3628                                   SendTimeRemaining(&first, FALSE);
3629                                 }
3630                                 if (first.useColors) {
3631                                   SendToProgram("black\n", &first);
3632                                 }
3633                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3634                                 first.maybeThinking = TRUE;
3635                             } else {
3636                                 if (first.usePlayother) {
3637                                   if (first.sendTime) {
3638                                     SendTimeRemaining(&first, FALSE);
3639                                   }
3640                                   SendToProgram("playother\n", &first);
3641                                   firstMove = FALSE;
3642                                 } else {
3643                                   firstMove = TRUE;
3644                                 }
3645                             }
3646                         }
3647                     }
3648 #endif
3649                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3650                         /* Moves came from oldmoves or moves command
3651                            while we weren't doing anything else.
3652                            */
3653                         currentMove = forwardMostMove;
3654                         ClearHighlights();/*!!could figure this out*/
3655                         flipView = appData.flipView;
3656                         DrawPosition(TRUE, boards[currentMove]);
3657                         DisplayBothClocks();
3658                         snprintf(str, MSG_SIZ, "%s %s %s",
3659                                 gameInfo.white, _("vs."),  gameInfo.black);
3660                         DisplayTitle(str);
3661                         gameMode = IcsIdle;
3662                     } else {
3663                         /* Moves were history of an active game */
3664                         if (gameInfo.resultDetails != NULL) {
3665                             free(gameInfo.resultDetails);
3666                             gameInfo.resultDetails = NULL;
3667                         }
3668                     }
3669                     HistorySet(parseList, backwardMostMove,
3670                                forwardMostMove, currentMove-1);
3671                     DisplayMove(currentMove - 1);
3672                     if (started == STARTED_MOVES) next_out = i;
3673                     started = STARTED_NONE;
3674                     ics_getting_history = H_FALSE;
3675                     break;
3676
3677                   case STARTED_OBSERVE:
3678                     started = STARTED_NONE;
3679                     SendToICS(ics_prefix);
3680                     SendToICS("refresh\n");
3681                     break;
3682
3683                   default:
3684                     break;
3685                 }
3686                 if(bookHit) { // [HGM] book: simulate book reply
3687                     static char bookMove[MSG_SIZ]; // a bit generous?
3688
3689                     programStats.nodes = programStats.depth = programStats.time =
3690                     programStats.score = programStats.got_only_move = 0;
3691                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3692
3693                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3694                     strcat(bookMove, bookHit);
3695                     HandleMachineMove(bookMove, &first);
3696                 }
3697                 continue;
3698             }
3699
3700             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3701                  started == STARTED_HOLDINGS ||
3702                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3703                 /* Accumulate characters in move list or board */
3704                 parse[parse_pos++] = buf[i];
3705             }
3706
3707             /* Start of game messages.  Mostly we detect start of game
3708                when the first board image arrives.  On some versions
3709                of the ICS, though, we need to do a "refresh" after starting
3710                to observe in order to get the current board right away. */
3711             if (looking_at(buf, &i, "Adding game * to observation list")) {
3712                 started = STARTED_OBSERVE;
3713                 continue;
3714             }
3715
3716             /* Handle auto-observe */
3717             if (appData.autoObserve &&
3718                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3719                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3720                 char *player;
3721                 /* Choose the player that was highlighted, if any. */
3722                 if (star_match[0][0] == '\033' ||
3723                     star_match[1][0] != '\033') {
3724                     player = star_match[0];
3725                 } else {
3726                     player = star_match[2];
3727                 }
3728                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3729                         ics_prefix, StripHighlightAndTitle(player));
3730                 SendToICS(str);
3731
3732                 /* Save ratings from notify string */
3733                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3734                 player1Rating = string_to_rating(star_match[1]);
3735                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3736                 player2Rating = string_to_rating(star_match[3]);
3737
3738                 if (appData.debugMode)
3739                   fprintf(debugFP,
3740                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3741                           player1Name, player1Rating,
3742                           player2Name, player2Rating);
3743
3744                 continue;
3745             }
3746
3747             /* Deal with automatic examine mode after a game,
3748                and with IcsObserving -> IcsExamining transition */
3749             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3750                 looking_at(buf, &i, "has made you an examiner of game *")) {
3751
3752                 int gamenum = atoi(star_match[0]);
3753                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3754                     gamenum == ics_gamenum) {
3755                     /* We were already playing or observing this game;
3756                        no need to refetch history */
3757                     gameMode = IcsExamining;
3758                     if (pausing) {
3759                         pauseExamForwardMostMove = forwardMostMove;
3760                     } else if (currentMove < forwardMostMove) {
3761                         ForwardInner(forwardMostMove);
3762                     }
3763                 } else {
3764                     /* I don't think this case really can happen */
3765                     SendToICS(ics_prefix);
3766                     SendToICS("refresh\n");
3767                 }
3768                 continue;
3769             }
3770
3771             /* Error messages */
3772 //          if (ics_user_moved) {
3773             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3774                 if (looking_at(buf, &i, "Illegal move") ||
3775                     looking_at(buf, &i, "Not a legal move") ||
3776                     looking_at(buf, &i, "Your king is in check") ||
3777                     looking_at(buf, &i, "It isn't your turn") ||
3778                     looking_at(buf, &i, "It is not your move")) {
3779                     /* Illegal move */
3780                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3781                         currentMove = forwardMostMove-1;
3782                         DisplayMove(currentMove - 1); /* before DMError */
3783                         DrawPosition(FALSE, boards[currentMove]);
3784                         SwitchClocks(forwardMostMove-1); // [HGM] race
3785                         DisplayBothClocks();
3786                     }
3787                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3788                     ics_user_moved = 0;
3789                     continue;
3790                 }
3791             }
3792
3793             if (looking_at(buf, &i, "still have time") ||
3794                 looking_at(buf, &i, "not out of time") ||
3795                 looking_at(buf, &i, "either player is out of time") ||
3796                 looking_at(buf, &i, "has timeseal; checking")) {
3797                 /* We must have called his flag a little too soon */
3798                 whiteFlag = blackFlag = FALSE;
3799                 continue;
3800             }
3801
3802             if (looking_at(buf, &i, "added * seconds to") ||
3803                 looking_at(buf, &i, "seconds were added to")) {
3804                 /* Update the clocks */
3805                 SendToICS(ics_prefix);
3806                 SendToICS("refresh\n");
3807                 continue;
3808             }
3809
3810             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3811                 ics_clock_paused = TRUE;
3812                 StopClocks();
3813                 continue;
3814             }
3815
3816             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3817                 ics_clock_paused = FALSE;
3818                 StartClocks();
3819                 continue;
3820             }
3821
3822             /* Grab player ratings from the Creating: message.
3823                Note we have to check for the special case when
3824                the ICS inserts things like [white] or [black]. */
3825             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3826                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3827                 /* star_matches:
3828                    0    player 1 name (not necessarily white)
3829                    1    player 1 rating
3830                    2    empty, white, or black (IGNORED)
3831                    3    player 2 name (not necessarily black)
3832                    4    player 2 rating
3833
3834                    The names/ratings are sorted out when the game
3835                    actually starts (below).
3836                 */
3837                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3838                 player1Rating = string_to_rating(star_match[1]);
3839                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3840                 player2Rating = string_to_rating(star_match[4]);
3841
3842                 if (appData.debugMode)
3843                   fprintf(debugFP,
3844                           "Ratings from 'Creating:' %s %d, %s %d\n",
3845                           player1Name, player1Rating,
3846                           player2Name, player2Rating);
3847
3848                 continue;
3849             }
3850
3851             /* Improved generic start/end-of-game messages */
3852             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3853                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3854                 /* If tkind == 0: */
3855                 /* star_match[0] is the game number */
3856                 /*           [1] is the white player's name */
3857                 /*           [2] is the black player's name */
3858                 /* For end-of-game: */
3859                 /*           [3] is the reason for the game end */
3860                 /*           [4] is a PGN end game-token, preceded by " " */
3861                 /* For start-of-game: */
3862                 /*           [3] begins with "Creating" or "Continuing" */
3863                 /*           [4] is " *" or empty (don't care). */
3864                 int gamenum = atoi(star_match[0]);
3865                 char *whitename, *blackname, *why, *endtoken;
3866                 ChessMove endtype = EndOfFile;
3867
3868                 if (tkind == 0) {
3869                   whitename = star_match[1];
3870                   blackname = star_match[2];
3871                   why = star_match[3];
3872                   endtoken = star_match[4];
3873                 } else {
3874                   whitename = star_match[1];
3875                   blackname = star_match[3];
3876                   why = star_match[5];
3877                   endtoken = star_match[6];
3878                 }
3879
3880                 /* Game start messages */
3881                 if (strncmp(why, "Creating ", 9) == 0 ||
3882                     strncmp(why, "Continuing ", 11) == 0) {
3883                     gs_gamenum = gamenum;
3884                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3885                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3886                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3887 #if ZIPPY
3888                     if (appData.zippyPlay) {
3889                         ZippyGameStart(whitename, blackname);
3890                     }
3891 #endif /*ZIPPY*/
3892                     partnerBoardValid = FALSE; // [HGM] bughouse
3893                     continue;
3894                 }
3895
3896                 /* Game end messages */
3897                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3898                     ics_gamenum != gamenum) {
3899                     continue;
3900                 }
3901                 while (endtoken[0] == ' ') endtoken++;
3902                 switch (endtoken[0]) {
3903                   case '*':
3904                   default:
3905                     endtype = GameUnfinished;
3906                     break;
3907                   case '0':
3908                     endtype = BlackWins;
3909                     break;
3910                   case '1':
3911                     if (endtoken[1] == '/')
3912                       endtype = GameIsDrawn;
3913                     else
3914                       endtype = WhiteWins;
3915                     break;
3916                 }
3917                 GameEnds(endtype, why, GE_ICS);
3918 #if ZIPPY
3919                 if (appData.zippyPlay && first.initDone) {
3920                     ZippyGameEnd(endtype, why);
3921                     if (first.pr == NoProc) {
3922                       /* Start the next process early so that we'll
3923                          be ready for the next challenge */
3924                       StartChessProgram(&first);
3925                     }
3926                     /* Send "new" early, in case this command takes
3927                        a long time to finish, so that we'll be ready
3928                        for the next challenge. */
3929                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3930                     Reset(TRUE, TRUE);
3931                 }
3932 #endif /*ZIPPY*/
3933                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3934                 continue;
3935             }
3936
3937             if (looking_at(buf, &i, "Removing game * from observation") ||
3938                 looking_at(buf, &i, "no longer observing game *") ||
3939                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3940                 if (gameMode == IcsObserving &&
3941                     atoi(star_match[0]) == ics_gamenum)
3942                   {
3943                       /* icsEngineAnalyze */
3944                       if (appData.icsEngineAnalyze) {
3945                             ExitAnalyzeMode();
3946                             ModeHighlight();
3947                       }
3948                       StopClocks();
3949                       gameMode = IcsIdle;
3950                       ics_gamenum = -1;
3951                       ics_user_moved = FALSE;
3952                   }
3953                 continue;
3954             }
3955
3956             if (looking_at(buf, &i, "no longer examining game *")) {
3957                 if (gameMode == IcsExamining &&
3958                     atoi(star_match[0]) == ics_gamenum)
3959                   {
3960                       gameMode = IcsIdle;
3961                       ics_gamenum = -1;
3962                       ics_user_moved = FALSE;
3963                   }
3964                 continue;
3965             }
3966
3967             /* Advance leftover_start past any newlines we find,
3968                so only partial lines can get reparsed */
3969             if (looking_at(buf, &i, "\n")) {
3970                 prevColor = curColor;
3971                 if (curColor != ColorNormal) {
3972                     if (oldi > next_out) {
3973                         SendToPlayer(&buf[next_out], oldi - next_out);
3974                         next_out = oldi;
3975                     }
3976                     Colorize(ColorNormal, FALSE);
3977                     curColor = ColorNormal;
3978                 }
3979                 if (started == STARTED_BOARD) {
3980                     started = STARTED_NONE;
3981                     parse[parse_pos] = NULLCHAR;
3982                     ParseBoard12(parse);
3983                     ics_user_moved = 0;
3984
3985                     /* Send premove here */
3986                     if (appData.premove) {
3987                       char str[MSG_SIZ];
3988                       if (currentMove == 0 &&
3989                           gameMode == IcsPlayingWhite &&
3990                           appData.premoveWhite) {
3991                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3992                         if (appData.debugMode)
3993                           fprintf(debugFP, "Sending premove:\n");
3994                         SendToICS(str);
3995                       } else if (currentMove == 1 &&
3996                                  gameMode == IcsPlayingBlack &&
3997                                  appData.premoveBlack) {
3998                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3999                         if (appData.debugMode)
4000                           fprintf(debugFP, "Sending premove:\n");
4001                         SendToICS(str);
4002                       } else if (gotPremove) {
4003                         gotPremove = 0;
4004                         ClearPremoveHighlights();
4005                         if (appData.debugMode)
4006                           fprintf(debugFP, "Sending premove:\n");
4007                           UserMoveEvent(premoveFromX, premoveFromY,
4008                                         premoveToX, premoveToY,
4009                                         premovePromoChar);
4010                       }
4011                     }
4012
4013                     /* Usually suppress following prompt */
4014                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4015                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4016                         if (looking_at(buf, &i, "*% ")) {
4017                             savingComment = FALSE;
4018                             suppressKibitz = 0;
4019                         }
4020                     }
4021                     next_out = i;
4022                 } else if (started == STARTED_HOLDINGS) {
4023                     int gamenum;
4024                     char new_piece[MSG_SIZ];
4025                     started = STARTED_NONE;
4026                     parse[parse_pos] = NULLCHAR;
4027                     if (appData.debugMode)
4028                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4029                                                         parse, currentMove);
4030                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4031                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4032                         if (gameInfo.variant == VariantNormal) {
4033                           /* [HGM] We seem to switch variant during a game!
4034                            * Presumably no holdings were displayed, so we have
4035                            * to move the position two files to the right to
4036                            * create room for them!
4037                            */
4038                           VariantClass newVariant;
4039                           switch(gameInfo.boardWidth) { // base guess on board width
4040                                 case 9:  newVariant = VariantShogi; break;
4041                                 case 10: newVariant = VariantGreat; break;
4042                                 default: newVariant = VariantCrazyhouse; break;
4043                           }
4044                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4045                           /* Get a move list just to see the header, which
4046                              will tell us whether this is really bug or zh */
4047                           if (ics_getting_history == H_FALSE) {
4048                             ics_getting_history = H_REQUESTED;
4049                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4050                             SendToICS(str);
4051                           }
4052                         }
4053                         new_piece[0] = NULLCHAR;
4054                         sscanf(parse, "game %d white [%s black [%s <- %s",
4055                                &gamenum, white_holding, black_holding,
4056                                new_piece);
4057                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4058                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4059                         /* [HGM] copy holdings to board holdings area */
4060                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4061                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4062                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4063 #if ZIPPY
4064                         if (appData.zippyPlay && first.initDone) {
4065                             ZippyHoldings(white_holding, black_holding,
4066                                           new_piece);
4067                         }
4068 #endif /*ZIPPY*/
4069                         if (tinyLayout || smallLayout) {
4070                             char wh[16], bh[16];
4071                             PackHolding(wh, white_holding);
4072                             PackHolding(bh, black_holding);
4073                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4074                                     gameInfo.white, gameInfo.black);
4075                         } else {
4076                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4077                                     gameInfo.white, white_holding, _("vs."),
4078                                     gameInfo.black, black_holding);
4079                         }
4080                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4081                         DrawPosition(FALSE, boards[currentMove]);
4082                         DisplayTitle(str);
4083                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4084                         sscanf(parse, "game %d white [%s black [%s <- %s",
4085                                &gamenum, white_holding, black_holding,
4086                                new_piece);
4087                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4088                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4089                         /* [HGM] copy holdings to partner-board holdings area */
4090                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4091                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4092                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4093                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4094                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4095                       }
4096                     }
4097                     /* Suppress following prompt */
4098                     if (looking_at(buf, &i, "*% ")) {
4099                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4100                         savingComment = FALSE;
4101                         suppressKibitz = 0;
4102                     }
4103                     next_out = i;
4104                 }
4105                 continue;
4106             }
4107
4108             i++;                /* skip unparsed character and loop back */
4109         }
4110
4111         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4112 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4113 //          SendToPlayer(&buf[next_out], i - next_out);
4114             started != STARTED_HOLDINGS && leftover_start > next_out) {
4115             SendToPlayer(&buf[next_out], leftover_start - next_out);
4116             next_out = i;
4117         }
4118
4119         leftover_len = buf_len - leftover_start;
4120         /* if buffer ends with something we couldn't parse,
4121            reparse it after appending the next read */
4122
4123     } else if (count == 0) {
4124         RemoveInputSource(isr);
4125         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4126     } else {
4127         DisplayFatalError(_("Error reading from ICS"), error, 1);
4128     }
4129 }
4130
4131
4132 /* Board style 12 looks like this:
4133
4134    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4135
4136  * The "<12> " is stripped before it gets to this routine.  The two
4137  * trailing 0's (flip state and clock ticking) are later addition, and
4138  * some chess servers may not have them, or may have only the first.
4139  * Additional trailing fields may be added in the future.
4140  */
4141
4142 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4143
4144 #define RELATION_OBSERVING_PLAYED    0
4145 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4146 #define RELATION_PLAYING_MYMOVE      1
4147 #define RELATION_PLAYING_NOTMYMOVE  -1
4148 #define RELATION_EXAMINING           2
4149 #define RELATION_ISOLATED_BOARD     -3
4150 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4151
4152 void
4153 ParseBoard12 (char *string)
4154 {
4155     GameMode newGameMode;
4156     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4157     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4158     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4159     char to_play, board_chars[200];
4160     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4161     char black[32], white[32];
4162     Board board;
4163     int prevMove = currentMove;
4164     int ticking = 2;
4165     ChessMove moveType;
4166     int fromX, fromY, toX, toY;
4167     char promoChar;
4168     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4169     char *bookHit = NULL; // [HGM] book
4170     Boolean weird = FALSE, reqFlag = FALSE;
4171
4172     fromX = fromY = toX = toY = -1;
4173
4174     newGame = FALSE;
4175
4176     if (appData.debugMode)
4177       fprintf(debugFP, _("Parsing board: %s\n"), string);
4178
4179     move_str[0] = NULLCHAR;
4180     elapsed_time[0] = NULLCHAR;
4181     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4182         int  i = 0, j;
4183         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4184             if(string[i] == ' ') { ranks++; files = 0; }
4185             else files++;
4186             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4187             i++;
4188         }
4189         for(j = 0; j <i; j++) board_chars[j] = string[j];
4190         board_chars[i] = '\0';
4191         string += i + 1;
4192     }
4193     n = sscanf(string, PATTERN, &to_play, &double_push,
4194                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4195                &gamenum, white, black, &relation, &basetime, &increment,
4196                &white_stren, &black_stren, &white_time, &black_time,
4197                &moveNum, str, elapsed_time, move_str, &ics_flip,
4198                &ticking);
4199
4200     if (n < 21) {
4201         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4202         DisplayError(str, 0);
4203         return;
4204     }
4205
4206     /* Convert the move number to internal form */
4207     moveNum = (moveNum - 1) * 2;
4208     if (to_play == 'B') moveNum++;
4209     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4210       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4211                         0, 1);
4212       return;
4213     }
4214
4215     switch (relation) {
4216       case RELATION_OBSERVING_PLAYED:
4217       case RELATION_OBSERVING_STATIC:
4218         if (gamenum == -1) {
4219             /* Old ICC buglet */
4220             relation = RELATION_OBSERVING_STATIC;
4221         }
4222         newGameMode = IcsObserving;
4223         break;
4224       case RELATION_PLAYING_MYMOVE:
4225       case RELATION_PLAYING_NOTMYMOVE:
4226         newGameMode =
4227           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4228             IcsPlayingWhite : IcsPlayingBlack;
4229         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4230         break;
4231       case RELATION_EXAMINING:
4232         newGameMode = IcsExamining;
4233         break;
4234       case RELATION_ISOLATED_BOARD:
4235       default:
4236         /* Just display this board.  If user was doing something else,
4237            we will forget about it until the next board comes. */
4238         newGameMode = IcsIdle;
4239         break;
4240       case RELATION_STARTING_POSITION:
4241         newGameMode = gameMode;
4242         break;
4243     }
4244
4245     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4246         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4247          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4248       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4249       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4250       static int lastBgGame = -1;
4251       char *toSqr;
4252       for (k = 0; k < ranks; k++) {
4253         for (j = 0; j < files; j++)
4254           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4255         if(gameInfo.holdingsWidth > 1) {
4256              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4257              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4258         }
4259       }
4260       CopyBoard(partnerBoard, board);
4261       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4262         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4263         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4264       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4265       if(toSqr = strchr(str, '-')) {
4266         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4267         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4268       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4269       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4270       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4271       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4272       if(twoBoards) {
4273           DisplayWhiteClock(white_time*fac, to_play == 'W');
4274           DisplayBlackClock(black_time*fac, to_play != 'W');
4275           activePartner = to_play;
4276           if(gamenum != lastBgGame) {
4277               char buf[MSG_SIZ];
4278               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4279               DisplayTitle(buf);
4280           }
4281           lastBgGame = gamenum;
4282           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4283                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4284       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4285                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4286       DisplayMessage(partnerStatus, "");
4287         partnerBoardValid = TRUE;
4288       return;
4289     }
4290
4291     if(appData.dualBoard && appData.bgObserve) {
4292         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4293             SendToICS(ics_prefix), SendToICS("pobserve\n");
4294         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4295             char buf[MSG_SIZ];
4296             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4297             SendToICS(buf);
4298         }
4299     }
4300
4301     /* Modify behavior for initial board display on move listing
4302        of wild games.
4303        */
4304     switch (ics_getting_history) {
4305       case H_FALSE:
4306       case H_REQUESTED:
4307         break;
4308       case H_GOT_REQ_HEADER:
4309       case H_GOT_UNREQ_HEADER:
4310         /* This is the initial position of the current game */
4311         gamenum = ics_gamenum;
4312         moveNum = 0;            /* old ICS bug workaround */
4313         if (to_play == 'B') {
4314           startedFromSetupPosition = TRUE;
4315           blackPlaysFirst = TRUE;
4316           moveNum = 1;
4317           if (forwardMostMove == 0) forwardMostMove = 1;
4318           if (backwardMostMove == 0) backwardMostMove = 1;
4319           if (currentMove == 0) currentMove = 1;
4320         }
4321         newGameMode = gameMode;
4322         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4323         break;
4324       case H_GOT_UNWANTED_HEADER:
4325         /* This is an initial board that we don't want */
4326         return;
4327       case H_GETTING_MOVES:
4328         /* Should not happen */
4329         DisplayError(_("Error gathering move list: extra board"), 0);
4330         ics_getting_history = H_FALSE;
4331         return;
4332     }
4333
4334    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4335                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4336                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4337      /* [HGM] We seem to have switched variant unexpectedly
4338       * Try to guess new variant from board size
4339       */
4340           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4341           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4342           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4343           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4344           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4345           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4346           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4347           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4348           /* Get a move list just to see the header, which
4349              will tell us whether this is really bug or zh */
4350           if (ics_getting_history == H_FALSE) {
4351             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4352             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4353             SendToICS(str);
4354           }
4355     }
4356
4357     /* Take action if this is the first board of a new game, or of a
4358        different game than is currently being displayed.  */
4359     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4360         relation == RELATION_ISOLATED_BOARD) {
4361
4362         /* Forget the old game and get the history (if any) of the new one */
4363         if (gameMode != BeginningOfGame) {
4364           Reset(TRUE, TRUE);
4365         }
4366         newGame = TRUE;
4367         if (appData.autoRaiseBoard) BoardToTop();
4368         prevMove = -3;
4369         if (gamenum == -1) {
4370             newGameMode = IcsIdle;
4371         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4372                    appData.getMoveList && !reqFlag) {
4373             /* Need to get game history */
4374             ics_getting_history = H_REQUESTED;
4375             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4376             SendToICS(str);
4377         }
4378
4379         /* Initially flip the board to have black on the bottom if playing
4380            black or if the ICS flip flag is set, but let the user change
4381            it with the Flip View button. */
4382         flipView = appData.autoFlipView ?
4383           (newGameMode == IcsPlayingBlack) || ics_flip :
4384           appData.flipView;
4385
4386         /* Done with values from previous mode; copy in new ones */
4387         gameMode = newGameMode;
4388         ModeHighlight();
4389         ics_gamenum = gamenum;
4390         if (gamenum == gs_gamenum) {
4391             int klen = strlen(gs_kind);
4392             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4393             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4394             gameInfo.event = StrSave(str);
4395         } else {
4396             gameInfo.event = StrSave("ICS game");
4397         }
4398         gameInfo.site = StrSave(appData.icsHost);
4399         gameInfo.date = PGNDate();
4400         gameInfo.round = StrSave("-");
4401         gameInfo.white = StrSave(white);
4402         gameInfo.black = StrSave(black);
4403         timeControl = basetime * 60 * 1000;
4404         timeControl_2 = 0;
4405         timeIncrement = increment * 1000;
4406         movesPerSession = 0;
4407         gameInfo.timeControl = TimeControlTagValue();
4408         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4409   if (appData.debugMode) {
4410     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4411     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4412     setbuf(debugFP, NULL);
4413   }
4414
4415         gameInfo.outOfBook = NULL;
4416
4417         /* Do we have the ratings? */
4418         if (strcmp(player1Name, white) == 0 &&
4419             strcmp(player2Name, black) == 0) {
4420             if (appData.debugMode)
4421               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4422                       player1Rating, player2Rating);
4423             gameInfo.whiteRating = player1Rating;
4424             gameInfo.blackRating = player2Rating;
4425         } else if (strcmp(player2Name, white) == 0 &&
4426                    strcmp(player1Name, black) == 0) {
4427             if (appData.debugMode)
4428               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4429                       player2Rating, player1Rating);
4430             gameInfo.whiteRating = player2Rating;
4431             gameInfo.blackRating = player1Rating;
4432         }
4433         player1Name[0] = player2Name[0] = NULLCHAR;
4434
4435         /* Silence shouts if requested */
4436         if (appData.quietPlay &&
4437             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4438             SendToICS(ics_prefix);
4439             SendToICS("set shout 0\n");
4440         }
4441     }
4442
4443     /* Deal with midgame name changes */
4444     if (!newGame) {
4445         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4446             if (gameInfo.white) free(gameInfo.white);
4447             gameInfo.white = StrSave(white);
4448         }
4449         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4450             if (gameInfo.black) free(gameInfo.black);
4451             gameInfo.black = StrSave(black);
4452         }
4453     }
4454
4455     /* Throw away game result if anything actually changes in examine mode */
4456     if (gameMode == IcsExamining && !newGame) {
4457         gameInfo.result = GameUnfinished;
4458         if (gameInfo.resultDetails != NULL) {
4459             free(gameInfo.resultDetails);
4460             gameInfo.resultDetails = NULL;
4461         }
4462     }
4463
4464     /* In pausing && IcsExamining mode, we ignore boards coming
4465        in if they are in a different variation than we are. */
4466     if (pauseExamInvalid) return;
4467     if (pausing && gameMode == IcsExamining) {
4468         if (moveNum <= pauseExamForwardMostMove) {
4469             pauseExamInvalid = TRUE;
4470             forwardMostMove = pauseExamForwardMostMove;
4471             return;
4472         }
4473     }
4474
4475   if (appData.debugMode) {
4476     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4477   }
4478     /* Parse the board */
4479     for (k = 0; k < ranks; k++) {
4480       for (j = 0; j < files; j++)
4481         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4482       if(gameInfo.holdingsWidth > 1) {
4483            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4484            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4485       }
4486     }
4487     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4488       board[5][BOARD_RGHT+1] = WhiteAngel;
4489       board[6][BOARD_RGHT+1] = WhiteMarshall;
4490       board[1][0] = BlackMarshall;
4491       board[2][0] = BlackAngel;
4492       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4493     }
4494     CopyBoard(boards[moveNum], board);
4495     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4496     if (moveNum == 0) {
4497         startedFromSetupPosition =
4498           !CompareBoards(board, initialPosition);
4499         if(startedFromSetupPosition)
4500             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4501     }
4502
4503     /* [HGM] Set castling rights. Take the outermost Rooks,
4504        to make it also work for FRC opening positions. Note that board12
4505        is really defective for later FRC positions, as it has no way to
4506        indicate which Rook can castle if they are on the same side of King.
4507        For the initial position we grant rights to the outermost Rooks,
4508        and remember thos rights, and we then copy them on positions
4509        later in an FRC game. This means WB might not recognize castlings with
4510        Rooks that have moved back to their original position as illegal,
4511        but in ICS mode that is not its job anyway.
4512     */
4513     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4514     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4515
4516         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4517             if(board[0][i] == WhiteRook) j = i;
4518         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4519         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4520             if(board[0][i] == WhiteRook) j = i;
4521         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4522         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4523             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4524         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4525         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4526             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4527         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4528
4529         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4530         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4531         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4532             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4533         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4534             if(board[BOARD_HEIGHT-1][k] == bKing)
4535                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4536         if(gameInfo.variant == VariantTwoKings) {
4537             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4538             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4539             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4540         }
4541     } else { int r;
4542         r = boards[moveNum][CASTLING][0] = initialRights[0];
4543         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4544         r = boards[moveNum][CASTLING][1] = initialRights[1];
4545         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4546         r = boards[moveNum][CASTLING][3] = initialRights[3];
4547         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4548         r = boards[moveNum][CASTLING][4] = initialRights[4];
4549         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4550         /* wildcastle kludge: always assume King has rights */
4551         r = boards[moveNum][CASTLING][2] = initialRights[2];
4552         r = boards[moveNum][CASTLING][5] = initialRights[5];
4553     }
4554     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4555     boards[moveNum][EP_STATUS] = EP_NONE;
4556     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4557     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4558     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4559
4560
4561     if (ics_getting_history == H_GOT_REQ_HEADER ||
4562         ics_getting_history == H_GOT_UNREQ_HEADER) {
4563         /* This was an initial position from a move list, not
4564            the current position */
4565         return;
4566     }
4567
4568     /* Update currentMove and known move number limits */
4569     newMove = newGame || moveNum > forwardMostMove;
4570
4571     if (newGame) {
4572         forwardMostMove = backwardMostMove = currentMove = moveNum;
4573         if (gameMode == IcsExamining && moveNum == 0) {
4574           /* Workaround for ICS limitation: we are not told the wild
4575              type when starting to examine a game.  But if we ask for
4576              the move list, the move list header will tell us */
4577             ics_getting_history = H_REQUESTED;
4578             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4579             SendToICS(str);
4580         }
4581     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4582                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4583 #if ZIPPY
4584         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4585         /* [HGM] applied this also to an engine that is silently watching        */
4586         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4587             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4588             gameInfo.variant == currentlyInitializedVariant) {
4589           takeback = forwardMostMove - moveNum;
4590           for (i = 0; i < takeback; i++) {
4591             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4592             SendToProgram("undo\n", &first);
4593           }
4594         }
4595 #endif
4596
4597         forwardMostMove = moveNum;
4598         if (!pausing || currentMove > forwardMostMove)
4599           currentMove = forwardMostMove;
4600     } else {
4601         /* New part of history that is not contiguous with old part */
4602         if (pausing && gameMode == IcsExamining) {
4603             pauseExamInvalid = TRUE;
4604             forwardMostMove = pauseExamForwardMostMove;
4605             return;
4606         }
4607         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4608 #if ZIPPY
4609             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4610                 // [HGM] when we will receive the move list we now request, it will be
4611                 // fed to the engine from the first move on. So if the engine is not
4612                 // in the initial position now, bring it there.
4613                 InitChessProgram(&first, 0);
4614             }
4615 #endif
4616             ics_getting_history = H_REQUESTED;
4617             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4618             SendToICS(str);
4619         }
4620         forwardMostMove = backwardMostMove = currentMove = moveNum;
4621     }
4622
4623     /* Update the clocks */
4624     if (strchr(elapsed_time, '.')) {
4625       /* Time is in ms */
4626       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4627       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4628     } else {
4629       /* Time is in seconds */
4630       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4631       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4632     }
4633
4634
4635 #if ZIPPY
4636     if (appData.zippyPlay && newGame &&
4637         gameMode != IcsObserving && gameMode != IcsIdle &&
4638         gameMode != IcsExamining)
4639       ZippyFirstBoard(moveNum, basetime, increment);
4640 #endif
4641
4642     /* Put the move on the move list, first converting
4643        to canonical algebraic form. */
4644     if (moveNum > 0) {
4645   if (appData.debugMode) {
4646     if (appData.debugMode) { int f = forwardMostMove;
4647         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4648                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4649                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4650     }
4651     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4652     fprintf(debugFP, "moveNum = %d\n", moveNum);
4653     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4654     setbuf(debugFP, NULL);
4655   }
4656         if (moveNum <= backwardMostMove) {
4657             /* We don't know what the board looked like before
4658                this move.  Punt. */
4659           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4660             strcat(parseList[moveNum - 1], " ");
4661             strcat(parseList[moveNum - 1], elapsed_time);
4662             moveList[moveNum - 1][0] = NULLCHAR;
4663         } else if (strcmp(move_str, "none") == 0) {
4664             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4665             /* Again, we don't know what the board looked like;
4666                this is really the start of the game. */
4667             parseList[moveNum - 1][0] = NULLCHAR;
4668             moveList[moveNum - 1][0] = NULLCHAR;
4669             backwardMostMove = moveNum;
4670             startedFromSetupPosition = TRUE;
4671             fromX = fromY = toX = toY = -1;
4672         } else {
4673           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4674           //                 So we parse the long-algebraic move string in stead of the SAN move
4675           int valid; char buf[MSG_SIZ], *prom;
4676
4677           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4678                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4679           // str looks something like "Q/a1-a2"; kill the slash
4680           if(str[1] == '/')
4681             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4682           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4683           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4684                 strcat(buf, prom); // long move lacks promo specification!
4685           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4686                 if(appData.debugMode)
4687                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4688                 safeStrCpy(move_str, buf, MSG_SIZ);
4689           }
4690           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4691                                 &fromX, &fromY, &toX, &toY, &promoChar)
4692                || ParseOneMove(buf, moveNum - 1, &moveType,
4693                                 &fromX, &fromY, &toX, &toY, &promoChar);
4694           // end of long SAN patch
4695           if (valid) {
4696             (void) CoordsToAlgebraic(boards[moveNum - 1],
4697                                      PosFlags(moveNum - 1),
4698                                      fromY, fromX, toY, toX, promoChar,
4699                                      parseList[moveNum-1]);
4700             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4701               case MT_NONE:
4702               case MT_STALEMATE:
4703               default:
4704                 break;
4705               case MT_CHECK:
4706                 if(gameInfo.variant != VariantShogi)
4707                     strcat(parseList[moveNum - 1], "+");
4708                 break;
4709               case MT_CHECKMATE:
4710               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4711                 strcat(parseList[moveNum - 1], "#");
4712                 break;
4713             }
4714             strcat(parseList[moveNum - 1], " ");
4715             strcat(parseList[moveNum - 1], elapsed_time);
4716             /* currentMoveString is set as a side-effect of ParseOneMove */
4717             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4718             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4719             strcat(moveList[moveNum - 1], "\n");
4720
4721             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4722                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4723               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4724                 ChessSquare old, new = boards[moveNum][k][j];
4725                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4726                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4727                   if(old == new) continue;
4728                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4729                   else if(new == WhiteWazir || new == BlackWazir) {
4730                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4731                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4732                       else boards[moveNum][k][j] = old; // preserve type of Gold
4733                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4734                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4735               }
4736           } else {
4737             /* Move from ICS was illegal!?  Punt. */
4738             if (appData.debugMode) {
4739               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4740               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4741             }
4742             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4743             strcat(parseList[moveNum - 1], " ");
4744             strcat(parseList[moveNum - 1], elapsed_time);
4745             moveList[moveNum - 1][0] = NULLCHAR;
4746             fromX = fromY = toX = toY = -1;
4747           }
4748         }
4749   if (appData.debugMode) {
4750     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4751     setbuf(debugFP, NULL);
4752   }
4753
4754 #if ZIPPY
4755         /* Send move to chess program (BEFORE animating it). */
4756         if (appData.zippyPlay && !newGame && newMove &&
4757            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4758
4759             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4760                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4761                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4762                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4763                             move_str);
4764                     DisplayError(str, 0);
4765                 } else {
4766                     if (first.sendTime) {
4767                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4768                     }
4769                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4770                     if (firstMove && !bookHit) {
4771                         firstMove = FALSE;
4772                         if (first.useColors) {
4773                           SendToProgram(gameMode == IcsPlayingWhite ?
4774                                         "white\ngo\n" :
4775                                         "black\ngo\n", &first);
4776                         } else {
4777                           SendToProgram("go\n", &first);
4778                         }
4779                         first.maybeThinking = TRUE;
4780                     }
4781                 }
4782             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4783               if (moveList[moveNum - 1][0] == NULLCHAR) {
4784                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4785                 DisplayError(str, 0);
4786               } else {
4787                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4788                 SendMoveToProgram(moveNum - 1, &first);
4789               }
4790             }
4791         }
4792 #endif
4793     }
4794
4795     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4796         /* If move comes from a remote source, animate it.  If it
4797            isn't remote, it will have already been animated. */
4798         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4799             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4800         }
4801         if (!pausing && appData.highlightLastMove) {
4802             SetHighlights(fromX, fromY, toX, toY);
4803         }
4804     }
4805
4806     /* Start the clocks */
4807     whiteFlag = blackFlag = FALSE;
4808     appData.clockMode = !(basetime == 0 && increment == 0);
4809     if (ticking == 0) {
4810       ics_clock_paused = TRUE;
4811       StopClocks();
4812     } else if (ticking == 1) {
4813       ics_clock_paused = FALSE;
4814     }
4815     if (gameMode == IcsIdle ||
4816         relation == RELATION_OBSERVING_STATIC ||
4817         relation == RELATION_EXAMINING ||
4818         ics_clock_paused)
4819       DisplayBothClocks();
4820     else
4821       StartClocks();
4822
4823     /* Display opponents and material strengths */
4824     if (gameInfo.variant != VariantBughouse &&
4825         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4826         if (tinyLayout || smallLayout) {
4827             if(gameInfo.variant == VariantNormal)
4828               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4829                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4830                     basetime, increment);
4831             else
4832               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4833                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4834                     basetime, increment, (int) gameInfo.variant);
4835         } else {
4836             if(gameInfo.variant == VariantNormal)
4837               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4838                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4839                     basetime, increment);
4840             else
4841               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4842                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4843                     basetime, increment, VariantName(gameInfo.variant));
4844         }
4845         DisplayTitle(str);
4846   if (appData.debugMode) {
4847     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4848   }
4849     }
4850
4851
4852     /* Display the board */
4853     if (!pausing && !appData.noGUI) {
4854
4855       if (appData.premove)
4856           if (!gotPremove ||
4857              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4858              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4859               ClearPremoveHighlights();
4860
4861       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4862         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4863       DrawPosition(j, boards[currentMove]);
4864
4865       DisplayMove(moveNum - 1);
4866       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4867             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4868               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4869         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4870       }
4871     }
4872
4873     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4874 #if ZIPPY
4875     if(bookHit) { // [HGM] book: simulate book reply
4876         static char bookMove[MSG_SIZ]; // a bit generous?
4877
4878         programStats.nodes = programStats.depth = programStats.time =
4879         programStats.score = programStats.got_only_move = 0;
4880         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4881
4882         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4883         strcat(bookMove, bookHit);
4884         HandleMachineMove(bookMove, &first);
4885     }
4886 #endif
4887 }
4888
4889 void
4890 GetMoveListEvent ()
4891 {
4892     char buf[MSG_SIZ];
4893     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4894         ics_getting_history = H_REQUESTED;
4895         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4896         SendToICS(buf);
4897     }
4898 }
4899
4900 void
4901 AnalysisPeriodicEvent (int force)
4902 {
4903     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4904          && !force) || !appData.periodicUpdates)
4905       return;
4906
4907     /* Send . command to Crafty to collect stats */
4908     SendToProgram(".\n", &first);
4909
4910     /* Don't send another until we get a response (this makes
4911        us stop sending to old Crafty's which don't understand
4912        the "." command (sending illegal cmds resets node count & time,
4913        which looks bad)) */
4914     programStats.ok_to_send = 0;
4915 }
4916
4917 void
4918 ics_update_width (int new_width)
4919 {
4920         ics_printf("set width %d\n", new_width);
4921 }
4922
4923 void
4924 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4925 {
4926     char buf[MSG_SIZ];
4927
4928     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4929         // null move in variant where engine does not understand it (for analysis purposes)
4930         SendBoard(cps, moveNum + 1); // send position after move in stead.
4931         return;
4932     }
4933     if (cps->useUsermove) {
4934       SendToProgram("usermove ", cps);
4935     }
4936     if (cps->useSAN) {
4937       char *space;
4938       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4939         int len = space - parseList[moveNum];
4940         memcpy(buf, parseList[moveNum], len);
4941         buf[len++] = '\n';
4942         buf[len] = NULLCHAR;
4943       } else {
4944         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4945       }
4946       SendToProgram(buf, cps);
4947     } else {
4948       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4949         AlphaRank(moveList[moveNum], 4);
4950         SendToProgram(moveList[moveNum], cps);
4951         AlphaRank(moveList[moveNum], 4); // and back
4952       } else
4953       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4954        * the engine. It would be nice to have a better way to identify castle
4955        * moves here. */
4956       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4957                                                                          && cps->useOOCastle) {
4958         int fromX = moveList[moveNum][0] - AAA;
4959         int fromY = moveList[moveNum][1] - ONE;
4960         int toX = moveList[moveNum][2] - AAA;
4961         int toY = moveList[moveNum][3] - ONE;
4962         if((boards[moveNum][fromY][fromX] == WhiteKing
4963             && boards[moveNum][toY][toX] == WhiteRook)
4964            || (boards[moveNum][fromY][fromX] == BlackKing
4965                && boards[moveNum][toY][toX] == BlackRook)) {
4966           if(toX > fromX) SendToProgram("O-O\n", cps);
4967           else SendToProgram("O-O-O\n", cps);
4968         }
4969         else SendToProgram(moveList[moveNum], cps);
4970       } else
4971       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4972         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4973           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4974           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4975                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4976         } else
4977           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4978                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4979         SendToProgram(buf, cps);
4980       }
4981       else SendToProgram(moveList[moveNum], cps);
4982       /* End of additions by Tord */
4983     }
4984
4985     /* [HGM] setting up the opening has brought engine in force mode! */
4986     /*       Send 'go' if we are in a mode where machine should play. */
4987     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4988         (gameMode == TwoMachinesPlay   ||
4989 #if ZIPPY
4990          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4991 #endif
4992          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4993         SendToProgram("go\n", cps);
4994   if (appData.debugMode) {
4995     fprintf(debugFP, "(extra)\n");
4996   }
4997     }
4998     setboardSpoiledMachineBlack = 0;
4999 }
5000
5001 void
5002 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5003 {
5004     char user_move[MSG_SIZ];
5005     char suffix[4];
5006
5007     if(gameInfo.variant == VariantSChess && promoChar) {
5008         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5009         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5010     } else suffix[0] = NULLCHAR;
5011
5012     switch (moveType) {
5013       default:
5014         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5015                 (int)moveType, fromX, fromY, toX, toY);
5016         DisplayError(user_move + strlen("say "), 0);
5017         break;
5018       case WhiteKingSideCastle:
5019       case BlackKingSideCastle:
5020       case WhiteQueenSideCastleWild:
5021       case BlackQueenSideCastleWild:
5022       /* PUSH Fabien */
5023       case WhiteHSideCastleFR:
5024       case BlackHSideCastleFR:
5025       /* POP Fabien */
5026         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5027         break;
5028       case WhiteQueenSideCastle:
5029       case BlackQueenSideCastle:
5030       case WhiteKingSideCastleWild:
5031       case BlackKingSideCastleWild:
5032       /* PUSH Fabien */
5033       case WhiteASideCastleFR:
5034       case BlackASideCastleFR:
5035       /* POP Fabien */
5036         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5037         break;
5038       case WhiteNonPromotion:
5039       case BlackNonPromotion:
5040         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5041         break;
5042       case WhitePromotion:
5043       case BlackPromotion:
5044         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5045           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5046                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5047                 PieceToChar(WhiteFerz));
5048         else if(gameInfo.variant == VariantGreat)
5049           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5050                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5051                 PieceToChar(WhiteMan));
5052         else
5053           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5054                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5055                 promoChar);
5056         break;
5057       case WhiteDrop:
5058       case BlackDrop:
5059       drop:
5060         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5061                  ToUpper(PieceToChar((ChessSquare) fromX)),
5062                  AAA + toX, ONE + toY);
5063         break;
5064       case IllegalMove:  /* could be a variant we don't quite understand */
5065         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5066       case NormalMove:
5067       case WhiteCapturesEnPassant:
5068       case BlackCapturesEnPassant:
5069         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5070                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5071         break;
5072     }
5073     SendToICS(user_move);
5074     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5075         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5076 }
5077
5078 void
5079 UploadGameEvent ()
5080 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5081     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5082     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5083     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5084       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5085       return;
5086     }
5087     if(gameMode != IcsExamining) { // is this ever not the case?
5088         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5089
5090         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5091           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5092         } else { // on FICS we must first go to general examine mode
5093           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5094         }
5095         if(gameInfo.variant != VariantNormal) {
5096             // try figure out wild number, as xboard names are not always valid on ICS
5097             for(i=1; i<=36; i++) {
5098               snprintf(buf, MSG_SIZ, "wild/%d", i);
5099                 if(StringToVariant(buf) == gameInfo.variant) break;
5100             }
5101             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5102             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5103             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5104         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5105         SendToICS(ics_prefix);
5106         SendToICS(buf);
5107         if(startedFromSetupPosition || backwardMostMove != 0) {
5108           fen = PositionToFEN(backwardMostMove, NULL);
5109           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5110             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5111             SendToICS(buf);
5112           } else { // FICS: everything has to set by separate bsetup commands
5113             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5114             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5115             SendToICS(buf);
5116             if(!WhiteOnMove(backwardMostMove)) {
5117                 SendToICS("bsetup tomove black\n");
5118             }
5119             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5120             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5121             SendToICS(buf);
5122             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5123             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5124             SendToICS(buf);
5125             i = boards[backwardMostMove][EP_STATUS];
5126             if(i >= 0) { // set e.p.
5127               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5128                 SendToICS(buf);
5129             }
5130             bsetup++;
5131           }
5132         }
5133       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5134             SendToICS("bsetup done\n"); // switch to normal examining.
5135     }
5136     for(i = backwardMostMove; i<last; i++) {
5137         char buf[20];
5138         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5139         SendToICS(buf);
5140     }
5141     SendToICS(ics_prefix);
5142     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5143 }
5144
5145 void
5146 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5147 {
5148     if (rf == DROP_RANK) {
5149       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5150       sprintf(move, "%c@%c%c\n",
5151                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5152     } else {
5153         if (promoChar == 'x' || promoChar == NULLCHAR) {
5154           sprintf(move, "%c%c%c%c\n",
5155                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5156         } else {
5157             sprintf(move, "%c%c%c%c%c\n",
5158                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5159         }
5160     }
5161 }
5162
5163 void
5164 ProcessICSInitScript (FILE *f)
5165 {
5166     char buf[MSG_SIZ];
5167
5168     while (fgets(buf, MSG_SIZ, f)) {
5169         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5170     }
5171
5172     fclose(f);
5173 }
5174
5175
5176 static int lastX, lastY, selectFlag, dragging;
5177
5178 void
5179 Sweep (int step)
5180 {
5181     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5182     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5183     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5184     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5185     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5186     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5187     do {
5188         promoSweep -= step;
5189         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5190         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5191         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5192         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5193         if(!step) step = -1;
5194     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5195             appData.testLegality && (promoSweep == king ||
5196             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5197     if(toX >= 0) {
5198         int victim = boards[currentMove][toY][toX];
5199         boards[currentMove][toY][toX] = promoSweep;
5200         DrawPosition(FALSE, boards[currentMove]);
5201         boards[currentMove][toY][toX] = victim;
5202     } else
5203     ChangeDragPiece(promoSweep);
5204 }
5205
5206 int
5207 PromoScroll (int x, int y)
5208 {
5209   int step = 0;
5210
5211   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5212   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5213   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5214   if(!step) return FALSE;
5215   lastX = x; lastY = y;
5216   if((promoSweep < BlackPawn) == flipView) step = -step;
5217   if(step > 0) selectFlag = 1;
5218   if(!selectFlag) Sweep(step);
5219   return FALSE;
5220 }
5221
5222 void
5223 NextPiece (int step)
5224 {
5225     ChessSquare piece = boards[currentMove][toY][toX];
5226     do {
5227         pieceSweep -= step;
5228         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5229         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5230         if(!step) step = -1;
5231     } while(PieceToChar(pieceSweep) == '.');
5232     boards[currentMove][toY][toX] = pieceSweep;
5233     DrawPosition(FALSE, boards[currentMove]);
5234     boards[currentMove][toY][toX] = piece;
5235 }
5236 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5237 void
5238 AlphaRank (char *move, int n)
5239 {
5240 //    char *p = move, c; int x, y;
5241
5242     if (appData.debugMode) {
5243         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5244     }
5245
5246     if(move[1]=='*' &&
5247        move[2]>='0' && move[2]<='9' &&
5248        move[3]>='a' && move[3]<='x'    ) {
5249         move[1] = '@';
5250         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5251         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5252     } else
5253     if(move[0]>='0' && move[0]<='9' &&
5254        move[1]>='a' && move[1]<='x' &&
5255        move[2]>='0' && move[2]<='9' &&
5256        move[3]>='a' && move[3]<='x'    ) {
5257         /* input move, Shogi -> normal */
5258         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5259         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5260         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5261         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5262     } else
5263     if(move[1]=='@' &&
5264        move[3]>='0' && move[3]<='9' &&
5265        move[2]>='a' && move[2]<='x'    ) {
5266         move[1] = '*';
5267         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5268         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5269     } else
5270     if(
5271        move[0]>='a' && move[0]<='x' &&
5272        move[3]>='0' && move[3]<='9' &&
5273        move[2]>='a' && move[2]<='x'    ) {
5274          /* output move, normal -> Shogi */
5275         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5276         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5277         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5278         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5279         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5280     }
5281     if (appData.debugMode) {
5282         fprintf(debugFP, "   out = '%s'\n", move);
5283     }
5284 }
5285
5286 char yy_textstr[8000];
5287
5288 /* Parser for moves from gnuchess, ICS, or user typein box */
5289 Boolean
5290 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5291 {
5292     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5293
5294     switch (*moveType) {
5295       case WhitePromotion:
5296       case BlackPromotion:
5297       case WhiteNonPromotion:
5298       case BlackNonPromotion:
5299       case NormalMove:
5300       case WhiteCapturesEnPassant:
5301       case BlackCapturesEnPassant:
5302       case WhiteKingSideCastle:
5303       case WhiteQueenSideCastle:
5304       case BlackKingSideCastle:
5305       case BlackQueenSideCastle:
5306       case WhiteKingSideCastleWild:
5307       case WhiteQueenSideCastleWild:
5308       case BlackKingSideCastleWild:
5309       case BlackQueenSideCastleWild:
5310       /* Code added by Tord: */
5311       case WhiteHSideCastleFR:
5312       case WhiteASideCastleFR:
5313       case BlackHSideCastleFR:
5314       case BlackASideCastleFR:
5315       /* End of code added by Tord */
5316       case IllegalMove:         /* bug or odd chess variant */
5317         *fromX = currentMoveString[0] - AAA;
5318         *fromY = currentMoveString[1] - ONE;
5319         *toX = currentMoveString[2] - AAA;
5320         *toY = currentMoveString[3] - ONE;
5321         *promoChar = currentMoveString[4];
5322         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5323             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5324     if (appData.debugMode) {
5325         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5326     }
5327             *fromX = *fromY = *toX = *toY = 0;
5328             return FALSE;
5329         }
5330         if (appData.testLegality) {
5331           return (*moveType != IllegalMove);
5332         } else {
5333           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5334                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5335         }
5336
5337       case WhiteDrop:
5338       case BlackDrop:
5339         *fromX = *moveType == WhiteDrop ?
5340           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5341           (int) CharToPiece(ToLower(currentMoveString[0]));
5342         *fromY = DROP_RANK;
5343         *toX = currentMoveString[2] - AAA;
5344         *toY = currentMoveString[3] - ONE;
5345         *promoChar = NULLCHAR;
5346         return TRUE;
5347
5348       case AmbiguousMove:
5349       case ImpossibleMove:
5350       case EndOfFile:
5351       case ElapsedTime:
5352       case Comment:
5353       case PGNTag:
5354       case NAG:
5355       case WhiteWins:
5356       case BlackWins:
5357       case GameIsDrawn:
5358       default:
5359     if (appData.debugMode) {
5360         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5361     }
5362         /* bug? */
5363         *fromX = *fromY = *toX = *toY = 0;
5364         *promoChar = NULLCHAR;
5365         return FALSE;
5366     }
5367 }
5368
5369 Boolean pushed = FALSE;
5370 char *lastParseAttempt;
5371
5372 void
5373 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5374 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5375   int fromX, fromY, toX, toY; char promoChar;
5376   ChessMove moveType;
5377   Boolean valid;
5378   int nr = 0;
5379
5380   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5381     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5382     pushed = TRUE;
5383   }
5384   endPV = forwardMostMove;
5385   do {
5386     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5387     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5388     lastParseAttempt = pv;
5389     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5390     if(!valid && nr == 0 &&
5391        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5392         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5393         // Hande case where played move is different from leading PV move
5394         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5395         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5396         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5397         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5398           endPV += 2; // if position different, keep this
5399           moveList[endPV-1][0] = fromX + AAA;
5400           moveList[endPV-1][1] = fromY + ONE;
5401           moveList[endPV-1][2] = toX + AAA;
5402           moveList[endPV-1][3] = toY + ONE;
5403           parseList[endPV-1][0] = NULLCHAR;
5404           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5405         }
5406       }
5407     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5408     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5409     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5410     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5411         valid++; // allow comments in PV
5412         continue;
5413     }
5414     nr++;
5415     if(endPV+1 > framePtr) break; // no space, truncate
5416     if(!valid) break;
5417     endPV++;
5418     CopyBoard(boards[endPV], boards[endPV-1]);
5419     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5420     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5421     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5422     CoordsToAlgebraic(boards[endPV - 1],
5423                              PosFlags(endPV - 1),
5424                              fromY, fromX, toY, toX, promoChar,
5425                              parseList[endPV - 1]);
5426   } while(valid);
5427   if(atEnd == 2) return; // used hidden, for PV conversion
5428   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5429   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5430   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5431                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5432   DrawPosition(TRUE, boards[currentMove]);
5433 }
5434
5435 int
5436 MultiPV (ChessProgramState *cps)
5437 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5438         int i;
5439         for(i=0; i<cps->nrOptions; i++)
5440             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5441                 return i;
5442         return -1;
5443 }
5444
5445 Boolean
5446 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5447 {
5448         int startPV, multi, lineStart, origIndex = index;
5449         char *p, buf2[MSG_SIZ];
5450
5451         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5452         lastX = x; lastY = y;
5453         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5454         lineStart = startPV = index;
5455         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5456         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5457         index = startPV;
5458         do{ while(buf[index] && buf[index] != '\n') index++;
5459         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5460         buf[index] = 0;
5461         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5462                 int n = first.option[multi].value;
5463                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5464                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5465                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5466                 first.option[multi].value = n;
5467                 *start = *end = 0;
5468                 return FALSE;
5469         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5470                 ExcludeClick(origIndex - lineStart);
5471                 return FALSE;
5472         }
5473         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5474         *start = startPV; *end = index-1;
5475         return TRUE;
5476 }
5477
5478 char *
5479 PvToSAN (char *pv)
5480 {
5481         static char buf[10*MSG_SIZ];
5482         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5483         *buf = NULLCHAR;
5484         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5485         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5486         for(i = forwardMostMove; i<endPV; i++){
5487             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5488             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5489             k += strlen(buf+k);
5490         }
5491         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5492         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5493         endPV = savedEnd;
5494         return buf;
5495 }
5496
5497 Boolean
5498 LoadPV (int x, int y)
5499 { // called on right mouse click to load PV
5500   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5501   lastX = x; lastY = y;
5502   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5503   return TRUE;
5504 }
5505
5506 void
5507 UnLoadPV ()
5508 {
5509   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5510   if(endPV < 0) return;
5511   if(appData.autoCopyPV) CopyFENToClipboard();
5512   endPV = -1;
5513   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5514         Boolean saveAnimate = appData.animate;
5515         if(pushed) {
5516             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5517                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5518             } else storedGames--; // abandon shelved tail of original game
5519         }
5520         pushed = FALSE;
5521         forwardMostMove = currentMove;
5522         currentMove = oldFMM;
5523         appData.animate = FALSE;
5524         ToNrEvent(forwardMostMove);
5525         appData.animate = saveAnimate;
5526   }
5527   currentMove = forwardMostMove;
5528   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5529   ClearPremoveHighlights();
5530   DrawPosition(TRUE, boards[currentMove]);
5531 }
5532
5533 void
5534 MovePV (int x, int y, int h)
5535 { // step through PV based on mouse coordinates (called on mouse move)
5536   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5537
5538   // we must somehow check if right button is still down (might be released off board!)
5539   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5540   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5541   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5542   if(!step) return;
5543   lastX = x; lastY = y;
5544
5545   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5546   if(endPV < 0) return;
5547   if(y < margin) step = 1; else
5548   if(y > h - margin) step = -1;
5549   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5550   currentMove += step;
5551   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5552   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5553                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5554   DrawPosition(FALSE, boards[currentMove]);
5555 }
5556
5557
5558 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5559 // All positions will have equal probability, but the current method will not provide a unique
5560 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5561 #define DARK 1
5562 #define LITE 2
5563 #define ANY 3
5564
5565 int squaresLeft[4];
5566 int piecesLeft[(int)BlackPawn];
5567 int seed, nrOfShuffles;
5568
5569 void
5570 GetPositionNumber ()
5571 {       // sets global variable seed
5572         int i;
5573
5574         seed = appData.defaultFrcPosition;
5575         if(seed < 0) { // randomize based on time for negative FRC position numbers
5576                 for(i=0; i<50; i++) seed += random();
5577                 seed = random() ^ random() >> 8 ^ random() << 8;
5578                 if(seed<0) seed = -seed;
5579         }
5580 }
5581
5582 int
5583 put (Board board, int pieceType, int rank, int n, int shade)
5584 // put the piece on the (n-1)-th empty squares of the given shade
5585 {
5586         int i;
5587
5588         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5589                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5590                         board[rank][i] = (ChessSquare) pieceType;
5591                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5592                         squaresLeft[ANY]--;
5593                         piecesLeft[pieceType]--;
5594                         return i;
5595                 }
5596         }
5597         return -1;
5598 }
5599
5600
5601 void
5602 AddOnePiece (Board board, int pieceType, int rank, int shade)
5603 // calculate where the next piece goes, (any empty square), and put it there
5604 {
5605         int i;
5606
5607         i = seed % squaresLeft[shade];
5608         nrOfShuffles *= squaresLeft[shade];
5609         seed /= squaresLeft[shade];
5610         put(board, pieceType, rank, i, shade);
5611 }
5612
5613 void
5614 AddTwoPieces (Board board, int pieceType, int rank)
5615 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5616 {
5617         int i, n=squaresLeft[ANY], j=n-1, k;
5618
5619         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5620         i = seed % k;  // pick one
5621         nrOfShuffles *= k;
5622         seed /= k;
5623         while(i >= j) i -= j--;
5624         j = n - 1 - j; i += j;
5625         put(board, pieceType, rank, j, ANY);
5626         put(board, pieceType, rank, i, ANY);
5627 }
5628
5629 void
5630 SetUpShuffle (Board board, int number)
5631 {
5632         int i, p, first=1;
5633
5634         GetPositionNumber(); nrOfShuffles = 1;
5635
5636         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5637         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5638         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5639
5640         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5641
5642         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5643             p = (int) board[0][i];
5644             if(p < (int) BlackPawn) piecesLeft[p] ++;
5645             board[0][i] = EmptySquare;
5646         }
5647
5648         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5649             // shuffles restricted to allow normal castling put KRR first
5650             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5651                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5652             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5653                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5654             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5655                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5656             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5657                 put(board, WhiteRook, 0, 0, ANY);
5658             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5659         }
5660
5661         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5662             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5663             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5664                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5665                 while(piecesLeft[p] >= 2) {
5666                     AddOnePiece(board, p, 0, LITE);
5667                     AddOnePiece(board, p, 0, DARK);
5668                 }
5669                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5670             }
5671
5672         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5673             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5674             // but we leave King and Rooks for last, to possibly obey FRC restriction
5675             if(p == (int)WhiteRook) continue;
5676             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5677             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5678         }
5679
5680         // now everything is placed, except perhaps King (Unicorn) and Rooks
5681
5682         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5683             // Last King gets castling rights
5684             while(piecesLeft[(int)WhiteUnicorn]) {
5685                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5686                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5687             }
5688
5689             while(piecesLeft[(int)WhiteKing]) {
5690                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5691                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5692             }
5693
5694
5695         } else {
5696             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5697             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5698         }
5699
5700         // Only Rooks can be left; simply place them all
5701         while(piecesLeft[(int)WhiteRook]) {
5702                 i = put(board, WhiteRook, 0, 0, ANY);
5703                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5704                         if(first) {
5705                                 first=0;
5706                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5707                         }
5708                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5709                 }
5710         }
5711         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5712             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5713         }
5714
5715         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5716 }
5717
5718 int
5719 SetCharTable (char *table, const char * map)
5720 /* [HGM] moved here from winboard.c because of its general usefulness */
5721 /*       Basically a safe strcpy that uses the last character as King */
5722 {
5723     int result = FALSE; int NrPieces;
5724
5725     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5726                     && NrPieces >= 12 && !(NrPieces&1)) {
5727         int i; /* [HGM] Accept even length from 12 to 34 */
5728
5729         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5730         for( i=0; i<NrPieces/2-1; i++ ) {
5731             table[i] = map[i];
5732             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5733         }
5734         table[(int) WhiteKing]  = map[NrPieces/2-1];
5735         table[(int) BlackKing]  = map[NrPieces-1];
5736
5737         result = TRUE;
5738     }
5739
5740     return result;
5741 }
5742
5743 void
5744 Prelude (Board board)
5745 {       // [HGM] superchess: random selection of exo-pieces
5746         int i, j, k; ChessSquare p;
5747         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5748
5749         GetPositionNumber(); // use FRC position number
5750
5751         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5752             SetCharTable(pieceToChar, appData.pieceToCharTable);
5753             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5754                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5755         }
5756
5757         j = seed%4;                 seed /= 4;
5758         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5759         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5760         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5761         j = seed%3 + (seed%3 >= j); seed /= 3;
5762         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5763         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5764         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5765         j = seed%3;                 seed /= 3;
5766         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5767         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5768         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5769         j = seed%2 + (seed%2 >= j); seed /= 2;
5770         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5771         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5772         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5773         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5774         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5775         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5776         put(board, exoPieces[0],    0, 0, ANY);
5777         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5778 }
5779
5780 void
5781 InitPosition (int redraw)
5782 {
5783     ChessSquare (* pieces)[BOARD_FILES];
5784     int i, j, pawnRow, overrule,
5785     oldx = gameInfo.boardWidth,
5786     oldy = gameInfo.boardHeight,
5787     oldh = gameInfo.holdingsWidth;
5788     static int oldv;
5789
5790     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5791
5792     /* [AS] Initialize pv info list [HGM] and game status */
5793     {
5794         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5795             pvInfoList[i].depth = 0;
5796             boards[i][EP_STATUS] = EP_NONE;
5797             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5798         }
5799
5800         initialRulePlies = 0; /* 50-move counter start */
5801
5802         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5803         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5804     }
5805
5806
5807     /* [HGM] logic here is completely changed. In stead of full positions */
5808     /* the initialized data only consist of the two backranks. The switch */
5809     /* selects which one we will use, which is than copied to the Board   */
5810     /* initialPosition, which for the rest is initialized by Pawns and    */
5811     /* empty squares. This initial position is then copied to boards[0],  */
5812     /* possibly after shuffling, so that it remains available.            */
5813
5814     gameInfo.holdingsWidth = 0; /* default board sizes */
5815     gameInfo.boardWidth    = 8;
5816     gameInfo.boardHeight   = 8;
5817     gameInfo.holdingsSize  = 0;
5818     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5819     for(i=0; i<BOARD_FILES-2; i++)
5820       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5821     initialPosition[EP_STATUS] = EP_NONE;
5822     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5823     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5824          SetCharTable(pieceNickName, appData.pieceNickNames);
5825     else SetCharTable(pieceNickName, "............");
5826     pieces = FIDEArray;
5827
5828     switch (gameInfo.variant) {
5829     case VariantFischeRandom:
5830       shuffleOpenings = TRUE;
5831     default:
5832       break;
5833     case VariantShatranj:
5834       pieces = ShatranjArray;
5835       nrCastlingRights = 0;
5836       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5837       break;
5838     case VariantMakruk:
5839       pieces = makrukArray;
5840       nrCastlingRights = 0;
5841       startedFromSetupPosition = TRUE;
5842       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5843       break;
5844     case VariantTwoKings:
5845       pieces = twoKingsArray;
5846       break;
5847     case VariantGrand:
5848       pieces = GrandArray;
5849       nrCastlingRights = 0;
5850       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5851       gameInfo.boardWidth = 10;
5852       gameInfo.boardHeight = 10;
5853       gameInfo.holdingsSize = 7;
5854       break;
5855     case VariantCapaRandom:
5856       shuffleOpenings = TRUE;
5857     case VariantCapablanca:
5858       pieces = CapablancaArray;
5859       gameInfo.boardWidth = 10;
5860       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5861       break;
5862     case VariantGothic:
5863       pieces = GothicArray;
5864       gameInfo.boardWidth = 10;
5865       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5866       break;
5867     case VariantSChess:
5868       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5869       gameInfo.holdingsSize = 7;
5870       break;
5871     case VariantJanus:
5872       pieces = JanusArray;
5873       gameInfo.boardWidth = 10;
5874       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5875       nrCastlingRights = 6;
5876         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5877         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5878         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5879         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5880         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5881         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5882       break;
5883     case VariantFalcon:
5884       pieces = FalconArray;
5885       gameInfo.boardWidth = 10;
5886       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5887       break;
5888     case VariantXiangqi:
5889       pieces = XiangqiArray;
5890       gameInfo.boardWidth  = 9;
5891       gameInfo.boardHeight = 10;
5892       nrCastlingRights = 0;
5893       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5894       break;
5895     case VariantShogi:
5896       pieces = ShogiArray;
5897       gameInfo.boardWidth  = 9;
5898       gameInfo.boardHeight = 9;
5899       gameInfo.holdingsSize = 7;
5900       nrCastlingRights = 0;
5901       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5902       break;
5903     case VariantCourier:
5904       pieces = CourierArray;
5905       gameInfo.boardWidth  = 12;
5906       nrCastlingRights = 0;
5907       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5908       break;
5909     case VariantKnightmate:
5910       pieces = KnightmateArray;
5911       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5912       break;
5913     case VariantSpartan:
5914       pieces = SpartanArray;
5915       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5916       break;
5917     case VariantFairy:
5918       pieces = fairyArray;
5919       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5920       break;
5921     case VariantGreat:
5922       pieces = GreatArray;
5923       gameInfo.boardWidth = 10;
5924       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5925       gameInfo.holdingsSize = 8;
5926       break;
5927     case VariantSuper:
5928       pieces = FIDEArray;
5929       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5930       gameInfo.holdingsSize = 8;
5931       startedFromSetupPosition = TRUE;
5932       break;
5933     case VariantCrazyhouse:
5934     case VariantBughouse:
5935       pieces = FIDEArray;
5936       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5937       gameInfo.holdingsSize = 5;
5938       break;
5939     case VariantWildCastle:
5940       pieces = FIDEArray;
5941       /* !!?shuffle with kings guaranteed to be on d or e file */
5942       shuffleOpenings = 1;
5943       break;
5944     case VariantNoCastle:
5945       pieces = FIDEArray;
5946       nrCastlingRights = 0;
5947       /* !!?unconstrained back-rank shuffle */
5948       shuffleOpenings = 1;
5949       break;
5950     }
5951
5952     overrule = 0;
5953     if(appData.NrFiles >= 0) {
5954         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5955         gameInfo.boardWidth = appData.NrFiles;
5956     }
5957     if(appData.NrRanks >= 0) {
5958         gameInfo.boardHeight = appData.NrRanks;
5959     }
5960     if(appData.holdingsSize >= 0) {
5961         i = appData.holdingsSize;
5962         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5963         gameInfo.holdingsSize = i;
5964     }
5965     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5966     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5967         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5968
5969     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5970     if(pawnRow < 1) pawnRow = 1;
5971     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5972
5973     /* User pieceToChar list overrules defaults */
5974     if(appData.pieceToCharTable != NULL)
5975         SetCharTable(pieceToChar, appData.pieceToCharTable);
5976
5977     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5978
5979         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5980             s = (ChessSquare) 0; /* account holding counts in guard band */
5981         for( i=0; i<BOARD_HEIGHT; i++ )
5982             initialPosition[i][j] = s;
5983
5984         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5985         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5986         initialPosition[pawnRow][j] = WhitePawn;
5987         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5988         if(gameInfo.variant == VariantXiangqi) {
5989             if(j&1) {
5990                 initialPosition[pawnRow][j] =
5991                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5992                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5993                    initialPosition[2][j] = WhiteCannon;
5994                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5995                 }
5996             }
5997         }
5998         if(gameInfo.variant == VariantGrand) {
5999             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6000                initialPosition[0][j] = WhiteRook;
6001                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6002             }
6003         }
6004         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6005     }
6006     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6007
6008             j=BOARD_LEFT+1;
6009             initialPosition[1][j] = WhiteBishop;
6010             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6011             j=BOARD_RGHT-2;
6012             initialPosition[1][j] = WhiteRook;
6013             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6014     }
6015
6016     if( nrCastlingRights == -1) {
6017         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6018         /*       This sets default castling rights from none to normal corners   */
6019         /* Variants with other castling rights must set them themselves above    */
6020         nrCastlingRights = 6;
6021
6022         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6023         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6024         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6025         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6026         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6027         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6028      }
6029
6030      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6031      if(gameInfo.variant == VariantGreat) { // promotion commoners
6032         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6033         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6034         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6035         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6036      }
6037      if( gameInfo.variant == VariantSChess ) {
6038       initialPosition[1][0] = BlackMarshall;
6039       initialPosition[2][0] = BlackAngel;
6040       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6041       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6042       initialPosition[1][1] = initialPosition[2][1] = 
6043       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6044      }
6045   if (appData.debugMode) {
6046     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6047   }
6048     if(shuffleOpenings) {
6049         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6050         startedFromSetupPosition = TRUE;
6051     }
6052     if(startedFromPositionFile) {
6053       /* [HGM] loadPos: use PositionFile for every new game */
6054       CopyBoard(initialPosition, filePosition);
6055       for(i=0; i<nrCastlingRights; i++)
6056           initialRights[i] = filePosition[CASTLING][i];
6057       startedFromSetupPosition = TRUE;
6058     }
6059
6060     CopyBoard(boards[0], initialPosition);
6061
6062     if(oldx != gameInfo.boardWidth ||
6063        oldy != gameInfo.boardHeight ||
6064        oldv != gameInfo.variant ||
6065        oldh != gameInfo.holdingsWidth
6066                                          )
6067             InitDrawingSizes(-2 ,0);
6068
6069     oldv = gameInfo.variant;
6070     if (redraw)
6071       DrawPosition(TRUE, boards[currentMove]);
6072 }
6073
6074 void
6075 SendBoard (ChessProgramState *cps, int moveNum)
6076 {
6077     char message[MSG_SIZ];
6078
6079     if (cps->useSetboard) {
6080       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6081       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6082       SendToProgram(message, cps);
6083       free(fen);
6084
6085     } else {
6086       ChessSquare *bp;
6087       int i, j, left=0, right=BOARD_WIDTH;
6088       /* Kludge to set black to move, avoiding the troublesome and now
6089        * deprecated "black" command.
6090        */
6091       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6092         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6093
6094       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6095
6096       SendToProgram("edit\n", cps);
6097       SendToProgram("#\n", cps);
6098       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6099         bp = &boards[moveNum][i][left];
6100         for (j = left; j < right; j++, bp++) {
6101           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6102           if ((int) *bp < (int) BlackPawn) {
6103             if(j == BOARD_RGHT+1)
6104                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6105             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6106             if(message[0] == '+' || message[0] == '~') {
6107               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6108                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6109                         AAA + j, ONE + i);
6110             }
6111             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6112                 message[1] = BOARD_RGHT   - 1 - j + '1';
6113                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6114             }
6115             SendToProgram(message, cps);
6116           }
6117         }
6118       }
6119
6120       SendToProgram("c\n", cps);
6121       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6122         bp = &boards[moveNum][i][left];
6123         for (j = left; j < right; j++, bp++) {
6124           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6125           if (((int) *bp != (int) EmptySquare)
6126               && ((int) *bp >= (int) BlackPawn)) {
6127             if(j == BOARD_LEFT-2)
6128                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6129             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6130                     AAA + j, ONE + i);
6131             if(message[0] == '+' || message[0] == '~') {
6132               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6133                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6134                         AAA + j, ONE + i);
6135             }
6136             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6137                 message[1] = BOARD_RGHT   - 1 - j + '1';
6138                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6139             }
6140             SendToProgram(message, cps);
6141           }
6142         }
6143       }
6144
6145       SendToProgram(".\n", cps);
6146     }
6147     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6148 }
6149
6150 char exclusionHeader[MSG_SIZ];
6151 int exCnt, excludePtr;
6152 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6153 static Exclusion excluTab[200];
6154 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6155
6156 static void
6157 WriteMap (int s)
6158 {
6159     int j;
6160     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6161     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6162 }
6163
6164 static void
6165 ClearMap ()
6166 {
6167     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6168     excludePtr = 24; exCnt = 0;
6169     WriteMap(0);
6170 }
6171
6172 static void
6173 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6174 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6175     char buf[2*MOVE_LEN], *p;
6176     Exclusion *e = excluTab;
6177     int i;
6178     for(i=0; i<exCnt; i++)
6179         if(e[i].ff == fromX && e[i].fr == fromY &&
6180            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6181     if(i == exCnt) { // was not in exclude list; add it
6182         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6183         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6184             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6185             return; // abort
6186         }
6187         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6188         excludePtr++; e[i].mark = excludePtr++;
6189         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6190         exCnt++;
6191     }
6192     exclusionHeader[e[i].mark] = state;
6193 }
6194
6195 static int
6196 ExcludeOneMove (int fromY, int fromX, int toY, int toX, signed char promoChar, char state)
6197 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6198     char buf[MSG_SIZ];
6199     int j, k;
6200     ChessMove moveType;
6201     if(promoChar == -1) { // kludge to indicate best move
6202         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6203             return 1; // if unparsable, abort
6204     }
6205     // update exclusion map (resolving toggle by consulting existing state)
6206     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6207     j = k%8; k >>= 3;
6208     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6209     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6210          excludeMap[k] |=   1<<j;
6211     else excludeMap[k] &= ~(1<<j);
6212     // update header
6213     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6214     // inform engine
6215     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6216     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6217     SendToProgram(buf, &first);
6218     return (state == '+');
6219 }
6220
6221 static void
6222 ExcludeClick (int index)
6223 {
6224     int i, j;
6225     Exclusion *e = excluTab;
6226     if(index < 25) { // none, best or tail clicked
6227         if(index < 13) { // none: include all
6228             WriteMap(0); // clear map
6229             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6230             SendToProgram("include all\n", &first); // and inform engine
6231         } else if(index > 18) { // tail
6232             if(exclusionHeader[19] == '-') { // tail was excluded
6233                 SendToProgram("include all\n", &first);
6234                 WriteMap(0); // clear map completely
6235                 // now re-exclude selected moves
6236                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6237                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6238             } else { // tail was included or in mixed state
6239                 SendToProgram("exclude all\n", &first);
6240                 WriteMap(0xFF); // fill map completely
6241                 // now re-include selected moves
6242                 j = 0; // count them
6243                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6244                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6245                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6246             }
6247         } else { // best
6248             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6249         }
6250     } else {
6251         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6252             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6253             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6254             break;
6255         }
6256     }
6257 }
6258
6259 ChessSquare
6260 DefaultPromoChoice (int white)
6261 {
6262     ChessSquare result;
6263     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6264         result = WhiteFerz; // no choice
6265     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6266         result= WhiteKing; // in Suicide Q is the last thing we want
6267     else if(gameInfo.variant == VariantSpartan)
6268         result = white ? WhiteQueen : WhiteAngel;
6269     else result = WhiteQueen;
6270     if(!white) result = WHITE_TO_BLACK result;
6271     return result;
6272 }
6273
6274 static int autoQueen; // [HGM] oneclick
6275
6276 int
6277 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6278 {
6279     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6280     /* [HGM] add Shogi promotions */
6281     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6282     ChessSquare piece;
6283     ChessMove moveType;
6284     Boolean premove;
6285
6286     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6287     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6288
6289     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6290       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6291         return FALSE;
6292
6293     piece = boards[currentMove][fromY][fromX];
6294     if(gameInfo.variant == VariantShogi) {
6295         promotionZoneSize = BOARD_HEIGHT/3;
6296         highestPromotingPiece = (int)WhiteFerz;
6297     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6298         promotionZoneSize = 3;
6299     }
6300
6301     // Treat Lance as Pawn when it is not representing Amazon
6302     if(gameInfo.variant != VariantSuper) {
6303         if(piece == WhiteLance) piece = WhitePawn; else
6304         if(piece == BlackLance) piece = BlackPawn;
6305     }
6306
6307     // next weed out all moves that do not touch the promotion zone at all
6308     if((int)piece >= BlackPawn) {
6309         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6310              return FALSE;
6311         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6312     } else {
6313         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6314            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6315     }
6316
6317     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6318
6319     // weed out mandatory Shogi promotions
6320     if(gameInfo.variant == VariantShogi) {
6321         if(piece >= BlackPawn) {
6322             if(toY == 0 && piece == BlackPawn ||
6323                toY == 0 && piece == BlackQueen ||
6324                toY <= 1 && piece == BlackKnight) {
6325                 *promoChoice = '+';
6326                 return FALSE;
6327             }
6328         } else {
6329             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6330                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6331                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6332                 *promoChoice = '+';
6333                 return FALSE;
6334             }
6335         }
6336     }
6337
6338     // weed out obviously illegal Pawn moves
6339     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6340         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6341         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6342         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6343         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6344         // note we are not allowed to test for valid (non-)capture, due to premove
6345     }
6346
6347     // we either have a choice what to promote to, or (in Shogi) whether to promote
6348     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6349         *promoChoice = PieceToChar(BlackFerz);  // no choice
6350         return FALSE;
6351     }
6352     // no sense asking what we must promote to if it is going to explode...
6353     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6354         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6355         return FALSE;
6356     }
6357     // give caller the default choice even if we will not make it
6358     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6359     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6360     if(        sweepSelect && gameInfo.variant != VariantGreat
6361                            && gameInfo.variant != VariantGrand
6362                            && gameInfo.variant != VariantSuper) return FALSE;
6363     if(autoQueen) return FALSE; // predetermined
6364
6365     // suppress promotion popup on illegal moves that are not premoves
6366     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6367               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6368     if(appData.testLegality && !premove) {
6369         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6370                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6371         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6372             return FALSE;
6373     }
6374
6375     return TRUE;
6376 }
6377
6378 int
6379 InPalace (int row, int column)
6380 {   /* [HGM] for Xiangqi */
6381     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6382          column < (BOARD_WIDTH + 4)/2 &&
6383          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6384     return FALSE;
6385 }
6386
6387 int
6388 PieceForSquare (int x, int y)
6389 {
6390   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6391      return -1;
6392   else
6393      return boards[currentMove][y][x];
6394 }
6395
6396 int
6397 OKToStartUserMove (int x, int y)
6398 {
6399     ChessSquare from_piece;
6400     int white_piece;
6401
6402     if (matchMode) return FALSE;
6403     if (gameMode == EditPosition) return TRUE;
6404
6405     if (x >= 0 && y >= 0)
6406       from_piece = boards[currentMove][y][x];
6407     else
6408       from_piece = EmptySquare;
6409
6410     if (from_piece == EmptySquare) return FALSE;
6411
6412     white_piece = (int)from_piece >= (int)WhitePawn &&
6413       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6414
6415     switch (gameMode) {
6416       case AnalyzeFile:
6417       case TwoMachinesPlay:
6418       case EndOfGame:
6419         return FALSE;
6420
6421       case IcsObserving:
6422       case IcsIdle:
6423         return FALSE;
6424
6425       case MachinePlaysWhite:
6426       case IcsPlayingBlack:
6427         if (appData.zippyPlay) return FALSE;
6428         if (white_piece) {
6429             DisplayMoveError(_("You are playing Black"));
6430             return FALSE;
6431         }
6432         break;
6433
6434       case MachinePlaysBlack:
6435       case IcsPlayingWhite:
6436         if (appData.zippyPlay) return FALSE;
6437         if (!white_piece) {
6438             DisplayMoveError(_("You are playing White"));
6439             return FALSE;
6440         }
6441         break;
6442
6443       case PlayFromGameFile:
6444             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6445       case EditGame:
6446         if (!white_piece && WhiteOnMove(currentMove)) {
6447             DisplayMoveError(_("It is White's turn"));
6448             return FALSE;
6449         }
6450         if (white_piece && !WhiteOnMove(currentMove)) {
6451             DisplayMoveError(_("It is Black's turn"));
6452             return FALSE;
6453         }
6454         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6455             /* Editing correspondence game history */
6456             /* Could disallow this or prompt for confirmation */
6457             cmailOldMove = -1;
6458         }
6459         break;
6460
6461       case BeginningOfGame:
6462         if (appData.icsActive) return FALSE;
6463         if (!appData.noChessProgram) {
6464             if (!white_piece) {
6465                 DisplayMoveError(_("You are playing White"));
6466                 return FALSE;
6467             }
6468         }
6469         break;
6470
6471       case Training:
6472         if (!white_piece && WhiteOnMove(currentMove)) {
6473             DisplayMoveError(_("It is White's turn"));
6474             return FALSE;
6475         }
6476         if (white_piece && !WhiteOnMove(currentMove)) {
6477             DisplayMoveError(_("It is Black's turn"));
6478             return FALSE;
6479         }
6480         break;
6481
6482       default:
6483       case IcsExamining:
6484         break;
6485     }
6486     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6487         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6488         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6489         && gameMode != AnalyzeFile && gameMode != Training) {
6490         DisplayMoveError(_("Displayed position is not current"));
6491         return FALSE;
6492     }
6493     return TRUE;
6494 }
6495
6496 Boolean
6497 OnlyMove (int *x, int *y, Boolean captures) 
6498 {
6499     DisambiguateClosure cl;
6500     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6501     switch(gameMode) {
6502       case MachinePlaysBlack:
6503       case IcsPlayingWhite:
6504       case BeginningOfGame:
6505         if(!WhiteOnMove(currentMove)) return FALSE;
6506         break;
6507       case MachinePlaysWhite:
6508       case IcsPlayingBlack:
6509         if(WhiteOnMove(currentMove)) return FALSE;
6510         break;
6511       case EditGame:
6512         break;
6513       default:
6514         return FALSE;
6515     }
6516     cl.pieceIn = EmptySquare;
6517     cl.rfIn = *y;
6518     cl.ffIn = *x;
6519     cl.rtIn = -1;
6520     cl.ftIn = -1;
6521     cl.promoCharIn = NULLCHAR;
6522     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6523     if( cl.kind == NormalMove ||
6524         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6525         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6526         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6527       fromX = cl.ff;
6528       fromY = cl.rf;
6529       *x = cl.ft;
6530       *y = cl.rt;
6531       return TRUE;
6532     }
6533     if(cl.kind != ImpossibleMove) return FALSE;
6534     cl.pieceIn = EmptySquare;
6535     cl.rfIn = -1;
6536     cl.ffIn = -1;
6537     cl.rtIn = *y;
6538     cl.ftIn = *x;
6539     cl.promoCharIn = NULLCHAR;
6540     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6541     if( cl.kind == NormalMove ||
6542         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6543         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6544         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6545       fromX = cl.ff;
6546       fromY = cl.rf;
6547       *x = cl.ft;
6548       *y = cl.rt;
6549       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6550       return TRUE;
6551     }
6552     return FALSE;
6553 }
6554
6555 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6556 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6557 int lastLoadGameUseList = FALSE;
6558 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6559 ChessMove lastLoadGameStart = EndOfFile;
6560 int doubleClick;
6561
6562 void
6563 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6564 {
6565     ChessMove moveType;
6566     ChessSquare pup;
6567     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6568
6569     /* Check if the user is playing in turn.  This is complicated because we
6570        let the user "pick up" a piece before it is his turn.  So the piece he
6571        tried to pick up may have been captured by the time he puts it down!
6572        Therefore we use the color the user is supposed to be playing in this
6573        test, not the color of the piece that is currently on the starting
6574        square---except in EditGame mode, where the user is playing both
6575        sides; fortunately there the capture race can't happen.  (It can
6576        now happen in IcsExamining mode, but that's just too bad.  The user
6577        will get a somewhat confusing message in that case.)
6578        */
6579
6580     switch (gameMode) {
6581       case AnalyzeFile:
6582       case TwoMachinesPlay:
6583       case EndOfGame:
6584       case IcsObserving:
6585       case IcsIdle:
6586         /* We switched into a game mode where moves are not accepted,
6587            perhaps while the mouse button was down. */
6588         return;
6589
6590       case MachinePlaysWhite:
6591         /* User is moving for Black */
6592         if (WhiteOnMove(currentMove)) {
6593             DisplayMoveError(_("It is White's turn"));
6594             return;
6595         }
6596         break;
6597
6598       case MachinePlaysBlack:
6599         /* User is moving for White */
6600         if (!WhiteOnMove(currentMove)) {
6601             DisplayMoveError(_("It is Black's turn"));
6602             return;
6603         }
6604         break;
6605
6606       case PlayFromGameFile:
6607             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6608       case EditGame:
6609       case IcsExamining:
6610       case BeginningOfGame:
6611       case AnalyzeMode:
6612       case Training:
6613         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6614         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6615             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6616             /* User is moving for Black */
6617             if (WhiteOnMove(currentMove)) {
6618                 DisplayMoveError(_("It is White's turn"));
6619                 return;
6620             }
6621         } else {
6622             /* User is moving for White */
6623             if (!WhiteOnMove(currentMove)) {
6624                 DisplayMoveError(_("It is Black's turn"));
6625                 return;
6626             }
6627         }
6628         break;
6629
6630       case IcsPlayingBlack:
6631         /* User is moving for Black */
6632         if (WhiteOnMove(currentMove)) {
6633             if (!appData.premove) {
6634                 DisplayMoveError(_("It is White's turn"));
6635             } else if (toX >= 0 && toY >= 0) {
6636                 premoveToX = toX;
6637                 premoveToY = toY;
6638                 premoveFromX = fromX;
6639                 premoveFromY = fromY;
6640                 premovePromoChar = promoChar;
6641                 gotPremove = 1;
6642                 if (appData.debugMode)
6643                     fprintf(debugFP, "Got premove: fromX %d,"
6644                             "fromY %d, toX %d, toY %d\n",
6645                             fromX, fromY, toX, toY);
6646             }
6647             return;
6648         }
6649         break;
6650
6651       case IcsPlayingWhite:
6652         /* User is moving for White */
6653         if (!WhiteOnMove(currentMove)) {
6654             if (!appData.premove) {
6655                 DisplayMoveError(_("It is Black's turn"));
6656             } else if (toX >= 0 && toY >= 0) {
6657                 premoveToX = toX;
6658                 premoveToY = toY;
6659                 premoveFromX = fromX;
6660                 premoveFromY = fromY;
6661                 premovePromoChar = promoChar;
6662                 gotPremove = 1;
6663                 if (appData.debugMode)
6664                     fprintf(debugFP, "Got premove: fromX %d,"
6665                             "fromY %d, toX %d, toY %d\n",
6666                             fromX, fromY, toX, toY);
6667             }
6668             return;
6669         }
6670         break;
6671
6672       default:
6673         break;
6674
6675       case EditPosition:
6676         /* EditPosition, empty square, or different color piece;
6677            click-click move is possible */
6678         if (toX == -2 || toY == -2) {
6679             boards[0][fromY][fromX] = EmptySquare;
6680             DrawPosition(FALSE, boards[currentMove]);
6681             return;
6682         } else if (toX >= 0 && toY >= 0) {
6683             boards[0][toY][toX] = boards[0][fromY][fromX];
6684             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6685                 if(boards[0][fromY][0] != EmptySquare) {
6686                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6687                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6688                 }
6689             } else
6690             if(fromX == BOARD_RGHT+1) {
6691                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6692                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6693                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6694                 }
6695             } else
6696             boards[0][fromY][fromX] = gatingPiece;
6697             DrawPosition(FALSE, boards[currentMove]);
6698             return;
6699         }
6700         return;
6701     }
6702
6703     if(toX < 0 || toY < 0) return;
6704     pup = boards[currentMove][toY][toX];
6705
6706     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6707     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6708          if( pup != EmptySquare ) return;
6709          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6710            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6711                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6712            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6713            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6714            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6715            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6716          fromY = DROP_RANK;
6717     }
6718
6719     /* [HGM] always test for legality, to get promotion info */
6720     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6721                                          fromY, fromX, toY, toX, promoChar);
6722
6723     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6724
6725     /* [HGM] but possibly ignore an IllegalMove result */
6726     if (appData.testLegality) {
6727         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6728             DisplayMoveError(_("Illegal move"));
6729             return;
6730         }
6731     }
6732
6733     if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6734         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6735              ClearPremoveHighlights(); // was included
6736         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6737         return;
6738     }
6739
6740     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6741 }
6742
6743 /* Common tail of UserMoveEvent and DropMenuEvent */
6744 int
6745 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6746 {
6747     char *bookHit = 0;
6748
6749     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6750         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6751         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6752         if(WhiteOnMove(currentMove)) {
6753             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6754         } else {
6755             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6756         }
6757     }
6758
6759     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6760        move type in caller when we know the move is a legal promotion */
6761     if(moveType == NormalMove && promoChar)
6762         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6763
6764     /* [HGM] <popupFix> The following if has been moved here from
6765        UserMoveEvent(). Because it seemed to belong here (why not allow
6766        piece drops in training games?), and because it can only be
6767        performed after it is known to what we promote. */
6768     if (gameMode == Training) {
6769       /* compare the move played on the board to the next move in the
6770        * game. If they match, display the move and the opponent's response.
6771        * If they don't match, display an error message.
6772        */
6773       int saveAnimate;
6774       Board testBoard;
6775       CopyBoard(testBoard, boards[currentMove]);
6776       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6777
6778       if (CompareBoards(testBoard, boards[currentMove+1])) {
6779         ForwardInner(currentMove+1);
6780
6781         /* Autoplay the opponent's response.
6782          * if appData.animate was TRUE when Training mode was entered,
6783          * the response will be animated.
6784          */
6785         saveAnimate = appData.animate;
6786         appData.animate = animateTraining;
6787         ForwardInner(currentMove+1);
6788         appData.animate = saveAnimate;
6789
6790         /* check for the end of the game */
6791         if (currentMove >= forwardMostMove) {
6792           gameMode = PlayFromGameFile;
6793           ModeHighlight();
6794           SetTrainingModeOff();
6795           DisplayInformation(_("End of game"));
6796         }
6797       } else {
6798         DisplayError(_("Incorrect move"), 0);
6799       }
6800       return 1;
6801     }
6802
6803   /* Ok, now we know that the move is good, so we can kill
6804      the previous line in Analysis Mode */
6805   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6806                                 && currentMove < forwardMostMove) {
6807     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6808     else forwardMostMove = currentMove;
6809   }
6810
6811   ClearMap();
6812
6813   /* If we need the chess program but it's dead, restart it */
6814   ResurrectChessProgram();
6815
6816   /* A user move restarts a paused game*/
6817   if (pausing)
6818     PauseEvent();
6819
6820   thinkOutput[0] = NULLCHAR;
6821
6822   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6823
6824   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6825     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6826     return 1;
6827   }
6828
6829   if (gameMode == BeginningOfGame) {
6830     if (appData.noChessProgram) {
6831       gameMode = EditGame;
6832       SetGameInfo();
6833     } else {
6834       char buf[MSG_SIZ];
6835       gameMode = MachinePlaysBlack;
6836       StartClocks();
6837       SetGameInfo();
6838       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6839       DisplayTitle(buf);
6840       if (first.sendName) {
6841         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6842         SendToProgram(buf, &first);
6843       }
6844       StartClocks();
6845     }
6846     ModeHighlight();
6847   }
6848
6849   /* Relay move to ICS or chess engine */
6850   if (appData.icsActive) {
6851     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6852         gameMode == IcsExamining) {
6853       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6854         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6855         SendToICS("draw ");
6856         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6857       }
6858       // also send plain move, in case ICS does not understand atomic claims
6859       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6860       ics_user_moved = 1;
6861     }
6862   } else {
6863     if (first.sendTime && (gameMode == BeginningOfGame ||
6864                            gameMode == MachinePlaysWhite ||
6865                            gameMode == MachinePlaysBlack)) {
6866       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6867     }
6868     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6869          // [HGM] book: if program might be playing, let it use book
6870         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6871         first.maybeThinking = TRUE;
6872     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6873         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6874         SendBoard(&first, currentMove+1);
6875     } else SendMoveToProgram(forwardMostMove-1, &first);
6876     if (currentMove == cmailOldMove + 1) {
6877       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6878     }
6879   }
6880
6881   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6882
6883   switch (gameMode) {
6884   case EditGame:
6885     if(appData.testLegality)
6886     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6887     case MT_NONE:
6888     case MT_CHECK:
6889       break;
6890     case MT_CHECKMATE:
6891     case MT_STAINMATE:
6892       if (WhiteOnMove(currentMove)) {
6893         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6894       } else {
6895         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6896       }
6897       break;
6898     case MT_STALEMATE:
6899       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6900       break;
6901     }
6902     break;
6903
6904   case MachinePlaysBlack:
6905   case MachinePlaysWhite:
6906     /* disable certain menu options while machine is thinking */
6907     SetMachineThinkingEnables();
6908     break;
6909
6910   default:
6911     break;
6912   }
6913
6914   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6915   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6916
6917   if(bookHit) { // [HGM] book: simulate book reply
6918         static char bookMove[MSG_SIZ]; // a bit generous?
6919
6920         programStats.nodes = programStats.depth = programStats.time =
6921         programStats.score = programStats.got_only_move = 0;
6922         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6923
6924         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6925         strcat(bookMove, bookHit);
6926         HandleMachineMove(bookMove, &first);
6927   }
6928   return 1;
6929 }
6930
6931 void
6932 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6933 {
6934     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6935     Markers *m = (Markers *) closure;
6936     if(rf == fromY && ff == fromX)
6937         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6938                          || kind == WhiteCapturesEnPassant
6939                          || kind == BlackCapturesEnPassant);
6940     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6941 }
6942
6943 void
6944 MarkTargetSquares (int clear)
6945 {
6946   int x, y;
6947   if(clear) // no reason to ever suppress clearing
6948     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6949   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6950      !appData.testLegality || gameMode == EditPosition) return;
6951   if(!clear) {
6952     int capt = 0;
6953     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6954     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6955       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6956       if(capt)
6957       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6958     }
6959   }
6960   DrawPosition(FALSE, NULL);
6961 }
6962
6963 int
6964 Explode (Board board, int fromX, int fromY, int toX, int toY)
6965 {
6966     if(gameInfo.variant == VariantAtomic &&
6967        (board[toY][toX] != EmptySquare ||                     // capture?
6968         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6969                          board[fromY][fromX] == BlackPawn   )
6970       )) {
6971         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6972         return TRUE;
6973     }
6974     return FALSE;
6975 }
6976
6977 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6978
6979 int
6980 CanPromote (ChessSquare piece, int y)
6981 {
6982         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6983         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6984         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6985            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6986            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6987                                                   gameInfo.variant == VariantMakruk) return FALSE;
6988         return (piece == BlackPawn && y == 1 ||
6989                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6990                 piece == BlackLance && y == 1 ||
6991                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6992 }
6993
6994 void
6995 LeftClick (ClickType clickType, int xPix, int yPix)
6996 {
6997     int x, y;
6998     Boolean saveAnimate;
6999     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7000     char promoChoice = NULLCHAR;
7001     ChessSquare piece;
7002     static TimeMark lastClickTime, prevClickTime;
7003
7004     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7005
7006     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7007
7008     if (clickType == Press) ErrorPopDown();
7009
7010     x = EventToSquare(xPix, BOARD_WIDTH);
7011     y = EventToSquare(yPix, BOARD_HEIGHT);
7012     if (!flipView && y >= 0) {
7013         y = BOARD_HEIGHT - 1 - y;
7014     }
7015     if (flipView && x >= 0) {
7016         x = BOARD_WIDTH - 1 - x;
7017     }
7018
7019     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7020         defaultPromoChoice = promoSweep;
7021         promoSweep = EmptySquare;   // terminate sweep
7022         promoDefaultAltered = TRUE;
7023         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7024     }
7025
7026     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7027         if(clickType == Release) return; // ignore upclick of click-click destination
7028         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7029         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7030         if(gameInfo.holdingsWidth &&
7031                 (WhiteOnMove(currentMove)
7032                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7033                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7034             // click in right holdings, for determining promotion piece
7035             ChessSquare p = boards[currentMove][y][x];
7036             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7037             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7038             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7039                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7040                 fromX = fromY = -1;
7041                 return;
7042             }
7043         }
7044         DrawPosition(FALSE, boards[currentMove]);
7045         return;
7046     }
7047
7048     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7049     if(clickType == Press
7050             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7051               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7052               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7053         return;
7054
7055     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7056         // could be static click on premove from-square: abort premove
7057         gotPremove = 0;
7058         ClearPremoveHighlights();
7059     }
7060
7061     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7062         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7063
7064     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7065         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7066                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7067         defaultPromoChoice = DefaultPromoChoice(side);
7068     }
7069
7070     autoQueen = appData.alwaysPromoteToQueen;
7071
7072     if (fromX == -1) {
7073       int originalY = y;
7074       gatingPiece = EmptySquare;
7075       if (clickType != Press) {
7076         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7077             DragPieceEnd(xPix, yPix); dragging = 0;
7078             DrawPosition(FALSE, NULL);
7079         }
7080         return;
7081       }
7082       doubleClick = FALSE;
7083       fromX = x; fromY = y; toX = toY = -1;
7084       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7085          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7086          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7087             /* First square */
7088             if (OKToStartUserMove(fromX, fromY)) {
7089                 second = 0;
7090                 MarkTargetSquares(0);
7091                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7092                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7093                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7094                     promoSweep = defaultPromoChoice;
7095                     selectFlag = 0; lastX = xPix; lastY = yPix;
7096                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7097                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7098                 }
7099                 if (appData.highlightDragging) {
7100                     SetHighlights(fromX, fromY, -1, -1);
7101                 } else {
7102                     ClearHighlights();
7103                 }
7104             } else fromX = fromY = -1;
7105             return;
7106         }
7107     }
7108
7109     /* fromX != -1 */
7110     if (clickType == Press && gameMode != EditPosition) {
7111         ChessSquare fromP;
7112         ChessSquare toP;
7113         int frc;
7114
7115         // ignore off-board to clicks
7116         if(y < 0 || x < 0) return;
7117
7118         /* Check if clicking again on the same color piece */
7119         fromP = boards[currentMove][fromY][fromX];
7120         toP = boards[currentMove][y][x];
7121         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7122         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7123              WhitePawn <= toP && toP <= WhiteKing &&
7124              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7125              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7126             (BlackPawn <= fromP && fromP <= BlackKing &&
7127              BlackPawn <= toP && toP <= BlackKing &&
7128              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7129              !(fromP == BlackKing && toP == BlackRook && frc))) {
7130             /* Clicked again on same color piece -- changed his mind */
7131             second = (x == fromX && y == fromY);
7132             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7133                 second = FALSE; // first double-click rather than scond click
7134                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7135             }
7136             promoDefaultAltered = FALSE;
7137             MarkTargetSquares(1);
7138            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7139             if (appData.highlightDragging) {
7140                 SetHighlights(x, y, -1, -1);
7141             } else {
7142                 ClearHighlights();
7143             }
7144             if (OKToStartUserMove(x, y)) {
7145                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7146                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7147                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7148                  gatingPiece = boards[currentMove][fromY][fromX];
7149                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7150                 fromX = x;
7151                 fromY = y; dragging = 1;
7152                 MarkTargetSquares(0);
7153                 DragPieceBegin(xPix, yPix, FALSE);
7154                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7155                     promoSweep = defaultPromoChoice;
7156                     selectFlag = 0; lastX = xPix; lastY = yPix;
7157                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7158                 }
7159             }
7160            }
7161            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7162            second = FALSE; 
7163         }
7164         // ignore clicks on holdings
7165         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7166     }
7167
7168     if (clickType == Release && x == fromX && y == fromY) {
7169         DragPieceEnd(xPix, yPix); dragging = 0;
7170         if(clearFlag) {
7171             // a deferred attempt to click-click move an empty square on top of a piece
7172             boards[currentMove][y][x] = EmptySquare;
7173             ClearHighlights();
7174             DrawPosition(FALSE, boards[currentMove]);
7175             fromX = fromY = -1; clearFlag = 0;
7176             return;
7177         }
7178         if (appData.animateDragging) {
7179             /* Undo animation damage if any */
7180             DrawPosition(FALSE, NULL);
7181         }
7182         if (second || sweepSelecting) {
7183             /* Second up/down in same square; just abort move */
7184             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7185             second = sweepSelecting = 0;
7186             fromX = fromY = -1;
7187             gatingPiece = EmptySquare;
7188             ClearHighlights();
7189             gotPremove = 0;
7190             ClearPremoveHighlights();
7191         } else {
7192             /* First upclick in same square; start click-click mode */
7193             SetHighlights(x, y, -1, -1);
7194         }
7195         return;
7196     }
7197
7198     clearFlag = 0;
7199
7200     /* we now have a different from- and (possibly off-board) to-square */
7201     /* Completed move */
7202     if(!sweepSelecting) {
7203         toX = x;
7204         toY = y;
7205     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7206
7207     saveAnimate = appData.animate;
7208     if (clickType == Press) {
7209         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7210             // must be Edit Position mode with empty-square selected
7211             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7212             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7213             return;
7214         }
7215         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7216           if(appData.sweepSelect) {
7217             ChessSquare piece = boards[currentMove][fromY][fromX];
7218             promoSweep = defaultPromoChoice;
7219             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7220             selectFlag = 0; lastX = xPix; lastY = yPix;
7221             Sweep(0); // Pawn that is going to promote: preview promotion piece
7222             sweepSelecting = 1;
7223             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7224             MarkTargetSquares(1);
7225           }
7226           return; // promo popup appears on up-click
7227         }
7228         /* Finish clickclick move */
7229         if (appData.animate || appData.highlightLastMove) {
7230             SetHighlights(fromX, fromY, toX, toY);
7231         } else {
7232             ClearHighlights();
7233         }
7234     } else {
7235         /* Finish drag move */
7236         if (appData.highlightLastMove) {
7237             SetHighlights(fromX, fromY, toX, toY);
7238         } else {
7239             ClearHighlights();
7240         }
7241         DragPieceEnd(xPix, yPix); dragging = 0;
7242         /* Don't animate move and drag both */
7243         appData.animate = FALSE;
7244     }
7245     MarkTargetSquares(1);
7246
7247     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7248     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7249         ChessSquare piece = boards[currentMove][fromY][fromX];
7250         if(gameMode == EditPosition && piece != EmptySquare &&
7251            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7252             int n;
7253
7254             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7255                 n = PieceToNumber(piece - (int)BlackPawn);
7256                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7257                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7258                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7259             } else
7260             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7261                 n = PieceToNumber(piece);
7262                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7263                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7264                 boards[currentMove][n][BOARD_WIDTH-2]++;
7265             }
7266             boards[currentMove][fromY][fromX] = EmptySquare;
7267         }
7268         ClearHighlights();
7269         fromX = fromY = -1;
7270         DrawPosition(TRUE, boards[currentMove]);
7271         return;
7272     }
7273
7274     // off-board moves should not be highlighted
7275     if(x < 0 || y < 0) ClearHighlights();
7276
7277     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7278
7279     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7280         SetHighlights(fromX, fromY, toX, toY);
7281         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7282             // [HGM] super: promotion to captured piece selected from holdings
7283             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7284             promotionChoice = TRUE;
7285             // kludge follows to temporarily execute move on display, without promoting yet
7286             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7287             boards[currentMove][toY][toX] = p;
7288             DrawPosition(FALSE, boards[currentMove]);
7289             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7290             boards[currentMove][toY][toX] = q;
7291             DisplayMessage("Click in holdings to choose piece", "");
7292             return;
7293         }
7294         PromotionPopUp();
7295     } else {
7296         int oldMove = currentMove;
7297         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7298         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7299         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7300         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7301            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7302             DrawPosition(TRUE, boards[currentMove]);
7303         fromX = fromY = -1;
7304     }
7305     appData.animate = saveAnimate;
7306     if (appData.animate || appData.animateDragging) {
7307         /* Undo animation damage if needed */
7308         DrawPosition(FALSE, NULL);
7309     }
7310 }
7311
7312 int
7313 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7314 {   // front-end-free part taken out of PieceMenuPopup
7315     int whichMenu; int xSqr, ySqr;
7316
7317     if(seekGraphUp) { // [HGM] seekgraph
7318         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7319         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7320         return -2;
7321     }
7322
7323     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7324          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7325         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7326         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7327         if(action == Press)   {
7328             originalFlip = flipView;
7329             flipView = !flipView; // temporarily flip board to see game from partners perspective
7330             DrawPosition(TRUE, partnerBoard);
7331             DisplayMessage(partnerStatus, "");
7332             partnerUp = TRUE;
7333         } else if(action == Release) {
7334             flipView = originalFlip;
7335             DrawPosition(TRUE, boards[currentMove]);
7336             partnerUp = FALSE;
7337         }
7338         return -2;
7339     }
7340
7341     xSqr = EventToSquare(x, BOARD_WIDTH);
7342     ySqr = EventToSquare(y, BOARD_HEIGHT);
7343     if (action == Release) {
7344         if(pieceSweep != EmptySquare) {
7345             EditPositionMenuEvent(pieceSweep, toX, toY);
7346             pieceSweep = EmptySquare;
7347         } else UnLoadPV(); // [HGM] pv
7348     }
7349     if (action != Press) return -2; // return code to be ignored
7350     switch (gameMode) {
7351       case IcsExamining:
7352         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7353       case EditPosition:
7354         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7355         if (xSqr < 0 || ySqr < 0) return -1;
7356         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7357         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7358         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7359         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7360         NextPiece(0);
7361         return 2; // grab
7362       case IcsObserving:
7363         if(!appData.icsEngineAnalyze) return -1;
7364       case IcsPlayingWhite:
7365       case IcsPlayingBlack:
7366         if(!appData.zippyPlay) goto noZip;
7367       case AnalyzeMode:
7368       case AnalyzeFile:
7369       case MachinePlaysWhite:
7370       case MachinePlaysBlack:
7371       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7372         if (!appData.dropMenu) {
7373           LoadPV(x, y);
7374           return 2; // flag front-end to grab mouse events
7375         }
7376         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7377            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7378       case EditGame:
7379       noZip:
7380         if (xSqr < 0 || ySqr < 0) return -1;
7381         if (!appData.dropMenu || appData.testLegality &&
7382             gameInfo.variant != VariantBughouse &&
7383             gameInfo.variant != VariantCrazyhouse) return -1;
7384         whichMenu = 1; // drop menu
7385         break;
7386       default:
7387         return -1;
7388     }
7389
7390     if (((*fromX = xSqr) < 0) ||
7391         ((*fromY = ySqr) < 0)) {
7392         *fromX = *fromY = -1;
7393         return -1;
7394     }
7395     if (flipView)
7396       *fromX = BOARD_WIDTH - 1 - *fromX;
7397     else
7398       *fromY = BOARD_HEIGHT - 1 - *fromY;
7399
7400     return whichMenu;
7401 }
7402
7403 void
7404 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7405 {
7406 //    char * hint = lastHint;
7407     FrontEndProgramStats stats;
7408
7409     stats.which = cps == &first ? 0 : 1;
7410     stats.depth = cpstats->depth;
7411     stats.nodes = cpstats->nodes;
7412     stats.score = cpstats->score;
7413     stats.time = cpstats->time;
7414     stats.pv = cpstats->movelist;
7415     stats.hint = lastHint;
7416     stats.an_move_index = 0;
7417     stats.an_move_count = 0;
7418
7419     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7420         stats.hint = cpstats->move_name;
7421         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7422         stats.an_move_count = cpstats->nr_moves;
7423     }
7424
7425     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
7426
7427     SetProgramStats( &stats );
7428 }
7429
7430 void
7431 ClearEngineOutputPane (int which)
7432 {
7433     static FrontEndProgramStats dummyStats;
7434     dummyStats.which = which;
7435     dummyStats.pv = "#";
7436     SetProgramStats( &dummyStats );
7437 }
7438
7439 #define MAXPLAYERS 500
7440
7441 char *
7442 TourneyStandings (int display)
7443 {
7444     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7445     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7446     char result, *p, *names[MAXPLAYERS];
7447
7448     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7449         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7450     names[0] = p = strdup(appData.participants);
7451     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7452
7453     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7454
7455     while(result = appData.results[nr]) {
7456         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7457         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7458         wScore = bScore = 0;
7459         switch(result) {
7460           case '+': wScore = 2; break;
7461           case '-': bScore = 2; break;
7462           case '=': wScore = bScore = 1; break;
7463           case ' ':
7464           case '*': return strdup("busy"); // tourney not finished
7465         }
7466         score[w] += wScore;
7467         score[b] += bScore;
7468         games[w]++;
7469         games[b]++;
7470         nr++;
7471     }
7472     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7473     for(w=0; w<nPlayers; w++) {
7474         bScore = -1;
7475         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7476         ranking[w] = b; points[w] = bScore; score[b] = -2;
7477     }
7478     p = malloc(nPlayers*34+1);
7479     for(w=0; w<nPlayers && w<display; w++)
7480         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7481     free(names[0]);
7482     return p;
7483 }
7484
7485 void
7486 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7487 {       // count all piece types
7488         int p, f, r;
7489         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7490         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7491         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7492                 p = board[r][f];
7493                 pCnt[p]++;
7494                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7495                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7496                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7497                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7498                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7499                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7500         }
7501 }
7502
7503 int
7504 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7505 {
7506         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7507         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7508
7509         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7510         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7511         if(myPawns == 2 && nMine == 3) // KPP
7512             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7513         if(myPawns == 1 && nMine == 2) // KP
7514             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7515         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7516             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7517         if(myPawns) return FALSE;
7518         if(pCnt[WhiteRook+side])
7519             return pCnt[BlackRook-side] ||
7520                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7521                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7522                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7523         if(pCnt[WhiteCannon+side]) {
7524             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7525             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7526         }
7527         if(pCnt[WhiteKnight+side])
7528             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7529         return FALSE;
7530 }
7531
7532 int
7533 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7534 {
7535         VariantClass v = gameInfo.variant;
7536
7537         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7538         if(v == VariantShatranj) return TRUE; // always winnable through baring
7539         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7540         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7541
7542         if(v == VariantXiangqi) {
7543                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7544
7545                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7546                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7547                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7548                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7549                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7550                 if(stale) // we have at least one last-rank P plus perhaps C
7551                     return majors // KPKX
7552                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7553                 else // KCA*E*
7554                     return pCnt[WhiteFerz+side] // KCAK
7555                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7556                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7557                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7558
7559         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7560                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7561
7562                 if(nMine == 1) return FALSE; // bare King
7563                 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
7564                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7565                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7566                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7567                 if(pCnt[WhiteKnight+side])
7568                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7569                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7570                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7571                 if(nBishops)
7572                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7573                 if(pCnt[WhiteAlfil+side])
7574                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7575                 if(pCnt[WhiteWazir+side])
7576                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7577         }
7578
7579         return TRUE;
7580 }
7581
7582 int
7583 CompareWithRights (Board b1, Board b2)
7584 {
7585     int rights = 0;
7586     if(!CompareBoards(b1, b2)) return FALSE;
7587     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7588     /* compare castling rights */
7589     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7590            rights++; /* King lost rights, while rook still had them */
7591     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7592         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7593            rights++; /* but at least one rook lost them */
7594     }
7595     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7596            rights++;
7597     if( b1[CASTLING][5] != NoRights ) {
7598         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7599            rights++;
7600     }
7601     return rights == 0;
7602 }
7603
7604 int
7605 Adjudicate (ChessProgramState *cps)
7606 {       // [HGM] some adjudications useful with buggy engines
7607         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7608         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7609         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7610         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7611         int k, count = 0; static int bare = 1;
7612         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7613         Boolean canAdjudicate = !appData.icsActive;
7614
7615         // most tests only when we understand the game, i.e. legality-checking on
7616             if( appData.testLegality )
7617             {   /* [HGM] Some more adjudications for obstinate engines */
7618                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7619                 static int moveCount = 6;
7620                 ChessMove result;
7621                 char *reason = NULL;
7622
7623                 /* Count what is on board. */
7624                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7625
7626                 /* Some material-based adjudications that have to be made before stalemate test */
7627                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7628                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7629                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7630                      if(canAdjudicate && appData.checkMates) {
7631                          if(engineOpponent)
7632                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7633                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7634                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7635                          return 1;
7636                      }
7637                 }
7638
7639                 /* Bare King in Shatranj (loses) or Losers (wins) */
7640                 if( nrW == 1 || nrB == 1) {
7641                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7642                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7643                      if(canAdjudicate && appData.checkMates) {
7644                          if(engineOpponent)
7645                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7646                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7647                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7648                          return 1;
7649                      }
7650                   } else
7651                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7652                   {    /* bare King */
7653                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7654                         if(canAdjudicate && appData.checkMates) {
7655                             /* but only adjudicate if adjudication enabled */
7656                             if(engineOpponent)
7657                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7658                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7659                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7660                             return 1;
7661                         }
7662                   }
7663                 } else bare = 1;
7664
7665
7666             // don't wait for engine to announce game end if we can judge ourselves
7667             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7668               case MT_CHECK:
7669                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7670                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7671                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7672                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7673                             checkCnt++;
7674                         if(checkCnt >= 2) {
7675                             reason = "Xboard adjudication: 3rd check";
7676                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7677                             break;
7678                         }
7679                     }
7680                 }
7681               case MT_NONE:
7682               default:
7683                 break;
7684               case MT_STALEMATE:
7685               case MT_STAINMATE:
7686                 reason = "Xboard adjudication: Stalemate";
7687                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7688                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7689                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7690                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7691                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7692                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7693                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7694                                                                         EP_CHECKMATE : EP_WINS);
7695                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7696                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7697                 }
7698                 break;
7699               case MT_CHECKMATE:
7700                 reason = "Xboard adjudication: Checkmate";
7701                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7702                 break;
7703             }
7704
7705                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7706                     case EP_STALEMATE:
7707                         result = GameIsDrawn; break;
7708                     case EP_CHECKMATE:
7709                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7710                     case EP_WINS:
7711                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7712                     default:
7713                         result = EndOfFile;
7714                 }
7715                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7716                     if(engineOpponent)
7717                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7718                     GameEnds( result, reason, GE_XBOARD );
7719                     return 1;
7720                 }
7721
7722                 /* Next absolutely insufficient mating material. */
7723                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7724                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7725                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7726
7727                      /* always flag draws, for judging claims */
7728                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7729
7730                      if(canAdjudicate && appData.materialDraws) {
7731                          /* but only adjudicate them if adjudication enabled */
7732                          if(engineOpponent) {
7733                            SendToProgram("force\n", engineOpponent); // suppress reply
7734                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7735                          }
7736                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7737                          return 1;
7738                      }
7739                 }
7740
7741                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7742                 if(gameInfo.variant == VariantXiangqi ?
7743                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7744                  : nrW + nrB == 4 &&
7745                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7746                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7747                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7748                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7749                    ) ) {
7750                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7751                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7752                           if(engineOpponent) {
7753                             SendToProgram("force\n", engineOpponent); // suppress reply
7754                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7755                           }
7756                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7757                           return 1;
7758                      }
7759                 } else moveCount = 6;
7760             }
7761
7762         // Repetition draws and 50-move rule can be applied independently of legality testing
7763
7764                 /* Check for rep-draws */
7765                 count = 0;
7766                 for(k = forwardMostMove-2;
7767                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7768                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7769                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7770                     k-=2)
7771                 {   int rights=0;
7772                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7773                         /* compare castling rights */
7774                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7775                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7776                                 rights++; /* King lost rights, while rook still had them */
7777                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7778                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7779                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7780                                    rights++; /* but at least one rook lost them */
7781                         }
7782                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7783                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7784                                 rights++;
7785                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7786                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7787                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7788                                    rights++;
7789                         }
7790                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7791                             && appData.drawRepeats > 1) {
7792                              /* adjudicate after user-specified nr of repeats */
7793                              int result = GameIsDrawn;
7794                              char *details = "XBoard adjudication: repetition draw";
7795                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7796                                 // [HGM] xiangqi: check for forbidden perpetuals
7797                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7798                                 for(m=forwardMostMove; m>k; m-=2) {
7799                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7800                                         ourPerpetual = 0; // the current mover did not always check
7801                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7802                                         hisPerpetual = 0; // the opponent did not always check
7803                                 }
7804                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7805                                                                         ourPerpetual, hisPerpetual);
7806                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7807                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7808                                     details = "Xboard adjudication: perpetual checking";
7809                                 } else
7810                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7811                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7812                                 } else
7813                                 // Now check for perpetual chases
7814                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7815                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7816                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7817                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7818                                         static char resdet[MSG_SIZ];
7819                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7820                                         details = resdet;
7821                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7822                                     } else
7823                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7824                                         break; // Abort repetition-checking loop.
7825                                 }
7826                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7827                              }
7828                              if(engineOpponent) {
7829                                SendToProgram("force\n", engineOpponent); // suppress reply
7830                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7831                              }
7832                              GameEnds( result, details, GE_XBOARD );
7833                              return 1;
7834                         }
7835                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7836                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7837                     }
7838                 }
7839
7840                 /* Now we test for 50-move draws. Determine ply count */
7841                 count = forwardMostMove;
7842                 /* look for last irreversble move */
7843                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7844                     count--;
7845                 /* if we hit starting position, add initial plies */
7846                 if( count == backwardMostMove )
7847                     count -= initialRulePlies;
7848                 count = forwardMostMove - count;
7849                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7850                         // adjust reversible move counter for checks in Xiangqi
7851                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7852                         if(i < backwardMostMove) i = backwardMostMove;
7853                         while(i <= forwardMostMove) {
7854                                 lastCheck = inCheck; // check evasion does not count
7855                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7856                                 if(inCheck || lastCheck) count--; // check does not count
7857                                 i++;
7858                         }
7859                 }
7860                 if( count >= 100)
7861                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7862                          /* this is used to judge if draw claims are legal */
7863                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7864                          if(engineOpponent) {
7865                            SendToProgram("force\n", engineOpponent); // suppress reply
7866                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7867                          }
7868                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7869                          return 1;
7870                 }
7871
7872                 /* if draw offer is pending, treat it as a draw claim
7873                  * when draw condition present, to allow engines a way to
7874                  * claim draws before making their move to avoid a race
7875                  * condition occurring after their move
7876                  */
7877                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7878                          char *p = NULL;
7879                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7880                              p = "Draw claim: 50-move rule";
7881                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7882                              p = "Draw claim: 3-fold repetition";
7883                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7884                              p = "Draw claim: insufficient mating material";
7885                          if( p != NULL && canAdjudicate) {
7886                              if(engineOpponent) {
7887                                SendToProgram("force\n", engineOpponent); // suppress reply
7888                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7889                              }
7890                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7891                              return 1;
7892                          }
7893                 }
7894
7895                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7896                     if(engineOpponent) {
7897                       SendToProgram("force\n", engineOpponent); // suppress reply
7898                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7899                     }
7900                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7901                     return 1;
7902                 }
7903         return 0;
7904 }
7905
7906 char *
7907 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7908 {   // [HGM] book: this routine intercepts moves to simulate book replies
7909     char *bookHit = NULL;
7910
7911     //first determine if the incoming move brings opponent into his book
7912     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7913         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7914     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7915     if(bookHit != NULL && !cps->bookSuspend) {
7916         // make sure opponent is not going to reply after receiving move to book position
7917         SendToProgram("force\n", cps);
7918         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7919     }
7920     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7921     // now arrange restart after book miss
7922     if(bookHit) {
7923         // after a book hit we never send 'go', and the code after the call to this routine
7924         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7925         char buf[MSG_SIZ], *move = bookHit;
7926         if(cps->useSAN) {
7927             int fromX, fromY, toX, toY;
7928             char promoChar;
7929             ChessMove moveType;
7930             move = buf + 30;
7931             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7932                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7933                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7934                                     PosFlags(forwardMostMove),
7935                                     fromY, fromX, toY, toX, promoChar, move);
7936             } else {
7937                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7938                 bookHit = NULL;
7939             }
7940         }
7941         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7942         SendToProgram(buf, cps);
7943         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7944     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7945         SendToProgram("go\n", cps);
7946         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7947     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7948         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7949             SendToProgram("go\n", cps);
7950         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7951     }
7952     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7953 }
7954
7955 int
7956 LoadError (char *errmess, ChessProgramState *cps)
7957 {   // unloads engine and switches back to -ncp mode if it was first
7958     if(cps->initDone) return FALSE;
7959     cps->isr = NULL; // this should suppress further error popups from breaking pipes
7960     DestroyChildProcess(cps->pr, 9 ); // just to be sure
7961     cps->pr = NoProc; 
7962     if(cps == &first) {
7963         appData.noChessProgram = TRUE;
7964         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
7965         gameMode = BeginningOfGame; ModeHighlight();
7966         SetNCPMode();
7967     }
7968     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
7969     DisplayMessage("", ""); // erase waiting message
7970     if(errmess) DisplayError(errmess, 0); // announce reason, if given
7971     return TRUE;
7972 }
7973
7974 char *savedMessage;
7975 ChessProgramState *savedState;
7976 void
7977 DeferredBookMove (void)
7978 {
7979         if(savedState->lastPing != savedState->lastPong)
7980                     ScheduleDelayedEvent(DeferredBookMove, 10);
7981         else
7982         HandleMachineMove(savedMessage, savedState);
7983 }
7984
7985 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7986
7987 void
7988 HandleMachineMove (char *message, ChessProgramState *cps)
7989 {
7990     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7991     char realname[MSG_SIZ];
7992     int fromX, fromY, toX, toY;
7993     ChessMove moveType;
7994     char promoChar;
7995     char *p, *pv=buf1;
7996     int machineWhite, oldError;
7997     char *bookHit;
7998
7999     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8000         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8001         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8002             DisplayError(_("Invalid pairing from pairing engine"), 0);
8003             return;
8004         }
8005         pairingReceived = 1;
8006         NextMatchGame();
8007         return; // Skim the pairing messages here.
8008     }
8009
8010     oldError = cps->userError; cps->userError = 0;
8011
8012 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8013     /*
8014      * Kludge to ignore BEL characters
8015      */
8016     while (*message == '\007') message++;
8017
8018     /*
8019      * [HGM] engine debug message: ignore lines starting with '#' character
8020      */
8021     if(cps->debug && *message == '#') return;
8022
8023     /*
8024      * Look for book output
8025      */
8026     if (cps == &first && bookRequested) {
8027         if (message[0] == '\t' || message[0] == ' ') {
8028             /* Part of the book output is here; append it */
8029             strcat(bookOutput, message);
8030             strcat(bookOutput, "  \n");
8031             return;
8032         } else if (bookOutput[0] != NULLCHAR) {
8033             /* All of book output has arrived; display it */
8034             char *p = bookOutput;
8035             while (*p != NULLCHAR) {
8036                 if (*p == '\t') *p = ' ';
8037                 p++;
8038             }
8039             DisplayInformation(bookOutput);
8040             bookRequested = FALSE;
8041             /* Fall through to parse the current output */
8042         }
8043     }
8044
8045     /*
8046      * Look for machine move.
8047      */
8048     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8049         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8050     {
8051         /* This method is only useful on engines that support ping */
8052         if (cps->lastPing != cps->lastPong) {
8053           if (gameMode == BeginningOfGame) {
8054             /* Extra move from before last new; ignore */
8055             if (appData.debugMode) {
8056                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8057             }
8058           } else {
8059             if (appData.debugMode) {
8060                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8061                         cps->which, gameMode);
8062             }
8063
8064             SendToProgram("undo\n", cps);
8065           }
8066           return;
8067         }
8068
8069         switch (gameMode) {
8070           case BeginningOfGame:
8071             /* Extra move from before last reset; ignore */
8072             if (appData.debugMode) {
8073                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8074             }
8075             return;
8076
8077           case EndOfGame:
8078           case IcsIdle:
8079           default:
8080             /* Extra move after we tried to stop.  The mode test is
8081                not a reliable way of detecting this problem, but it's
8082                the best we can do on engines that don't support ping.
8083             */
8084             if (appData.debugMode) {
8085                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8086                         cps->which, gameMode);
8087             }
8088             SendToProgram("undo\n", cps);
8089             return;
8090
8091           case MachinePlaysWhite:
8092           case IcsPlayingWhite:
8093             machineWhite = TRUE;
8094             break;
8095
8096           case MachinePlaysBlack:
8097           case IcsPlayingBlack:
8098             machineWhite = FALSE;
8099             break;
8100
8101           case TwoMachinesPlay:
8102             machineWhite = (cps->twoMachinesColor[0] == 'w');
8103             break;
8104         }
8105         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8106             if (appData.debugMode) {
8107                 fprintf(debugFP,
8108                         "Ignoring move out of turn by %s, gameMode %d"
8109                         ", forwardMost %d\n",
8110                         cps->which, gameMode, forwardMostMove);
8111             }
8112             return;
8113         }
8114
8115         if(cps->alphaRank) AlphaRank(machineMove, 4);
8116         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8117                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8118             /* Machine move could not be parsed; ignore it. */
8119           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8120                     machineMove, _(cps->which));
8121             DisplayError(buf1, 0);
8122             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8123                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8124             if (gameMode == TwoMachinesPlay) {
8125               GameEnds(machineWhite ? BlackWins : WhiteWins,
8126                        buf1, GE_XBOARD);
8127             }
8128             return;
8129         }
8130
8131         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8132         /* So we have to redo legality test with true e.p. status here,  */
8133         /* to make sure an illegal e.p. capture does not slip through,   */
8134         /* to cause a forfeit on a justified illegal-move complaint      */
8135         /* of the opponent.                                              */
8136         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8137            ChessMove moveType;
8138            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8139                              fromY, fromX, toY, toX, promoChar);
8140             if(moveType == IllegalMove) {
8141               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8142                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8143                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8144                            buf1, GE_XBOARD);
8145                 return;
8146            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8147            /* [HGM] Kludge to handle engines that send FRC-style castling
8148               when they shouldn't (like TSCP-Gothic) */
8149            switch(moveType) {
8150              case WhiteASideCastleFR:
8151              case BlackASideCastleFR:
8152                toX+=2;
8153                currentMoveString[2]++;
8154                break;
8155              case WhiteHSideCastleFR:
8156              case BlackHSideCastleFR:
8157                toX--;
8158                currentMoveString[2]--;
8159                break;
8160              default: ; // nothing to do, but suppresses warning of pedantic compilers
8161            }
8162         }
8163         hintRequested = FALSE;
8164         lastHint[0] = NULLCHAR;
8165         bookRequested = FALSE;
8166         /* Program may be pondering now */
8167         cps->maybeThinking = TRUE;
8168         if (cps->sendTime == 2) cps->sendTime = 1;
8169         if (cps->offeredDraw) cps->offeredDraw--;
8170
8171         /* [AS] Save move info*/
8172         pvInfoList[ forwardMostMove ].score = programStats.score;
8173         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8174         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8175
8176         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8177
8178         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8179         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8180             int count = 0;
8181
8182             while( count < adjudicateLossPlies ) {
8183                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8184
8185                 if( count & 1 ) {
8186                     score = -score; /* Flip score for winning side */
8187                 }
8188
8189                 if( score > adjudicateLossThreshold ) {
8190                     break;
8191                 }
8192
8193                 count++;
8194             }
8195
8196             if( count >= adjudicateLossPlies ) {
8197                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8198
8199                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8200                     "Xboard adjudication",
8201                     GE_XBOARD );
8202
8203                 return;
8204             }
8205         }
8206
8207         if(Adjudicate(cps)) {
8208             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8209             return; // [HGM] adjudicate: for all automatic game ends
8210         }
8211
8212 #if ZIPPY
8213         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8214             first.initDone) {
8215           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8216                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8217                 SendToICS("draw ");
8218                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8219           }
8220           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8221           ics_user_moved = 1;
8222           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8223                 char buf[3*MSG_SIZ];
8224
8225                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8226                         programStats.score / 100.,
8227                         programStats.depth,
8228                         programStats.time / 100.,
8229                         (unsigned int)programStats.nodes,
8230                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8231                         programStats.movelist);
8232                 SendToICS(buf);
8233 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8234           }
8235         }
8236 #endif
8237
8238         /* [AS] Clear stats for next move */
8239         ClearProgramStats();
8240         thinkOutput[0] = NULLCHAR;
8241         hiddenThinkOutputState = 0;
8242
8243         bookHit = NULL;
8244         if (gameMode == TwoMachinesPlay) {
8245             /* [HGM] relaying draw offers moved to after reception of move */
8246             /* and interpreting offer as claim if it brings draw condition */
8247             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8248                 SendToProgram("draw\n", cps->other);
8249             }
8250             if (cps->other->sendTime) {
8251                 SendTimeRemaining(cps->other,
8252                                   cps->other->twoMachinesColor[0] == 'w');
8253             }
8254             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8255             if (firstMove && !bookHit) {
8256                 firstMove = FALSE;
8257                 if (cps->other->useColors) {
8258                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8259                 }
8260                 SendToProgram("go\n", cps->other);
8261             }
8262             cps->other->maybeThinking = TRUE;
8263         }
8264
8265         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8266
8267         if (!pausing && appData.ringBellAfterMoves) {
8268             RingBell();
8269         }
8270
8271         /*
8272          * Reenable menu items that were disabled while
8273          * machine was thinking
8274          */
8275         if (gameMode != TwoMachinesPlay)
8276             SetUserThinkingEnables();
8277
8278         // [HGM] book: after book hit opponent has received move and is now in force mode
8279         // force the book reply into it, and then fake that it outputted this move by jumping
8280         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8281         if(bookHit) {
8282                 static char bookMove[MSG_SIZ]; // a bit generous?
8283
8284                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8285                 strcat(bookMove, bookHit);
8286                 message = bookMove;
8287                 cps = cps->other;
8288                 programStats.nodes = programStats.depth = programStats.time =
8289                 programStats.score = programStats.got_only_move = 0;
8290                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8291
8292                 if(cps->lastPing != cps->lastPong) {
8293                     savedMessage = message; // args for deferred call
8294                     savedState = cps;
8295                     ScheduleDelayedEvent(DeferredBookMove, 10);
8296                     return;
8297                 }
8298                 goto FakeBookMove;
8299         }
8300
8301         return;
8302     }
8303
8304     /* Set special modes for chess engines.  Later something general
8305      *  could be added here; for now there is just one kludge feature,
8306      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8307      *  when "xboard" is given as an interactive command.
8308      */
8309     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8310         cps->useSigint = FALSE;
8311         cps->useSigterm = FALSE;
8312     }
8313     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8314       ParseFeatures(message+8, cps);
8315       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8316     }
8317
8318     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8319                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8320       int dummy, s=6; char buf[MSG_SIZ];
8321       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8322       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8323       if(startedFromSetupPosition) return;
8324       ParseFEN(boards[0], &dummy, message+s);
8325       DrawPosition(TRUE, boards[0]);
8326       startedFromSetupPosition = TRUE;
8327       return;
8328     }
8329     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8330      * want this, I was asked to put it in, and obliged.
8331      */
8332     if (!strncmp(message, "setboard ", 9)) {
8333         Board initial_position;
8334
8335         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8336
8337         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8338             DisplayError(_("Bad FEN received from engine"), 0);
8339             return ;
8340         } else {
8341            Reset(TRUE, FALSE);
8342            CopyBoard(boards[0], initial_position);
8343            initialRulePlies = FENrulePlies;
8344            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8345            else gameMode = MachinePlaysBlack;
8346            DrawPosition(FALSE, boards[currentMove]);
8347         }
8348         return;
8349     }
8350
8351     /*
8352      * Look for communication commands
8353      */
8354     if (!strncmp(message, "telluser ", 9)) {
8355         if(message[9] == '\\' && message[10] == '\\')
8356             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8357         PlayTellSound();
8358         DisplayNote(message + 9);
8359         return;
8360     }
8361     if (!strncmp(message, "tellusererror ", 14)) {
8362         cps->userError = 1;
8363         if(message[14] == '\\' && message[15] == '\\')
8364             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8365         PlayTellSound();
8366         DisplayError(message + 14, 0);
8367         return;
8368     }
8369     if (!strncmp(message, "tellopponent ", 13)) {
8370       if (appData.icsActive) {
8371         if (loggedOn) {
8372           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8373           SendToICS(buf1);
8374         }
8375       } else {
8376         DisplayNote(message + 13);
8377       }
8378       return;
8379     }
8380     if (!strncmp(message, "tellothers ", 11)) {
8381       if (appData.icsActive) {
8382         if (loggedOn) {
8383           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8384           SendToICS(buf1);
8385         }
8386       }
8387       return;
8388     }
8389     if (!strncmp(message, "tellall ", 8)) {
8390       if (appData.icsActive) {
8391         if (loggedOn) {
8392           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8393           SendToICS(buf1);
8394         }
8395       } else {
8396         DisplayNote(message + 8);
8397       }
8398       return;
8399     }
8400     if (strncmp(message, "warning", 7) == 0) {
8401         /* Undocumented feature, use tellusererror in new code */
8402         DisplayError(message, 0);
8403         return;
8404     }
8405     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8406         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8407         strcat(realname, " query");
8408         AskQuestion(realname, buf2, buf1, cps->pr);
8409         return;
8410     }
8411     /* Commands from the engine directly to ICS.  We don't allow these to be
8412      *  sent until we are logged on. Crafty kibitzes have been known to
8413      *  interfere with the login process.
8414      */
8415     if (loggedOn) {
8416         if (!strncmp(message, "tellics ", 8)) {
8417             SendToICS(message + 8);
8418             SendToICS("\n");
8419             return;
8420         }
8421         if (!strncmp(message, "tellicsnoalias ", 15)) {
8422             SendToICS(ics_prefix);
8423             SendToICS(message + 15);
8424             SendToICS("\n");
8425             return;
8426         }
8427         /* The following are for backward compatibility only */
8428         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8429             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8430             SendToICS(ics_prefix);
8431             SendToICS(message);
8432             SendToICS("\n");
8433             return;
8434         }
8435     }
8436     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8437         return;
8438     }
8439     /*
8440      * If the move is illegal, cancel it and redraw the board.
8441      * Also deal with other error cases.  Matching is rather loose
8442      * here to accommodate engines written before the spec.
8443      */
8444     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8445         strncmp(message, "Error", 5) == 0) {
8446         if (StrStr(message, "name") ||
8447             StrStr(message, "rating") || StrStr(message, "?") ||
8448             StrStr(message, "result") || StrStr(message, "board") ||
8449             StrStr(message, "bk") || StrStr(message, "computer") ||
8450             StrStr(message, "variant") || StrStr(message, "hint") ||
8451             StrStr(message, "random") || StrStr(message, "depth") ||
8452             StrStr(message, "accepted")) {
8453             return;
8454         }
8455         if (StrStr(message, "protover")) {
8456           /* Program is responding to input, so it's apparently done
8457              initializing, and this error message indicates it is
8458              protocol version 1.  So we don't need to wait any longer
8459              for it to initialize and send feature commands. */
8460           FeatureDone(cps, 1);
8461           cps->protocolVersion = 1;
8462           return;
8463         }
8464         cps->maybeThinking = FALSE;
8465
8466         if (StrStr(message, "draw")) {
8467             /* Program doesn't have "draw" command */
8468             cps->sendDrawOffers = 0;
8469             return;
8470         }
8471         if (cps->sendTime != 1 &&
8472             (StrStr(message, "time") || StrStr(message, "otim"))) {
8473           /* Program apparently doesn't have "time" or "otim" command */
8474           cps->sendTime = 0;
8475           return;
8476         }
8477         if (StrStr(message, "analyze")) {
8478             cps->analysisSupport = FALSE;
8479             cps->analyzing = FALSE;
8480 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8481             EditGameEvent(); // [HGM] try to preserve loaded game
8482             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8483             DisplayError(buf2, 0);
8484             return;
8485         }
8486         if (StrStr(message, "(no matching move)st")) {
8487           /* Special kludge for GNU Chess 4 only */
8488           cps->stKludge = TRUE;
8489           SendTimeControl(cps, movesPerSession, timeControl,
8490                           timeIncrement, appData.searchDepth,
8491                           searchTime);
8492           return;
8493         }
8494         if (StrStr(message, "(no matching move)sd")) {
8495           /* Special kludge for GNU Chess 4 only */
8496           cps->sdKludge = TRUE;
8497           SendTimeControl(cps, movesPerSession, timeControl,
8498                           timeIncrement, appData.searchDepth,
8499                           searchTime);
8500           return;
8501         }
8502         if (!StrStr(message, "llegal")) {
8503             return;
8504         }
8505         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8506             gameMode == IcsIdle) return;
8507         if (forwardMostMove <= backwardMostMove) return;
8508         if (pausing) PauseEvent();
8509       if(appData.forceIllegal) {
8510             // [HGM] illegal: machine refused move; force position after move into it
8511           SendToProgram("force\n", cps);
8512           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8513                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8514                 // when black is to move, while there might be nothing on a2 or black
8515                 // might already have the move. So send the board as if white has the move.
8516                 // But first we must change the stm of the engine, as it refused the last move
8517                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8518                 if(WhiteOnMove(forwardMostMove)) {
8519                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8520                     SendBoard(cps, forwardMostMove); // kludgeless board
8521                 } else {
8522                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8523                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8524                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8525                 }
8526           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8527             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8528                  gameMode == TwoMachinesPlay)
8529               SendToProgram("go\n", cps);
8530             return;
8531       } else
8532         if (gameMode == PlayFromGameFile) {
8533             /* Stop reading this game file */
8534             gameMode = EditGame;
8535             ModeHighlight();
8536         }
8537         /* [HGM] illegal-move claim should forfeit game when Xboard */
8538         /* only passes fully legal moves                            */
8539         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8540             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8541                                 "False illegal-move claim", GE_XBOARD );
8542             return; // do not take back move we tested as valid
8543         }
8544         currentMove = forwardMostMove-1;
8545         DisplayMove(currentMove-1); /* before DisplayMoveError */
8546         SwitchClocks(forwardMostMove-1); // [HGM] race
8547         DisplayBothClocks();
8548         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8549                 parseList[currentMove], _(cps->which));
8550         DisplayMoveError(buf1);
8551         DrawPosition(FALSE, boards[currentMove]);
8552
8553         SetUserThinkingEnables();
8554         return;
8555     }
8556     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8557         /* Program has a broken "time" command that
8558            outputs a string not ending in newline.
8559            Don't use it. */
8560         cps->sendTime = 0;
8561     }
8562
8563     /*
8564      * If chess program startup fails, exit with an error message.
8565      * Attempts to recover here are futile. [HGM] Well, we try anyway
8566      */
8567     if ((StrStr(message, "unknown host") != NULL)
8568         || (StrStr(message, "No remote directory") != NULL)
8569         || (StrStr(message, "not found") != NULL)
8570         || (StrStr(message, "No such file") != NULL)
8571         || (StrStr(message, "can't alloc") != NULL)
8572         || (StrStr(message, "Permission denied") != NULL)) {
8573
8574         cps->maybeThinking = FALSE;
8575         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8576                 _(cps->which), cps->program, cps->host, message);
8577         RemoveInputSource(cps->isr);
8578         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8579             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8580             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8581         }
8582         return;
8583     }
8584
8585     /*
8586      * Look for hint output
8587      */
8588     if (sscanf(message, "Hint: %s", buf1) == 1) {
8589         if (cps == &first && hintRequested) {
8590             hintRequested = FALSE;
8591             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8592                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8593                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8594                                     PosFlags(forwardMostMove),
8595                                     fromY, fromX, toY, toX, promoChar, buf1);
8596                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8597                 DisplayInformation(buf2);
8598             } else {
8599                 /* Hint move could not be parsed!? */
8600               snprintf(buf2, sizeof(buf2),
8601                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8602                         buf1, _(cps->which));
8603                 DisplayError(buf2, 0);
8604             }
8605         } else {
8606           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8607         }
8608         return;
8609     }
8610
8611     /*
8612      * Ignore other messages if game is not in progress
8613      */
8614     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8615         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8616
8617     /*
8618      * look for win, lose, draw, or draw offer
8619      */
8620     if (strncmp(message, "1-0", 3) == 0) {
8621         char *p, *q, *r = "";
8622         p = strchr(message, '{');
8623         if (p) {
8624             q = strchr(p, '}');
8625             if (q) {
8626                 *q = NULLCHAR;
8627                 r = p + 1;
8628             }
8629         }
8630         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8631         return;
8632     } else if (strncmp(message, "0-1", 3) == 0) {
8633         char *p, *q, *r = "";
8634         p = strchr(message, '{');
8635         if (p) {
8636             q = strchr(p, '}');
8637             if (q) {
8638                 *q = NULLCHAR;
8639                 r = p + 1;
8640             }
8641         }
8642         /* Kludge for Arasan 4.1 bug */
8643         if (strcmp(r, "Black resigns") == 0) {
8644             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8645             return;
8646         }
8647         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8648         return;
8649     } else if (strncmp(message, "1/2", 3) == 0) {
8650         char *p, *q, *r = "";
8651         p = strchr(message, '{');
8652         if (p) {
8653             q = strchr(p, '}');
8654             if (q) {
8655                 *q = NULLCHAR;
8656                 r = p + 1;
8657             }
8658         }
8659
8660         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8661         return;
8662
8663     } else if (strncmp(message, "White resign", 12) == 0) {
8664         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8665         return;
8666     } else if (strncmp(message, "Black resign", 12) == 0) {
8667         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8668         return;
8669     } else if (strncmp(message, "White matches", 13) == 0 ||
8670                strncmp(message, "Black matches", 13) == 0   ) {
8671         /* [HGM] ignore GNUShogi noises */
8672         return;
8673     } else if (strncmp(message, "White", 5) == 0 &&
8674                message[5] != '(' &&
8675                StrStr(message, "Black") == NULL) {
8676         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8677         return;
8678     } else if (strncmp(message, "Black", 5) == 0 &&
8679                message[5] != '(') {
8680         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8681         return;
8682     } else if (strcmp(message, "resign") == 0 ||
8683                strcmp(message, "computer resigns") == 0) {
8684         switch (gameMode) {
8685           case MachinePlaysBlack:
8686           case IcsPlayingBlack:
8687             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8688             break;
8689           case MachinePlaysWhite:
8690           case IcsPlayingWhite:
8691             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8692             break;
8693           case TwoMachinesPlay:
8694             if (cps->twoMachinesColor[0] == 'w')
8695               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8696             else
8697               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8698             break;
8699           default:
8700             /* can't happen */
8701             break;
8702         }
8703         return;
8704     } else if (strncmp(message, "opponent mates", 14) == 0) {
8705         switch (gameMode) {
8706           case MachinePlaysBlack:
8707           case IcsPlayingBlack:
8708             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8709             break;
8710           case MachinePlaysWhite:
8711           case IcsPlayingWhite:
8712             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8713             break;
8714           case TwoMachinesPlay:
8715             if (cps->twoMachinesColor[0] == 'w')
8716               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8717             else
8718               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8719             break;
8720           default:
8721             /* can't happen */
8722             break;
8723         }
8724         return;
8725     } else if (strncmp(message, "computer mates", 14) == 0) {
8726         switch (gameMode) {
8727           case MachinePlaysBlack:
8728           case IcsPlayingBlack:
8729             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8730             break;
8731           case MachinePlaysWhite:
8732           case IcsPlayingWhite:
8733             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8734             break;
8735           case TwoMachinesPlay:
8736             if (cps->twoMachinesColor[0] == 'w')
8737               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8738             else
8739               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8740             break;
8741           default:
8742             /* can't happen */
8743             break;
8744         }
8745         return;
8746     } else if (strncmp(message, "checkmate", 9) == 0) {
8747         if (WhiteOnMove(forwardMostMove)) {
8748             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8749         } else {
8750             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8751         }
8752         return;
8753     } else if (strstr(message, "Draw") != NULL ||
8754                strstr(message, "game is a draw") != NULL) {
8755         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8756         return;
8757     } else if (strstr(message, "offer") != NULL &&
8758                strstr(message, "draw") != NULL) {
8759 #if ZIPPY
8760         if (appData.zippyPlay && first.initDone) {
8761             /* Relay offer to ICS */
8762             SendToICS(ics_prefix);
8763             SendToICS("draw\n");
8764         }
8765 #endif
8766         cps->offeredDraw = 2; /* valid until this engine moves twice */
8767         if (gameMode == TwoMachinesPlay) {
8768             if (cps->other->offeredDraw) {
8769                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8770             /* [HGM] in two-machine mode we delay relaying draw offer      */
8771             /* until after we also have move, to see if it is really claim */
8772             }
8773         } else if (gameMode == MachinePlaysWhite ||
8774                    gameMode == MachinePlaysBlack) {
8775           if (userOfferedDraw) {
8776             DisplayInformation(_("Machine accepts your draw offer"));
8777             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8778           } else {
8779             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8780           }
8781         }
8782     }
8783
8784
8785     /*
8786      * Look for thinking output
8787      */
8788     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8789           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8790                                 ) {
8791         int plylev, mvleft, mvtot, curscore, time;
8792         char mvname[MOVE_LEN];
8793         u64 nodes; // [DM]
8794         char plyext;
8795         int ignore = FALSE;
8796         int prefixHint = FALSE;
8797         mvname[0] = NULLCHAR;
8798
8799         switch (gameMode) {
8800           case MachinePlaysBlack:
8801           case IcsPlayingBlack:
8802             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8803             break;
8804           case MachinePlaysWhite:
8805           case IcsPlayingWhite:
8806             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8807             break;
8808           case AnalyzeMode:
8809           case AnalyzeFile:
8810             break;
8811           case IcsObserving: /* [DM] icsEngineAnalyze */
8812             if (!appData.icsEngineAnalyze) ignore = TRUE;
8813             break;
8814           case TwoMachinesPlay:
8815             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8816                 ignore = TRUE;
8817             }
8818             break;
8819           default:
8820             ignore = TRUE;
8821             break;
8822         }
8823
8824         if (!ignore) {
8825             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8826             buf1[0] = NULLCHAR;
8827             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8828                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8829
8830                 if (plyext != ' ' && plyext != '\t') {
8831                     time *= 100;
8832                 }
8833
8834                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8835                 if( cps->scoreIsAbsolute &&
8836                     ( gameMode == MachinePlaysBlack ||
8837                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8838                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8839                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8840                      !WhiteOnMove(currentMove)
8841                     ) )
8842                 {
8843                     curscore = -curscore;
8844                 }
8845
8846                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8847
8848                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8849                         char buf[MSG_SIZ];
8850                         FILE *f;
8851                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8852                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8853                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8854                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8855                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8856                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8857                                 fclose(f);
8858                         } else DisplayError(_("failed writing PV"), 0);
8859                 }
8860
8861                 tempStats.depth = plylev;
8862                 tempStats.nodes = nodes;
8863                 tempStats.time = time;
8864                 tempStats.score = curscore;
8865                 tempStats.got_only_move = 0;
8866
8867                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8868                         int ticklen;
8869
8870                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8871                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8872                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8873                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8874                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8875                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8876                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8877                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8878                 }
8879
8880                 /* Buffer overflow protection */
8881                 if (pv[0] != NULLCHAR) {
8882                     if (strlen(pv) >= sizeof(tempStats.movelist)
8883                         && appData.debugMode) {
8884                         fprintf(debugFP,
8885                                 "PV is too long; using the first %u bytes.\n",
8886                                 (unsigned) sizeof(tempStats.movelist) - 1);
8887                     }
8888
8889                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8890                 } else {
8891                     sprintf(tempStats.movelist, " no PV\n");
8892                 }
8893
8894                 if (tempStats.seen_stat) {
8895                     tempStats.ok_to_send = 1;
8896                 }
8897
8898                 if (strchr(tempStats.movelist, '(') != NULL) {
8899                     tempStats.line_is_book = 1;
8900                     tempStats.nr_moves = 0;
8901                     tempStats.moves_left = 0;
8902                 } else {
8903                     tempStats.line_is_book = 0;
8904                 }
8905
8906                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8907                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8908
8909                 SendProgramStatsToFrontend( cps, &tempStats );
8910
8911                 /*
8912                     [AS] Protect the thinkOutput buffer from overflow... this
8913                     is only useful if buf1 hasn't overflowed first!
8914                 */
8915                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8916                          plylev,
8917                          (gameMode == TwoMachinesPlay ?
8918                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8919                          ((double) curscore) / 100.0,
8920                          prefixHint ? lastHint : "",
8921                          prefixHint ? " " : "" );
8922
8923                 if( buf1[0] != NULLCHAR ) {
8924                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8925
8926                     if( strlen(pv) > max_len ) {
8927                         if( appData.debugMode) {
8928                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8929                         }
8930                         pv[max_len+1] = '\0';
8931                     }
8932
8933                     strcat( thinkOutput, pv);
8934                 }
8935
8936                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8937                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8938                     DisplayMove(currentMove - 1);
8939                 }
8940                 return;
8941
8942             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8943                 /* crafty (9.25+) says "(only move) <move>"
8944                  * if there is only 1 legal move
8945                  */
8946                 sscanf(p, "(only move) %s", buf1);
8947                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8948                 sprintf(programStats.movelist, "%s (only move)", buf1);
8949                 programStats.depth = 1;
8950                 programStats.nr_moves = 1;
8951                 programStats.moves_left = 1;
8952                 programStats.nodes = 1;
8953                 programStats.time = 1;
8954                 programStats.got_only_move = 1;
8955
8956                 /* Not really, but we also use this member to
8957                    mean "line isn't going to change" (Crafty
8958                    isn't searching, so stats won't change) */
8959                 programStats.line_is_book = 1;
8960
8961                 SendProgramStatsToFrontend( cps, &programStats );
8962
8963                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8964                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8965                     DisplayMove(currentMove - 1);
8966                 }
8967                 return;
8968             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8969                               &time, &nodes, &plylev, &mvleft,
8970                               &mvtot, mvname) >= 5) {
8971                 /* The stat01: line is from Crafty (9.29+) in response
8972                    to the "." command */
8973                 programStats.seen_stat = 1;
8974                 cps->maybeThinking = TRUE;
8975
8976                 if (programStats.got_only_move || !appData.periodicUpdates)
8977                   return;
8978
8979                 programStats.depth = plylev;
8980                 programStats.time = time;
8981                 programStats.nodes = nodes;
8982                 programStats.moves_left = mvleft;
8983                 programStats.nr_moves = mvtot;
8984                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8985                 programStats.ok_to_send = 1;
8986                 programStats.movelist[0] = '\0';
8987
8988                 SendProgramStatsToFrontend( cps, &programStats );
8989
8990                 return;
8991
8992             } else if (strncmp(message,"++",2) == 0) {
8993                 /* Crafty 9.29+ outputs this */
8994                 programStats.got_fail = 2;
8995                 return;
8996
8997             } else if (strncmp(message,"--",2) == 0) {
8998                 /* Crafty 9.29+ outputs this */
8999                 programStats.got_fail = 1;
9000                 return;
9001
9002             } else if (thinkOutput[0] != NULLCHAR &&
9003                        strncmp(message, "    ", 4) == 0) {
9004                 unsigned message_len;
9005
9006                 p = message;
9007                 while (*p && *p == ' ') p++;
9008
9009                 message_len = strlen( p );
9010
9011                 /* [AS] Avoid buffer overflow */
9012                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9013                     strcat(thinkOutput, " ");
9014                     strcat(thinkOutput, p);
9015                 }
9016
9017                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9018                     strcat(programStats.movelist, " ");
9019                     strcat(programStats.movelist, p);
9020                 }
9021
9022                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9023                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9024                     DisplayMove(currentMove - 1);
9025                 }
9026                 return;
9027             }
9028         }
9029         else {
9030             buf1[0] = NULLCHAR;
9031
9032             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9033                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9034             {
9035                 ChessProgramStats cpstats;
9036
9037                 if (plyext != ' ' && plyext != '\t') {
9038                     time *= 100;
9039                 }
9040
9041                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9042                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9043                     curscore = -curscore;
9044                 }
9045
9046                 cpstats.depth = plylev;
9047                 cpstats.nodes = nodes;
9048                 cpstats.time = time;
9049                 cpstats.score = curscore;
9050                 cpstats.got_only_move = 0;
9051                 cpstats.movelist[0] = '\0';
9052
9053                 if (buf1[0] != NULLCHAR) {
9054                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9055                 }
9056
9057                 cpstats.ok_to_send = 0;
9058                 cpstats.line_is_book = 0;
9059                 cpstats.nr_moves = 0;
9060                 cpstats.moves_left = 0;
9061
9062                 SendProgramStatsToFrontend( cps, &cpstats );
9063             }
9064         }
9065     }
9066 }
9067
9068
9069 /* Parse a game score from the character string "game", and
9070    record it as the history of the current game.  The game
9071    score is NOT assumed to start from the standard position.
9072    The display is not updated in any way.
9073    */
9074 void
9075 ParseGameHistory (char *game)
9076 {
9077     ChessMove moveType;
9078     int fromX, fromY, toX, toY, boardIndex;
9079     char promoChar;
9080     char *p, *q;
9081     char buf[MSG_SIZ];
9082
9083     if (appData.debugMode)
9084       fprintf(debugFP, "Parsing game history: %s\n", game);
9085
9086     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9087     gameInfo.site = StrSave(appData.icsHost);
9088     gameInfo.date = PGNDate();
9089     gameInfo.round = StrSave("-");
9090
9091     /* Parse out names of players */
9092     while (*game == ' ') game++;
9093     p = buf;
9094     while (*game != ' ') *p++ = *game++;
9095     *p = NULLCHAR;
9096     gameInfo.white = StrSave(buf);
9097     while (*game == ' ') game++;
9098     p = buf;
9099     while (*game != ' ' && *game != '\n') *p++ = *game++;
9100     *p = NULLCHAR;
9101     gameInfo.black = StrSave(buf);
9102
9103     /* Parse moves */
9104     boardIndex = blackPlaysFirst ? 1 : 0;
9105     yynewstr(game);
9106     for (;;) {
9107         yyboardindex = boardIndex;
9108         moveType = (ChessMove) Myylex();
9109         switch (moveType) {
9110           case IllegalMove:             /* maybe suicide chess, etc. */
9111   if (appData.debugMode) {
9112     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9113     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9114     setbuf(debugFP, NULL);
9115   }
9116           case WhitePromotion:
9117           case BlackPromotion:
9118           case WhiteNonPromotion:
9119           case BlackNonPromotion:
9120           case NormalMove:
9121           case WhiteCapturesEnPassant:
9122           case BlackCapturesEnPassant:
9123           case WhiteKingSideCastle:
9124           case WhiteQueenSideCastle:
9125           case BlackKingSideCastle:
9126           case BlackQueenSideCastle:
9127           case WhiteKingSideCastleWild:
9128           case WhiteQueenSideCastleWild:
9129           case BlackKingSideCastleWild:
9130           case BlackQueenSideCastleWild:
9131           /* PUSH Fabien */
9132           case WhiteHSideCastleFR:
9133           case WhiteASideCastleFR:
9134           case BlackHSideCastleFR:
9135           case BlackASideCastleFR:
9136           /* POP Fabien */
9137             fromX = currentMoveString[0] - AAA;
9138             fromY = currentMoveString[1] - ONE;
9139             toX = currentMoveString[2] - AAA;
9140             toY = currentMoveString[3] - ONE;
9141             promoChar = currentMoveString[4];
9142             break;
9143           case WhiteDrop:
9144           case BlackDrop:
9145             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9146             fromX = moveType == WhiteDrop ?
9147               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9148             (int) CharToPiece(ToLower(currentMoveString[0]));
9149             fromY = DROP_RANK;
9150             toX = currentMoveString[2] - AAA;
9151             toY = currentMoveString[3] - ONE;
9152             promoChar = NULLCHAR;
9153             break;
9154           case AmbiguousMove:
9155             /* bug? */
9156             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9157   if (appData.debugMode) {
9158     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9159     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9160     setbuf(debugFP, NULL);
9161   }
9162             DisplayError(buf, 0);
9163             return;
9164           case ImpossibleMove:
9165             /* bug? */
9166             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9167   if (appData.debugMode) {
9168     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9169     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9170     setbuf(debugFP, NULL);
9171   }
9172             DisplayError(buf, 0);
9173             return;
9174           case EndOfFile:
9175             if (boardIndex < backwardMostMove) {
9176                 /* Oops, gap.  How did that happen? */
9177                 DisplayError(_("Gap in move list"), 0);
9178                 return;
9179             }
9180             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9181             if (boardIndex > forwardMostMove) {
9182                 forwardMostMove = boardIndex;
9183             }
9184             return;
9185           case ElapsedTime:
9186             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9187                 strcat(parseList[boardIndex-1], " ");
9188                 strcat(parseList[boardIndex-1], yy_text);
9189             }
9190             continue;
9191           case Comment:
9192           case PGNTag:
9193           case NAG:
9194           default:
9195             /* ignore */
9196             continue;
9197           case WhiteWins:
9198           case BlackWins:
9199           case GameIsDrawn:
9200           case GameUnfinished:
9201             if (gameMode == IcsExamining) {
9202                 if (boardIndex < backwardMostMove) {
9203                     /* Oops, gap.  How did that happen? */
9204                     return;
9205                 }
9206                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9207                 return;
9208             }
9209             gameInfo.result = moveType;
9210             p = strchr(yy_text, '{');
9211             if (p == NULL) p = strchr(yy_text, '(');
9212             if (p == NULL) {
9213                 p = yy_text;
9214                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9215             } else {
9216                 q = strchr(p, *p == '{' ? '}' : ')');
9217                 if (q != NULL) *q = NULLCHAR;
9218                 p++;
9219             }
9220             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9221             gameInfo.resultDetails = StrSave(p);
9222             continue;
9223         }
9224         if (boardIndex >= forwardMostMove &&
9225             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9226             backwardMostMove = blackPlaysFirst ? 1 : 0;
9227             return;
9228         }
9229         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9230                                  fromY, fromX, toY, toX, promoChar,
9231                                  parseList[boardIndex]);
9232         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9233         /* currentMoveString is set as a side-effect of yylex */
9234         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9235         strcat(moveList[boardIndex], "\n");
9236         boardIndex++;
9237         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9238         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9239           case MT_NONE:
9240           case MT_STALEMATE:
9241           default:
9242             break;
9243           case MT_CHECK:
9244             if(gameInfo.variant != VariantShogi)
9245                 strcat(parseList[boardIndex - 1], "+");
9246             break;
9247           case MT_CHECKMATE:
9248           case MT_STAINMATE:
9249             strcat(parseList[boardIndex - 1], "#");
9250             break;
9251         }
9252     }
9253 }
9254
9255
9256 /* Apply a move to the given board  */
9257 void
9258 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9259 {
9260   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9261   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9262
9263     /* [HGM] compute & store e.p. status and castling rights for new position */
9264     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9265
9266       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9267       oldEP = (signed char)board[EP_STATUS];
9268       board[EP_STATUS] = EP_NONE;
9269
9270   if (fromY == DROP_RANK) {
9271         /* must be first */
9272         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9273             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9274             return;
9275         }
9276         piece = board[toY][toX] = (ChessSquare) fromX;
9277   } else {
9278       int i;
9279
9280       if( board[toY][toX] != EmptySquare )
9281            board[EP_STATUS] = EP_CAPTURE;
9282
9283       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9284            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9285                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9286       } else
9287       if( board[fromY][fromX] == WhitePawn ) {
9288            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9289                board[EP_STATUS] = EP_PAWN_MOVE;
9290            if( toY-fromY==2) {
9291                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9292                         gameInfo.variant != VariantBerolina || toX < fromX)
9293                       board[EP_STATUS] = toX | berolina;
9294                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9295                         gameInfo.variant != VariantBerolina || toX > fromX)
9296                       board[EP_STATUS] = toX;
9297            }
9298       } else
9299       if( board[fromY][fromX] == BlackPawn ) {
9300            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9301                board[EP_STATUS] = EP_PAWN_MOVE;
9302            if( toY-fromY== -2) {
9303                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9304                         gameInfo.variant != VariantBerolina || toX < fromX)
9305                       board[EP_STATUS] = toX | berolina;
9306                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9307                         gameInfo.variant != VariantBerolina || toX > fromX)
9308                       board[EP_STATUS] = toX;
9309            }
9310        }
9311
9312        for(i=0; i<nrCastlingRights; i++) {
9313            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9314               board[CASTLING][i] == toX   && castlingRank[i] == toY
9315              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9316        }
9317
9318      if (fromX == toX && fromY == toY) return;
9319
9320      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9321      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9322      if(gameInfo.variant == VariantKnightmate)
9323          king += (int) WhiteUnicorn - (int) WhiteKing;
9324
9325     /* Code added by Tord: */
9326     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9327     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9328         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9329       board[fromY][fromX] = EmptySquare;
9330       board[toY][toX] = EmptySquare;
9331       if((toX > fromX) != (piece == WhiteRook)) {
9332         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9333       } else {
9334         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9335       }
9336     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9337                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9338       board[fromY][fromX] = EmptySquare;
9339       board[toY][toX] = EmptySquare;
9340       if((toX > fromX) != (piece == BlackRook)) {
9341         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9342       } else {
9343         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9344       }
9345     /* End of code added by Tord */
9346
9347     } else if (board[fromY][fromX] == king
9348         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9349         && toY == fromY && toX > fromX+1) {
9350         board[fromY][fromX] = EmptySquare;
9351         board[toY][toX] = king;
9352         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9353         board[fromY][BOARD_RGHT-1] = EmptySquare;
9354     } else if (board[fromY][fromX] == king
9355         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9356                && toY == fromY && toX < fromX-1) {
9357         board[fromY][fromX] = EmptySquare;
9358         board[toY][toX] = king;
9359         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9360         board[fromY][BOARD_LEFT] = EmptySquare;
9361     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9362                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9363                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9364                ) {
9365         /* white pawn promotion */
9366         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9367         if(gameInfo.variant==VariantBughouse ||
9368            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9369             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9370         board[fromY][fromX] = EmptySquare;
9371     } else if ((fromY >= BOARD_HEIGHT>>1)
9372                && (toX != fromX)
9373                && gameInfo.variant != VariantXiangqi
9374                && gameInfo.variant != VariantBerolina
9375                && (board[fromY][fromX] == WhitePawn)
9376                && (board[toY][toX] == EmptySquare)) {
9377         board[fromY][fromX] = EmptySquare;
9378         board[toY][toX] = WhitePawn;
9379         captured = board[toY - 1][toX];
9380         board[toY - 1][toX] = EmptySquare;
9381     } else if ((fromY == BOARD_HEIGHT-4)
9382                && (toX == fromX)
9383                && gameInfo.variant == VariantBerolina
9384                && (board[fromY][fromX] == WhitePawn)
9385                && (board[toY][toX] == EmptySquare)) {
9386         board[fromY][fromX] = EmptySquare;
9387         board[toY][toX] = WhitePawn;
9388         if(oldEP & EP_BEROLIN_A) {
9389                 captured = board[fromY][fromX-1];
9390                 board[fromY][fromX-1] = EmptySquare;
9391         }else{  captured = board[fromY][fromX+1];
9392                 board[fromY][fromX+1] = EmptySquare;
9393         }
9394     } else if (board[fromY][fromX] == king
9395         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9396                && toY == fromY && toX > fromX+1) {
9397         board[fromY][fromX] = EmptySquare;
9398         board[toY][toX] = king;
9399         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9400         board[fromY][BOARD_RGHT-1] = EmptySquare;
9401     } else if (board[fromY][fromX] == king
9402         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9403                && toY == fromY && toX < fromX-1) {
9404         board[fromY][fromX] = EmptySquare;
9405         board[toY][toX] = king;
9406         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9407         board[fromY][BOARD_LEFT] = EmptySquare;
9408     } else if (fromY == 7 && fromX == 3
9409                && board[fromY][fromX] == BlackKing
9410                && toY == 7 && toX == 5) {
9411         board[fromY][fromX] = EmptySquare;
9412         board[toY][toX] = BlackKing;
9413         board[fromY][7] = EmptySquare;
9414         board[toY][4] = BlackRook;
9415     } else if (fromY == 7 && fromX == 3
9416                && board[fromY][fromX] == BlackKing
9417                && toY == 7 && toX == 1) {
9418         board[fromY][fromX] = EmptySquare;
9419         board[toY][toX] = BlackKing;
9420         board[fromY][0] = EmptySquare;
9421         board[toY][2] = BlackRook;
9422     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9423                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9424                && toY < promoRank && promoChar
9425                ) {
9426         /* black pawn promotion */
9427         board[toY][toX] = CharToPiece(ToLower(promoChar));
9428         if(gameInfo.variant==VariantBughouse ||
9429            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9430             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9431         board[fromY][fromX] = EmptySquare;
9432     } else if ((fromY < BOARD_HEIGHT>>1)
9433                && (toX != fromX)
9434                && gameInfo.variant != VariantXiangqi
9435                && gameInfo.variant != VariantBerolina
9436                && (board[fromY][fromX] == BlackPawn)
9437                && (board[toY][toX] == EmptySquare)) {
9438         board[fromY][fromX] = EmptySquare;
9439         board[toY][toX] = BlackPawn;
9440         captured = board[toY + 1][toX];
9441         board[toY + 1][toX] = EmptySquare;
9442     } else if ((fromY == 3)
9443                && (toX == fromX)
9444                && gameInfo.variant == VariantBerolina
9445                && (board[fromY][fromX] == BlackPawn)
9446                && (board[toY][toX] == EmptySquare)) {
9447         board[fromY][fromX] = EmptySquare;
9448         board[toY][toX] = BlackPawn;
9449         if(oldEP & EP_BEROLIN_A) {
9450                 captured = board[fromY][fromX-1];
9451                 board[fromY][fromX-1] = EmptySquare;
9452         }else{  captured = board[fromY][fromX+1];
9453                 board[fromY][fromX+1] = EmptySquare;
9454         }
9455     } else {
9456         board[toY][toX] = board[fromY][fromX];
9457         board[fromY][fromX] = EmptySquare;
9458     }
9459   }
9460
9461     if (gameInfo.holdingsWidth != 0) {
9462
9463       /* !!A lot more code needs to be written to support holdings  */
9464       /* [HGM] OK, so I have written it. Holdings are stored in the */
9465       /* penultimate board files, so they are automaticlly stored   */
9466       /* in the game history.                                       */
9467       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9468                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9469         /* Delete from holdings, by decreasing count */
9470         /* and erasing image if necessary            */
9471         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9472         if(p < (int) BlackPawn) { /* white drop */
9473              p -= (int)WhitePawn;
9474                  p = PieceToNumber((ChessSquare)p);
9475              if(p >= gameInfo.holdingsSize) p = 0;
9476              if(--board[p][BOARD_WIDTH-2] <= 0)
9477                   board[p][BOARD_WIDTH-1] = EmptySquare;
9478              if((int)board[p][BOARD_WIDTH-2] < 0)
9479                         board[p][BOARD_WIDTH-2] = 0;
9480         } else {                  /* black drop */
9481              p -= (int)BlackPawn;
9482                  p = PieceToNumber((ChessSquare)p);
9483              if(p >= gameInfo.holdingsSize) p = 0;
9484              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9485                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9486              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9487                         board[BOARD_HEIGHT-1-p][1] = 0;
9488         }
9489       }
9490       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9491           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9492         /* [HGM] holdings: Add to holdings, if holdings exist */
9493         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9494                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9495                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9496         }
9497         p = (int) captured;
9498         if (p >= (int) BlackPawn) {
9499           p -= (int)BlackPawn;
9500           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9501                   /* in Shogi restore piece to its original  first */
9502                   captured = (ChessSquare) (DEMOTED captured);
9503                   p = DEMOTED p;
9504           }
9505           p = PieceToNumber((ChessSquare)p);
9506           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9507           board[p][BOARD_WIDTH-2]++;
9508           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9509         } else {
9510           p -= (int)WhitePawn;
9511           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9512                   captured = (ChessSquare) (DEMOTED captured);
9513                   p = DEMOTED p;
9514           }
9515           p = PieceToNumber((ChessSquare)p);
9516           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9517           board[BOARD_HEIGHT-1-p][1]++;
9518           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9519         }
9520       }
9521     } else if (gameInfo.variant == VariantAtomic) {
9522       if (captured != EmptySquare) {
9523         int y, x;
9524         for (y = toY-1; y <= toY+1; y++) {
9525           for (x = toX-1; x <= toX+1; x++) {
9526             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9527                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9528               board[y][x] = EmptySquare;
9529             }
9530           }
9531         }
9532         board[toY][toX] = EmptySquare;
9533       }
9534     }
9535     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9536         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9537     } else
9538     if(promoChar == '+') {
9539         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9540         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9541     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9542         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9543         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9544            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9545         board[toY][toX] = newPiece;
9546     }
9547     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9548                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9549         // [HGM] superchess: take promotion piece out of holdings
9550         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9551         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9552             if(!--board[k][BOARD_WIDTH-2])
9553                 board[k][BOARD_WIDTH-1] = EmptySquare;
9554         } else {
9555             if(!--board[BOARD_HEIGHT-1-k][1])
9556                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9557         }
9558     }
9559
9560 }
9561
9562 /* Updates forwardMostMove */
9563 void
9564 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9565 {
9566 //    forwardMostMove++; // [HGM] bare: moved downstream
9567
9568     (void) CoordsToAlgebraic(boards[forwardMostMove],
9569                              PosFlags(forwardMostMove),
9570                              fromY, fromX, toY, toX, promoChar,
9571                              parseList[forwardMostMove]);
9572
9573     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9574         int timeLeft; static int lastLoadFlag=0; int king, piece;
9575         piece = boards[forwardMostMove][fromY][fromX];
9576         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9577         if(gameInfo.variant == VariantKnightmate)
9578             king += (int) WhiteUnicorn - (int) WhiteKing;
9579         if(forwardMostMove == 0) {
9580             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9581                 fprintf(serverMoves, "%s;", UserName());
9582             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9583                 fprintf(serverMoves, "%s;", second.tidy);
9584             fprintf(serverMoves, "%s;", first.tidy);
9585             if(gameMode == MachinePlaysWhite)
9586                 fprintf(serverMoves, "%s;", UserName());
9587             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9588                 fprintf(serverMoves, "%s;", second.tidy);
9589         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9590         lastLoadFlag = loadFlag;
9591         // print base move
9592         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9593         // print castling suffix
9594         if( toY == fromY && piece == king ) {
9595             if(toX-fromX > 1)
9596                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9597             if(fromX-toX >1)
9598                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9599         }
9600         // e.p. suffix
9601         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9602              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9603              boards[forwardMostMove][toY][toX] == EmptySquare
9604              && fromX != toX && fromY != toY)
9605                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9606         // promotion suffix
9607         if(promoChar != NULLCHAR)
9608                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9609         if(!loadFlag) {
9610                 char buf[MOVE_LEN*2], *p; int len;
9611             fprintf(serverMoves, "/%d/%d",
9612                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9613             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9614             else                      timeLeft = blackTimeRemaining/1000;
9615             fprintf(serverMoves, "/%d", timeLeft);
9616                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9617                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9618                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9619             fprintf(serverMoves, "/%s", buf);
9620         }
9621         fflush(serverMoves);
9622     }
9623
9624     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9625         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9626       return;
9627     }
9628     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9629     if (commentList[forwardMostMove+1] != NULL) {
9630         free(commentList[forwardMostMove+1]);
9631         commentList[forwardMostMove+1] = NULL;
9632     }
9633     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9634     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9635     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9636     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9637     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9638     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9639     adjustedClock = FALSE;
9640     gameInfo.result = GameUnfinished;
9641     if (gameInfo.resultDetails != NULL) {
9642         free(gameInfo.resultDetails);
9643         gameInfo.resultDetails = NULL;
9644     }
9645     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9646                               moveList[forwardMostMove - 1]);
9647     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9648       case MT_NONE:
9649       case MT_STALEMATE:
9650       default:
9651         break;
9652       case MT_CHECK:
9653         if(gameInfo.variant != VariantShogi)
9654             strcat(parseList[forwardMostMove - 1], "+");
9655         break;
9656       case MT_CHECKMATE:
9657       case MT_STAINMATE:
9658         strcat(parseList[forwardMostMove - 1], "#");
9659         break;
9660     }
9661
9662 }
9663
9664 /* Updates currentMove if not pausing */
9665 void
9666 ShowMove (int fromX, int fromY, int toX, int toY)
9667 {
9668     int instant = (gameMode == PlayFromGameFile) ?
9669         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9670     if(appData.noGUI) return;
9671     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9672         if (!instant) {
9673             if (forwardMostMove == currentMove + 1) {
9674                 AnimateMove(boards[forwardMostMove - 1],
9675                             fromX, fromY, toX, toY);
9676             }
9677             if (appData.highlightLastMove) {
9678                 SetHighlights(fromX, fromY, toX, toY);
9679             }
9680         }
9681         currentMove = forwardMostMove;
9682     }
9683
9684     if (instant) return;
9685
9686     DisplayMove(currentMove - 1);
9687     DrawPosition(FALSE, boards[currentMove]);
9688     DisplayBothClocks();
9689     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9690 }
9691
9692 void
9693 SendEgtPath (ChessProgramState *cps)
9694 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9695         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9696
9697         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9698
9699         while(*p) {
9700             char c, *q = name+1, *r, *s;
9701
9702             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9703             while(*p && *p != ',') *q++ = *p++;
9704             *q++ = ':'; *q = 0;
9705             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9706                 strcmp(name, ",nalimov:") == 0 ) {
9707                 // take nalimov path from the menu-changeable option first, if it is defined
9708               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9709                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9710             } else
9711             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9712                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9713                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9714                 s = r = StrStr(s, ":") + 1; // beginning of path info
9715                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9716                 c = *r; *r = 0;             // temporarily null-terminate path info
9717                     *--q = 0;               // strip of trailig ':' from name
9718                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9719                 *r = c;
9720                 SendToProgram(buf,cps);     // send egtbpath command for this format
9721             }
9722             if(*p == ',') p++; // read away comma to position for next format name
9723         }
9724 }
9725
9726 void
9727 InitChessProgram (ChessProgramState *cps, int setup)
9728 /* setup needed to setup FRC opening position */
9729 {
9730     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9731     if (appData.noChessProgram) return;
9732     hintRequested = FALSE;
9733     bookRequested = FALSE;
9734
9735     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9736     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9737     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9738     if(cps->memSize) { /* [HGM] memory */
9739       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9740         SendToProgram(buf, cps);
9741     }
9742     SendEgtPath(cps); /* [HGM] EGT */
9743     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9744       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9745         SendToProgram(buf, cps);
9746     }
9747
9748     SendToProgram(cps->initString, cps);
9749     if (gameInfo.variant != VariantNormal &&
9750         gameInfo.variant != VariantLoadable
9751         /* [HGM] also send variant if board size non-standard */
9752         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9753                                             ) {
9754       char *v = VariantName(gameInfo.variant);
9755       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9756         /* [HGM] in protocol 1 we have to assume all variants valid */
9757         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9758         DisplayFatalError(buf, 0, 1);
9759         return;
9760       }
9761
9762       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9763       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9764       if( gameInfo.variant == VariantXiangqi )
9765            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9766       if( gameInfo.variant == VariantShogi )
9767            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9768       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9769            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9770       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9771           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9772            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9773       if( gameInfo.variant == VariantCourier )
9774            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9775       if( gameInfo.variant == VariantSuper )
9776            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9777       if( gameInfo.variant == VariantGreat )
9778            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9779       if( gameInfo.variant == VariantSChess )
9780            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9781       if( gameInfo.variant == VariantGrand )
9782            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9783
9784       if(overruled) {
9785         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9786                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9787            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9788            if(StrStr(cps->variants, b) == NULL) {
9789                // specific sized variant not known, check if general sizing allowed
9790                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9791                    if(StrStr(cps->variants, "boardsize") == NULL) {
9792                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9793                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9794                        DisplayFatalError(buf, 0, 1);
9795                        return;
9796                    }
9797                    /* [HGM] here we really should compare with the maximum supported board size */
9798                }
9799            }
9800       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9801       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9802       SendToProgram(buf, cps);
9803     }
9804     currentlyInitializedVariant = gameInfo.variant;
9805
9806     /* [HGM] send opening position in FRC to first engine */
9807     if(setup) {
9808           SendToProgram("force\n", cps);
9809           SendBoard(cps, 0);
9810           /* engine is now in force mode! Set flag to wake it up after first move. */
9811           setboardSpoiledMachineBlack = 1;
9812     }
9813
9814     if (cps->sendICS) {
9815       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9816       SendToProgram(buf, cps);
9817     }
9818     cps->maybeThinking = FALSE;
9819     cps->offeredDraw = 0;
9820     if (!appData.icsActive) {
9821         SendTimeControl(cps, movesPerSession, timeControl,
9822                         timeIncrement, appData.searchDepth,
9823                         searchTime);
9824     }
9825     if (appData.showThinking
9826         // [HGM] thinking: four options require thinking output to be sent
9827         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9828                                 ) {
9829         SendToProgram("post\n", cps);
9830     }
9831     SendToProgram("hard\n", cps);
9832     if (!appData.ponderNextMove) {
9833         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9834            it without being sure what state we are in first.  "hard"
9835            is not a toggle, so that one is OK.
9836          */
9837         SendToProgram("easy\n", cps);
9838     }
9839     if (cps->usePing) {
9840       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9841       SendToProgram(buf, cps);
9842     }
9843     cps->initDone = TRUE;
9844     ClearEngineOutputPane(cps == &second);
9845 }
9846
9847
9848 void
9849 StartChessProgram (ChessProgramState *cps)
9850 {
9851     char buf[MSG_SIZ];
9852     int err;
9853
9854     if (appData.noChessProgram) return;
9855     cps->initDone = FALSE;
9856
9857     if (strcmp(cps->host, "localhost") == 0) {
9858         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9859     } else if (*appData.remoteShell == NULLCHAR) {
9860         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9861     } else {
9862         if (*appData.remoteUser == NULLCHAR) {
9863           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9864                     cps->program);
9865         } else {
9866           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9867                     cps->host, appData.remoteUser, cps->program);
9868         }
9869         err = StartChildProcess(buf, "", &cps->pr);
9870     }
9871
9872     if (err != 0) {
9873       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9874         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9875         if(cps != &first) return;
9876         appData.noChessProgram = TRUE;
9877         ThawUI();
9878         SetNCPMode();
9879 //      DisplayFatalError(buf, err, 1);
9880 //      cps->pr = NoProc;
9881 //      cps->isr = NULL;
9882         return;
9883     }
9884
9885     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9886     if (cps->protocolVersion > 1) {
9887       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9888       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9889       cps->comboCnt = 0;  //                and values of combo boxes
9890       SendToProgram(buf, cps);
9891     } else {
9892       SendToProgram("xboard\n", cps);
9893     }
9894 }
9895
9896 void
9897 TwoMachinesEventIfReady P((void))
9898 {
9899   static int curMess = 0;
9900   if (first.lastPing != first.lastPong) {
9901     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9902     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9903     return;
9904   }
9905   if (second.lastPing != second.lastPong) {
9906     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9907     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9908     return;
9909   }
9910   DisplayMessage("", ""); curMess = 0;
9911   ThawUI();
9912   TwoMachinesEvent();
9913 }
9914
9915 char *
9916 MakeName (char *template)
9917 {
9918     time_t clock;
9919     struct tm *tm;
9920     static char buf[MSG_SIZ];
9921     char *p = buf;
9922     int i;
9923
9924     clock = time((time_t *)NULL);
9925     tm = localtime(&clock);
9926
9927     while(*p++ = *template++) if(p[-1] == '%') {
9928         switch(*template++) {
9929           case 0:   *p = 0; return buf;
9930           case 'Y': i = tm->tm_year+1900; break;
9931           case 'y': i = tm->tm_year-100; break;
9932           case 'M': i = tm->tm_mon+1; break;
9933           case 'd': i = tm->tm_mday; break;
9934           case 'h': i = tm->tm_hour; break;
9935           case 'm': i = tm->tm_min; break;
9936           case 's': i = tm->tm_sec; break;
9937           default:  i = 0;
9938         }
9939         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9940     }
9941     return buf;
9942 }
9943
9944 int
9945 CountPlayers (char *p)
9946 {
9947     int n = 0;
9948     while(p = strchr(p, '\n')) p++, n++; // count participants
9949     return n;
9950 }
9951
9952 FILE *
9953 WriteTourneyFile (char *results, FILE *f)
9954 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9955     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9956     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9957         // create a file with tournament description
9958         fprintf(f, "-participants {%s}\n", appData.participants);
9959         fprintf(f, "-seedBase %d\n", appData.seedBase);
9960         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9961         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9962         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9963         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9964         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9965         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9966         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9967         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9968         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9969         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9970         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9971         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9972         if(searchTime > 0)
9973                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9974         else {
9975                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9976                 fprintf(f, "-tc %s\n", appData.timeControl);
9977                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9978         }
9979         fprintf(f, "-results \"%s\"\n", results);
9980     }
9981     return f;
9982 }
9983
9984 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9985
9986 void
9987 Substitute (char *participants, int expunge)
9988 {
9989     int i, changed, changes=0, nPlayers=0;
9990     char *p, *q, *r, buf[MSG_SIZ];
9991     if(participants == NULL) return;
9992     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9993     r = p = participants; q = appData.participants;
9994     while(*p && *p == *q) {
9995         if(*p == '\n') r = p+1, nPlayers++;
9996         p++; q++;
9997     }
9998     if(*p) { // difference
9999         while(*p && *p++ != '\n');
10000         while(*q && *q++ != '\n');
10001       changed = nPlayers;
10002         changes = 1 + (strcmp(p, q) != 0);
10003     }
10004     if(changes == 1) { // a single engine mnemonic was changed
10005         q = r; while(*q) nPlayers += (*q++ == '\n');
10006         p = buf; while(*r && (*p = *r++) != '\n') p++;
10007         *p = NULLCHAR;
10008         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10009         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10010         if(mnemonic[i]) { // The substitute is valid
10011             FILE *f;
10012             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10013                 flock(fileno(f), LOCK_EX);
10014                 ParseArgsFromFile(f);
10015                 fseek(f, 0, SEEK_SET);
10016                 FREE(appData.participants); appData.participants = participants;
10017                 if(expunge) { // erase results of replaced engine
10018                     int len = strlen(appData.results), w, b, dummy;
10019                     for(i=0; i<len; i++) {
10020                         Pairing(i, nPlayers, &w, &b, &dummy);
10021                         if((w == changed || b == changed) && appData.results[i] == '*') {
10022                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10023                             fclose(f);
10024                             return;
10025                         }
10026                     }
10027                     for(i=0; i<len; i++) {
10028                         Pairing(i, nPlayers, &w, &b, &dummy);
10029                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10030                     }
10031                 }
10032                 WriteTourneyFile(appData.results, f);
10033                 fclose(f); // release lock
10034                 return;
10035             }
10036         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10037     }
10038     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10039     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10040     free(participants);
10041     return;
10042 }
10043
10044 int
10045 CreateTourney (char *name)
10046 {
10047         FILE *f;
10048         if(matchMode && strcmp(name, appData.tourneyFile)) {
10049              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10050         }
10051         if(name[0] == NULLCHAR) {
10052             if(appData.participants[0])
10053                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10054             return 0;
10055         }
10056         f = fopen(name, "r");
10057         if(f) { // file exists
10058             ASSIGN(appData.tourneyFile, name);
10059             ParseArgsFromFile(f); // parse it
10060         } else {
10061             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10062             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10063                 DisplayError(_("Not enough participants"), 0);
10064                 return 0;
10065             }
10066             ASSIGN(appData.tourneyFile, name);
10067             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10068             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10069         }
10070         fclose(f);
10071         appData.noChessProgram = FALSE;
10072         appData.clockMode = TRUE;
10073         SetGNUMode();
10074         return 1;
10075 }
10076
10077 int
10078 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10079 {
10080     char buf[MSG_SIZ], *p, *q;
10081     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10082     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10083     skip = !all && group[0]; // if group requested, we start in skip mode
10084     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10085         p = names; q = buf; header = 0;
10086         while(*p && *p != '\n') *q++ = *p++;
10087         *q = 0;
10088         if(*p == '\n') p++;
10089         if(buf[0] == '#') {
10090             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10091             depth++; // we must be entering a new group
10092             if(all) continue; // suppress printing group headers when complete list requested
10093             header = 1;
10094             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10095         }
10096         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10097         if(engineList[i]) free(engineList[i]);
10098         engineList[i] = strdup(buf);
10099         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10100         if(engineMnemonic[i]) free(engineMnemonic[i]);
10101         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10102             strcat(buf, " (");
10103             sscanf(q + 8, "%s", buf + strlen(buf));
10104             strcat(buf, ")");
10105         }
10106         engineMnemonic[i] = strdup(buf);
10107         i++;
10108     }
10109     engineList[i] = engineMnemonic[i] = NULL;
10110     return i;
10111 }
10112
10113 // following implemented as macro to avoid type limitations
10114 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10115
10116 void
10117 SwapEngines (int n)
10118 {   // swap settings for first engine and other engine (so far only some selected options)
10119     int h;
10120     char *p;
10121     if(n == 0) return;
10122     SWAP(directory, p)
10123     SWAP(chessProgram, p)
10124     SWAP(isUCI, h)
10125     SWAP(hasOwnBookUCI, h)
10126     SWAP(protocolVersion, h)
10127     SWAP(reuse, h)
10128     SWAP(scoreIsAbsolute, h)
10129     SWAP(timeOdds, h)
10130     SWAP(logo, p)
10131     SWAP(pgnName, p)
10132     SWAP(pvSAN, h)
10133     SWAP(engOptions, p)
10134     SWAP(engInitString, p)
10135     SWAP(computerString, p)
10136     SWAP(features, p)
10137     SWAP(fenOverride, p)
10138     SWAP(NPS, h)
10139     SWAP(accumulateTC, h)
10140     SWAP(host, p)
10141 }
10142
10143 int
10144 SetPlayer (int player, char *p)
10145 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10146     int i;
10147     char buf[MSG_SIZ], *engineName;
10148     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10149     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10150     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10151     if(mnemonic[i]) {
10152         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10153         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10154         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10155         ParseArgsFromString(buf);
10156     }
10157     free(engineName);
10158     return i;
10159 }
10160
10161 char *recentEngines;
10162
10163 void
10164 RecentEngineEvent (int nr)
10165 {
10166     int n;
10167 //    SwapEngines(1); // bump first to second
10168 //    ReplaceEngine(&second, 1); // and load it there
10169     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10170     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10171     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10172         ReplaceEngine(&first, 0);
10173         FloatToFront(&appData.recentEngineList, command[n]);
10174     }
10175 }
10176
10177 int
10178 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10179 {   // determine players from game number
10180     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10181
10182     if(appData.tourneyType == 0) {
10183         roundsPerCycle = (nPlayers - 1) | 1;
10184         pairingsPerRound = nPlayers / 2;
10185     } else if(appData.tourneyType > 0) {
10186         roundsPerCycle = nPlayers - appData.tourneyType;
10187         pairingsPerRound = appData.tourneyType;
10188     }
10189     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10190     gamesPerCycle = gamesPerRound * roundsPerCycle;
10191     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10192     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10193     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10194     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10195     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10196     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10197
10198     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10199     if(appData.roundSync) *syncInterval = gamesPerRound;
10200
10201     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10202
10203     if(appData.tourneyType == 0) {
10204         if(curPairing == (nPlayers-1)/2 ) {
10205             *whitePlayer = curRound;
10206             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10207         } else {
10208             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10209             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10210             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10211             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10212         }
10213     } else if(appData.tourneyType > 1) {
10214         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10215         *whitePlayer = curRound + appData.tourneyType;
10216     } else if(appData.tourneyType > 0) {
10217         *whitePlayer = curPairing;
10218         *blackPlayer = curRound + appData.tourneyType;
10219     }
10220
10221     // take care of white/black alternation per round. 
10222     // For cycles and games this is already taken care of by default, derived from matchGame!
10223     return curRound & 1;
10224 }
10225
10226 int
10227 NextTourneyGame (int nr, int *swapColors)
10228 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10229     char *p, *q;
10230     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10231     FILE *tf;
10232     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10233     tf = fopen(appData.tourneyFile, "r");
10234     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10235     ParseArgsFromFile(tf); fclose(tf);
10236     InitTimeControls(); // TC might be altered from tourney file
10237
10238     nPlayers = CountPlayers(appData.participants); // count participants
10239     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10240     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10241
10242     if(syncInterval) {
10243         p = q = appData.results;
10244         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10245         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10246             DisplayMessage(_("Waiting for other game(s)"),"");
10247             waitingForGame = TRUE;
10248             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10249             return 0;
10250         }
10251         waitingForGame = FALSE;
10252     }
10253
10254     if(appData.tourneyType < 0) {
10255         if(nr>=0 && !pairingReceived) {
10256             char buf[1<<16];
10257             if(pairing.pr == NoProc) {
10258                 if(!appData.pairingEngine[0]) {
10259                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10260                     return 0;
10261                 }
10262                 StartChessProgram(&pairing); // starts the pairing engine
10263             }
10264             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10265             SendToProgram(buf, &pairing);
10266             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10267             SendToProgram(buf, &pairing);
10268             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10269         }
10270         pairingReceived = 0;                              // ... so we continue here 
10271         *swapColors = 0;
10272         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10273         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10274         matchGame = 1; roundNr = nr / syncInterval + 1;
10275     }
10276
10277     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10278
10279     // redefine engines, engine dir, etc.
10280     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10281     if(first.pr == NoProc) {
10282       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10283       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10284     }
10285     if(second.pr == NoProc) {
10286       SwapEngines(1);
10287       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10288       SwapEngines(1);         // and make that valid for second engine by swapping
10289       InitEngine(&second, 1);
10290     }
10291     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10292     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10293     return 1;
10294 }
10295
10296 void
10297 NextMatchGame ()
10298 {   // performs game initialization that does not invoke engines, and then tries to start the game
10299     int res, firstWhite, swapColors = 0;
10300     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10301     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
10302         char buf[MSG_SIZ];
10303         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10304         if(strcmp(buf, currentDebugFile)) { // name has changed
10305             FILE *f = fopen(buf, "w");
10306             if(f) { // if opening the new file failed, just keep using the old one
10307                 ASSIGN(currentDebugFile, buf);
10308                 fclose(debugFP);
10309                 debugFP = f;
10310             }
10311             if(appData.serverFileName) {
10312                 if(serverFP) fclose(serverFP);
10313                 serverFP = fopen(appData.serverFileName, "w");
10314                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10315                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10316             }
10317         }
10318     }
10319     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10320     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10321     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10322     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10323     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10324     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10325     Reset(FALSE, first.pr != NoProc);
10326     res = LoadGameOrPosition(matchGame); // setup game
10327     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10328     if(!res) return; // abort when bad game/pos file
10329     TwoMachinesEvent();
10330 }
10331
10332 void
10333 UserAdjudicationEvent (int result)
10334 {
10335     ChessMove gameResult = GameIsDrawn;
10336
10337     if( result > 0 ) {
10338         gameResult = WhiteWins;
10339     }
10340     else if( result < 0 ) {
10341         gameResult = BlackWins;
10342     }
10343
10344     if( gameMode == TwoMachinesPlay ) {
10345         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10346     }
10347 }
10348
10349
10350 // [HGM] save: calculate checksum of game to make games easily identifiable
10351 int
10352 StringCheckSum (char *s)
10353 {
10354         int i = 0;
10355         if(s==NULL) return 0;
10356         while(*s) i = i*259 + *s++;
10357         return i;
10358 }
10359
10360 int
10361 GameCheckSum ()
10362 {
10363         int i, sum=0;
10364         for(i=backwardMostMove; i<forwardMostMove; i++) {
10365                 sum += pvInfoList[i].depth;
10366                 sum += StringCheckSum(parseList[i]);
10367                 sum += StringCheckSum(commentList[i]);
10368                 sum *= 261;
10369         }
10370         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10371         return sum + StringCheckSum(commentList[i]);
10372 } // end of save patch
10373
10374 void
10375 GameEnds (ChessMove result, char *resultDetails, int whosays)
10376 {
10377     GameMode nextGameMode;
10378     int isIcsGame;
10379     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10380
10381     if(endingGame) return; /* [HGM] crash: forbid recursion */
10382     endingGame = 1;
10383     if(twoBoards) { // [HGM] dual: switch back to one board
10384         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10385         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10386     }
10387     if (appData.debugMode) {
10388       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10389               result, resultDetails ? resultDetails : "(null)", whosays);
10390     }
10391
10392     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10393
10394     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10395         /* If we are playing on ICS, the server decides when the
10396            game is over, but the engine can offer to draw, claim
10397            a draw, or resign.
10398          */
10399 #if ZIPPY
10400         if (appData.zippyPlay && first.initDone) {
10401             if (result == GameIsDrawn) {
10402                 /* In case draw still needs to be claimed */
10403                 SendToICS(ics_prefix);
10404                 SendToICS("draw\n");
10405             } else if (StrCaseStr(resultDetails, "resign")) {
10406                 SendToICS(ics_prefix);
10407                 SendToICS("resign\n");
10408             }
10409         }
10410 #endif
10411         endingGame = 0; /* [HGM] crash */
10412         return;
10413     }
10414
10415     /* If we're loading the game from a file, stop */
10416     if (whosays == GE_FILE) {
10417       (void) StopLoadGameTimer();
10418       gameFileFP = NULL;
10419     }
10420
10421     /* Cancel draw offers */
10422     first.offeredDraw = second.offeredDraw = 0;
10423
10424     /* If this is an ICS game, only ICS can really say it's done;
10425        if not, anyone can. */
10426     isIcsGame = (gameMode == IcsPlayingWhite ||
10427                  gameMode == IcsPlayingBlack ||
10428                  gameMode == IcsObserving    ||
10429                  gameMode == IcsExamining);
10430
10431     if (!isIcsGame || whosays == GE_ICS) {
10432         /* OK -- not an ICS game, or ICS said it was done */
10433         StopClocks();
10434         if (!isIcsGame && !appData.noChessProgram)
10435           SetUserThinkingEnables();
10436
10437         /* [HGM] if a machine claims the game end we verify this claim */
10438         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10439             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10440                 char claimer;
10441                 ChessMove trueResult = (ChessMove) -1;
10442
10443                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10444                                             first.twoMachinesColor[0] :
10445                                             second.twoMachinesColor[0] ;
10446
10447                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10448                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10449                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10450                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10451                 } else
10452                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10453                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10454                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10455                 } else
10456                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10457                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10458                 }
10459
10460                 // now verify win claims, but not in drop games, as we don't understand those yet
10461                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10462                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10463                     (result == WhiteWins && claimer == 'w' ||
10464                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10465                       if (appData.debugMode) {
10466                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10467                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10468                       }
10469                       if(result != trueResult) {
10470                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10471                               result = claimer == 'w' ? BlackWins : WhiteWins;
10472                               resultDetails = buf;
10473                       }
10474                 } else
10475                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10476                     && (forwardMostMove <= backwardMostMove ||
10477                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10478                         (claimer=='b')==(forwardMostMove&1))
10479                                                                                   ) {
10480                       /* [HGM] verify: draws that were not flagged are false claims */
10481                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10482                       result = claimer == 'w' ? BlackWins : WhiteWins;
10483                       resultDetails = buf;
10484                 }
10485                 /* (Claiming a loss is accepted no questions asked!) */
10486             }
10487             /* [HGM] bare: don't allow bare King to win */
10488             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10489                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10490                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10491                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10492                && result != GameIsDrawn)
10493             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10494                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10495                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10496                         if(p >= 0 && p <= (int)WhiteKing) k++;
10497                 }
10498                 if (appData.debugMode) {
10499                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10500                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10501                 }
10502                 if(k <= 1) {
10503                         result = GameIsDrawn;
10504                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10505                         resultDetails = buf;
10506                 }
10507             }
10508         }
10509
10510
10511         if(serverMoves != NULL && !loadFlag) { char c = '=';
10512             if(result==WhiteWins) c = '+';
10513             if(result==BlackWins) c = '-';
10514             if(resultDetails != NULL)
10515                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10516         }
10517         if (resultDetails != NULL) {
10518             gameInfo.result = result;
10519             gameInfo.resultDetails = StrSave(resultDetails);
10520
10521             /* display last move only if game was not loaded from file */
10522             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10523                 DisplayMove(currentMove - 1);
10524
10525             if (forwardMostMove != 0) {
10526                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10527                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10528                                                                 ) {
10529                     if (*appData.saveGameFile != NULLCHAR) {
10530                         SaveGameToFile(appData.saveGameFile, TRUE);
10531                     } else if (appData.autoSaveGames) {
10532                         AutoSaveGame();
10533                     }
10534                     if (*appData.savePositionFile != NULLCHAR) {
10535                         SavePositionToFile(appData.savePositionFile);
10536                     }
10537                 }
10538             }
10539
10540             /* Tell program how game ended in case it is learning */
10541             /* [HGM] Moved this to after saving the PGN, just in case */
10542             /* engine died and we got here through time loss. In that */
10543             /* case we will get a fatal error writing the pipe, which */
10544             /* would otherwise lose us the PGN.                       */
10545             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10546             /* output during GameEnds should never be fatal anymore   */
10547             if (gameMode == MachinePlaysWhite ||
10548                 gameMode == MachinePlaysBlack ||
10549                 gameMode == TwoMachinesPlay ||
10550                 gameMode == IcsPlayingWhite ||
10551                 gameMode == IcsPlayingBlack ||
10552                 gameMode == BeginningOfGame) {
10553                 char buf[MSG_SIZ];
10554                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10555                         resultDetails);
10556                 if (first.pr != NoProc) {
10557                     SendToProgram(buf, &first);
10558                 }
10559                 if (second.pr != NoProc &&
10560                     gameMode == TwoMachinesPlay) {
10561                     SendToProgram(buf, &second);
10562                 }
10563             }
10564         }
10565
10566         if (appData.icsActive) {
10567             if (appData.quietPlay &&
10568                 (gameMode == IcsPlayingWhite ||
10569                  gameMode == IcsPlayingBlack)) {
10570                 SendToICS(ics_prefix);
10571                 SendToICS("set shout 1\n");
10572             }
10573             nextGameMode = IcsIdle;
10574             ics_user_moved = FALSE;
10575             /* clean up premove.  It's ugly when the game has ended and the
10576              * premove highlights are still on the board.
10577              */
10578             if (gotPremove) {
10579               gotPremove = FALSE;
10580               ClearPremoveHighlights();
10581               DrawPosition(FALSE, boards[currentMove]);
10582             }
10583             if (whosays == GE_ICS) {
10584                 switch (result) {
10585                 case WhiteWins:
10586                     if (gameMode == IcsPlayingWhite)
10587                         PlayIcsWinSound();
10588                     else if(gameMode == IcsPlayingBlack)
10589                         PlayIcsLossSound();
10590                     break;
10591                 case BlackWins:
10592                     if (gameMode == IcsPlayingBlack)
10593                         PlayIcsWinSound();
10594                     else if(gameMode == IcsPlayingWhite)
10595                         PlayIcsLossSound();
10596                     break;
10597                 case GameIsDrawn:
10598                     PlayIcsDrawSound();
10599                     break;
10600                 default:
10601                     PlayIcsUnfinishedSound();
10602                 }
10603             }
10604         } else if (gameMode == EditGame ||
10605                    gameMode == PlayFromGameFile ||
10606                    gameMode == AnalyzeMode ||
10607                    gameMode == AnalyzeFile) {
10608             nextGameMode = gameMode;
10609         } else {
10610             nextGameMode = EndOfGame;
10611         }
10612         pausing = FALSE;
10613         ModeHighlight();
10614     } else {
10615         nextGameMode = gameMode;
10616     }
10617
10618     if (appData.noChessProgram) {
10619         gameMode = nextGameMode;
10620         ModeHighlight();
10621         endingGame = 0; /* [HGM] crash */
10622         return;
10623     }
10624
10625     if (first.reuse) {
10626         /* Put first chess program into idle state */
10627         if (first.pr != NoProc &&
10628             (gameMode == MachinePlaysWhite ||
10629              gameMode == MachinePlaysBlack ||
10630              gameMode == TwoMachinesPlay ||
10631              gameMode == IcsPlayingWhite ||
10632              gameMode == IcsPlayingBlack ||
10633              gameMode == BeginningOfGame)) {
10634             SendToProgram("force\n", &first);
10635             if (first.usePing) {
10636               char buf[MSG_SIZ];
10637               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10638               SendToProgram(buf, &first);
10639             }
10640         }
10641     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10642         /* Kill off first chess program */
10643         if (first.isr != NULL)
10644           RemoveInputSource(first.isr);
10645         first.isr = NULL;
10646
10647         if (first.pr != NoProc) {
10648             ExitAnalyzeMode();
10649             DoSleep( appData.delayBeforeQuit );
10650             SendToProgram("quit\n", &first);
10651             DoSleep( appData.delayAfterQuit );
10652             DestroyChildProcess(first.pr, first.useSigterm);
10653         }
10654         first.pr = NoProc;
10655     }
10656     if (second.reuse) {
10657         /* Put second chess program into idle state */
10658         if (second.pr != NoProc &&
10659             gameMode == TwoMachinesPlay) {
10660             SendToProgram("force\n", &second);
10661             if (second.usePing) {
10662               char buf[MSG_SIZ];
10663               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10664               SendToProgram(buf, &second);
10665             }
10666         }
10667     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10668         /* Kill off second chess program */
10669         if (second.isr != NULL)
10670           RemoveInputSource(second.isr);
10671         second.isr = NULL;
10672
10673         if (second.pr != NoProc) {
10674             DoSleep( appData.delayBeforeQuit );
10675             SendToProgram("quit\n", &second);
10676             DoSleep( appData.delayAfterQuit );
10677             DestroyChildProcess(second.pr, second.useSigterm);
10678         }
10679         second.pr = NoProc;
10680     }
10681
10682     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10683         char resChar = '=';
10684         switch (result) {
10685         case WhiteWins:
10686           resChar = '+';
10687           if (first.twoMachinesColor[0] == 'w') {
10688             first.matchWins++;
10689           } else {
10690             second.matchWins++;
10691           }
10692           break;
10693         case BlackWins:
10694           resChar = '-';
10695           if (first.twoMachinesColor[0] == 'b') {
10696             first.matchWins++;
10697           } else {
10698             second.matchWins++;
10699           }
10700           break;
10701         case GameUnfinished:
10702           resChar = ' ';
10703         default:
10704           break;
10705         }
10706
10707         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10708         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10709             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10710             ReserveGame(nextGame, resChar); // sets nextGame
10711             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10712             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10713         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10714
10715         if (nextGame <= appData.matchGames && !abortMatch) {
10716             gameMode = nextGameMode;
10717             matchGame = nextGame; // this will be overruled in tourney mode!
10718             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10719             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10720             endingGame = 0; /* [HGM] crash */
10721             return;
10722         } else {
10723             gameMode = nextGameMode;
10724             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10725                      first.tidy, second.tidy,
10726                      first.matchWins, second.matchWins,
10727                      appData.matchGames - (first.matchWins + second.matchWins));
10728             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10729             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10730             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10731             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10732                 first.twoMachinesColor = "black\n";
10733                 second.twoMachinesColor = "white\n";
10734             } else {
10735                 first.twoMachinesColor = "white\n";
10736                 second.twoMachinesColor = "black\n";
10737             }
10738         }
10739     }
10740     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10741         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10742       ExitAnalyzeMode();
10743     gameMode = nextGameMode;
10744     ModeHighlight();
10745     endingGame = 0;  /* [HGM] crash */
10746     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10747         if(matchMode == TRUE) { // match through command line: exit with or without popup
10748             if(ranking) {
10749                 ToNrEvent(forwardMostMove);
10750                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10751                 else ExitEvent(0);
10752             } else DisplayFatalError(buf, 0, 0);
10753         } else { // match through menu; just stop, with or without popup
10754             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10755             ModeHighlight();
10756             if(ranking){
10757                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10758             } else DisplayNote(buf);
10759       }
10760       if(ranking) free(ranking);
10761     }
10762 }
10763
10764 /* Assumes program was just initialized (initString sent).
10765    Leaves program in force mode. */
10766 void
10767 FeedMovesToProgram (ChessProgramState *cps, int upto)
10768 {
10769     int i;
10770
10771     if (appData.debugMode)
10772       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10773               startedFromSetupPosition ? "position and " : "",
10774               backwardMostMove, upto, cps->which);
10775     if(currentlyInitializedVariant != gameInfo.variant) {
10776       char buf[MSG_SIZ];
10777         // [HGM] variantswitch: make engine aware of new variant
10778         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10779                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10780         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10781         SendToProgram(buf, cps);
10782         currentlyInitializedVariant = gameInfo.variant;
10783     }
10784     SendToProgram("force\n", cps);
10785     if (startedFromSetupPosition) {
10786         SendBoard(cps, backwardMostMove);
10787     if (appData.debugMode) {
10788         fprintf(debugFP, "feedMoves\n");
10789     }
10790     }
10791     for (i = backwardMostMove; i < upto; i++) {
10792         SendMoveToProgram(i, cps);
10793     }
10794 }
10795
10796
10797 int
10798 ResurrectChessProgram ()
10799 {
10800      /* The chess program may have exited.
10801         If so, restart it and feed it all the moves made so far. */
10802     static int doInit = 0;
10803
10804     if (appData.noChessProgram) return 1;
10805
10806     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10807         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10808         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10809         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10810     } else {
10811         if (first.pr != NoProc) return 1;
10812         StartChessProgram(&first);
10813     }
10814     InitChessProgram(&first, FALSE);
10815     FeedMovesToProgram(&first, currentMove);
10816
10817     if (!first.sendTime) {
10818         /* can't tell gnuchess what its clock should read,
10819            so we bow to its notion. */
10820         ResetClocks();
10821         timeRemaining[0][currentMove] = whiteTimeRemaining;
10822         timeRemaining[1][currentMove] = blackTimeRemaining;
10823     }
10824
10825     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10826                 appData.icsEngineAnalyze) && first.analysisSupport) {
10827       SendToProgram("analyze\n", &first);
10828       first.analyzing = TRUE;
10829     }
10830     return 1;
10831 }
10832
10833 /*
10834  * Button procedures
10835  */
10836 void
10837 Reset (int redraw, int init)
10838 {
10839     int i;
10840
10841     if (appData.debugMode) {
10842         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10843                 redraw, init, gameMode);
10844     }
10845     CleanupTail(); // [HGM] vari: delete any stored variations
10846     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10847     pausing = pauseExamInvalid = FALSE;
10848     startedFromSetupPosition = blackPlaysFirst = FALSE;
10849     firstMove = TRUE;
10850     whiteFlag = blackFlag = FALSE;
10851     userOfferedDraw = FALSE;
10852     hintRequested = bookRequested = FALSE;
10853     first.maybeThinking = FALSE;
10854     second.maybeThinking = FALSE;
10855     first.bookSuspend = FALSE; // [HGM] book
10856     second.bookSuspend = FALSE;
10857     thinkOutput[0] = NULLCHAR;
10858     lastHint[0] = NULLCHAR;
10859     ClearGameInfo(&gameInfo);
10860     gameInfo.variant = StringToVariant(appData.variant);
10861     ics_user_moved = ics_clock_paused = FALSE;
10862     ics_getting_history = H_FALSE;
10863     ics_gamenum = -1;
10864     white_holding[0] = black_holding[0] = NULLCHAR;
10865     ClearProgramStats();
10866     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10867
10868     ResetFrontEnd();
10869     ClearHighlights();
10870     flipView = appData.flipView;
10871     ClearPremoveHighlights();
10872     gotPremove = FALSE;
10873     alarmSounded = FALSE;
10874
10875     GameEnds(EndOfFile, NULL, GE_PLAYER);
10876     if(appData.serverMovesName != NULL) {
10877         /* [HGM] prepare to make moves file for broadcasting */
10878         clock_t t = clock();
10879         if(serverMoves != NULL) fclose(serverMoves);
10880         serverMoves = fopen(appData.serverMovesName, "r");
10881         if(serverMoves != NULL) {
10882             fclose(serverMoves);
10883             /* delay 15 sec before overwriting, so all clients can see end */
10884             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10885         }
10886         serverMoves = fopen(appData.serverMovesName, "w");
10887     }
10888
10889     ExitAnalyzeMode();
10890     gameMode = BeginningOfGame;
10891     ModeHighlight();
10892     if(appData.icsActive) gameInfo.variant = VariantNormal;
10893     currentMove = forwardMostMove = backwardMostMove = 0;
10894     MarkTargetSquares(1);
10895     InitPosition(redraw);
10896     for (i = 0; i < MAX_MOVES; i++) {
10897         if (commentList[i] != NULL) {
10898             free(commentList[i]);
10899             commentList[i] = NULL;
10900         }
10901     }
10902     ResetClocks();
10903     timeRemaining[0][0] = whiteTimeRemaining;
10904     timeRemaining[1][0] = blackTimeRemaining;
10905
10906     if (first.pr == NoProc) {
10907         StartChessProgram(&first);
10908     }
10909     if (init) {
10910             InitChessProgram(&first, startedFromSetupPosition);
10911     }
10912     DisplayTitle("");
10913     DisplayMessage("", "");
10914     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10915     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10916     ClearMap();        // [HGM] exclude: invalidate map
10917 }
10918
10919 void
10920 AutoPlayGameLoop ()
10921 {
10922     for (;;) {
10923         if (!AutoPlayOneMove())
10924           return;
10925         if (matchMode || appData.timeDelay == 0)
10926           continue;
10927         if (appData.timeDelay < 0)
10928           return;
10929         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
10930         break;
10931     }
10932 }
10933
10934
10935 int
10936 AutoPlayOneMove ()
10937 {
10938     int fromX, fromY, toX, toY;
10939
10940     if (appData.debugMode) {
10941       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10942     }
10943
10944     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10945       return FALSE;
10946
10947     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10948       pvInfoList[currentMove].depth = programStats.depth;
10949       pvInfoList[currentMove].score = programStats.score;
10950       pvInfoList[currentMove].time  = 0;
10951       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10952     }
10953
10954     if (currentMove >= forwardMostMove) {
10955       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10956 //      gameMode = EndOfGame;
10957 //      ModeHighlight();
10958
10959       /* [AS] Clear current move marker at the end of a game */
10960       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10961
10962       return FALSE;
10963     }
10964
10965     toX = moveList[currentMove][2] - AAA;
10966     toY = moveList[currentMove][3] - ONE;
10967
10968     if (moveList[currentMove][1] == '@') {
10969         if (appData.highlightLastMove) {
10970             SetHighlights(-1, -1, toX, toY);
10971         }
10972     } else {
10973         fromX = moveList[currentMove][0] - AAA;
10974         fromY = moveList[currentMove][1] - ONE;
10975
10976         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10977
10978         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10979
10980         if (appData.highlightLastMove) {
10981             SetHighlights(fromX, fromY, toX, toY);
10982         }
10983     }
10984     DisplayMove(currentMove);
10985     SendMoveToProgram(currentMove++, &first);
10986     DisplayBothClocks();
10987     DrawPosition(FALSE, boards[currentMove]);
10988     // [HGM] PV info: always display, routine tests if empty
10989     DisplayComment(currentMove - 1, commentList[currentMove]);
10990     return TRUE;
10991 }
10992
10993
10994 int
10995 LoadGameOneMove (ChessMove readAhead)
10996 {
10997     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10998     char promoChar = NULLCHAR;
10999     ChessMove moveType;
11000     char move[MSG_SIZ];
11001     char *p, *q;
11002
11003     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11004         gameMode != AnalyzeMode && gameMode != Training) {
11005         gameFileFP = NULL;
11006         return FALSE;
11007     }
11008
11009     yyboardindex = forwardMostMove;
11010     if (readAhead != EndOfFile) {
11011       moveType = readAhead;
11012     } else {
11013       if (gameFileFP == NULL)
11014           return FALSE;
11015       moveType = (ChessMove) Myylex();
11016     }
11017
11018     done = FALSE;
11019     switch (moveType) {
11020       case Comment:
11021         if (appData.debugMode)
11022           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11023         p = yy_text;
11024
11025         /* append the comment but don't display it */
11026         AppendComment(currentMove, p, FALSE);
11027         return TRUE;
11028
11029       case WhiteCapturesEnPassant:
11030       case BlackCapturesEnPassant:
11031       case WhitePromotion:
11032       case BlackPromotion:
11033       case WhiteNonPromotion:
11034       case BlackNonPromotion:
11035       case NormalMove:
11036       case WhiteKingSideCastle:
11037       case WhiteQueenSideCastle:
11038       case BlackKingSideCastle:
11039       case BlackQueenSideCastle:
11040       case WhiteKingSideCastleWild:
11041       case WhiteQueenSideCastleWild:
11042       case BlackKingSideCastleWild:
11043       case BlackQueenSideCastleWild:
11044       /* PUSH Fabien */
11045       case WhiteHSideCastleFR:
11046       case WhiteASideCastleFR:
11047       case BlackHSideCastleFR:
11048       case BlackASideCastleFR:
11049       /* POP Fabien */
11050         if (appData.debugMode)
11051           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11052         fromX = currentMoveString[0] - AAA;
11053         fromY = currentMoveString[1] - ONE;
11054         toX = currentMoveString[2] - AAA;
11055         toY = currentMoveString[3] - ONE;
11056         promoChar = currentMoveString[4];
11057         break;
11058
11059       case WhiteDrop:
11060       case BlackDrop:
11061         if (appData.debugMode)
11062           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11063         fromX = moveType == WhiteDrop ?
11064           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11065         (int) CharToPiece(ToLower(currentMoveString[0]));
11066         fromY = DROP_RANK;
11067         toX = currentMoveString[2] - AAA;
11068         toY = currentMoveString[3] - ONE;
11069         break;
11070
11071       case WhiteWins:
11072       case BlackWins:
11073       case GameIsDrawn:
11074       case GameUnfinished:
11075         if (appData.debugMode)
11076           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11077         p = strchr(yy_text, '{');
11078         if (p == NULL) p = strchr(yy_text, '(');
11079         if (p == NULL) {
11080             p = yy_text;
11081             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11082         } else {
11083             q = strchr(p, *p == '{' ? '}' : ')');
11084             if (q != NULL) *q = NULLCHAR;
11085             p++;
11086         }
11087         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11088         GameEnds(moveType, p, GE_FILE);
11089         done = TRUE;
11090         if (cmailMsgLoaded) {
11091             ClearHighlights();
11092             flipView = WhiteOnMove(currentMove);
11093             if (moveType == GameUnfinished) flipView = !flipView;
11094             if (appData.debugMode)
11095               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11096         }
11097         break;
11098
11099       case EndOfFile:
11100         if (appData.debugMode)
11101           fprintf(debugFP, "Parser hit end of file\n");
11102         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11103           case MT_NONE:
11104           case MT_CHECK:
11105             break;
11106           case MT_CHECKMATE:
11107           case MT_STAINMATE:
11108             if (WhiteOnMove(currentMove)) {
11109                 GameEnds(BlackWins, "Black mates", GE_FILE);
11110             } else {
11111                 GameEnds(WhiteWins, "White mates", GE_FILE);
11112             }
11113             break;
11114           case MT_STALEMATE:
11115             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11116             break;
11117         }
11118         done = TRUE;
11119         break;
11120
11121       case MoveNumberOne:
11122         if (lastLoadGameStart == GNUChessGame) {
11123             /* GNUChessGames have numbers, but they aren't move numbers */
11124             if (appData.debugMode)
11125               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11126                       yy_text, (int) moveType);
11127             return LoadGameOneMove(EndOfFile); /* tail recursion */
11128         }
11129         /* else fall thru */
11130
11131       case XBoardGame:
11132       case GNUChessGame:
11133       case PGNTag:
11134         /* Reached start of next game in file */
11135         if (appData.debugMode)
11136           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11137         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11138           case MT_NONE:
11139           case MT_CHECK:
11140             break;
11141           case MT_CHECKMATE:
11142           case MT_STAINMATE:
11143             if (WhiteOnMove(currentMove)) {
11144                 GameEnds(BlackWins, "Black mates", GE_FILE);
11145             } else {
11146                 GameEnds(WhiteWins, "White mates", GE_FILE);
11147             }
11148             break;
11149           case MT_STALEMATE:
11150             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11151             break;
11152         }
11153         done = TRUE;
11154         break;
11155
11156       case PositionDiagram:     /* should not happen; ignore */
11157       case ElapsedTime:         /* ignore */
11158       case NAG:                 /* ignore */
11159         if (appData.debugMode)
11160           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11161                   yy_text, (int) moveType);
11162         return LoadGameOneMove(EndOfFile); /* tail recursion */
11163
11164       case IllegalMove:
11165         if (appData.testLegality) {
11166             if (appData.debugMode)
11167               fprintf(debugFP, "Parsed IllegalMove: %s\n", 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         } else {
11174             if (appData.debugMode)
11175               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11176                       yy_text, currentMoveString);
11177             fromX = currentMoveString[0] - AAA;
11178             fromY = currentMoveString[1] - ONE;
11179             toX = currentMoveString[2] - AAA;
11180             toY = currentMoveString[3] - ONE;
11181             promoChar = currentMoveString[4];
11182         }
11183         break;
11184
11185       case AmbiguousMove:
11186         if (appData.debugMode)
11187           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11188         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11189                 (forwardMostMove / 2) + 1,
11190                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11191         DisplayError(move, 0);
11192         done = TRUE;
11193         break;
11194
11195       default:
11196       case ImpossibleMove:
11197         if (appData.debugMode)
11198           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11199         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11200                 (forwardMostMove / 2) + 1,
11201                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11202         DisplayError(move, 0);
11203         done = TRUE;
11204         break;
11205     }
11206
11207     if (done) {
11208         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11209             DrawPosition(FALSE, boards[currentMove]);
11210             DisplayBothClocks();
11211             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11212               DisplayComment(currentMove - 1, commentList[currentMove]);
11213         }
11214         (void) StopLoadGameTimer();
11215         gameFileFP = NULL;
11216         cmailOldMove = forwardMostMove;
11217         return FALSE;
11218     } else {
11219         /* currentMoveString is set as a side-effect of yylex */
11220
11221         thinkOutput[0] = NULLCHAR;
11222         MakeMove(fromX, fromY, toX, toY, promoChar);
11223         currentMove = forwardMostMove;
11224         return TRUE;
11225     }
11226 }
11227
11228 /* Load the nth game from the given file */
11229 int
11230 LoadGameFromFile (char *filename, int n, char *title, int useList)
11231 {
11232     FILE *f;
11233     char buf[MSG_SIZ];
11234
11235     if (strcmp(filename, "-") == 0) {
11236         f = stdin;
11237         title = "stdin";
11238     } else {
11239         f = fopen(filename, "rb");
11240         if (f == NULL) {
11241           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11242             DisplayError(buf, errno);
11243             return FALSE;
11244         }
11245     }
11246     if (fseek(f, 0, 0) == -1) {
11247         /* f is not seekable; probably a pipe */
11248         useList = FALSE;
11249     }
11250     if (useList && n == 0) {
11251         int error = GameListBuild(f);
11252         if (error) {
11253             DisplayError(_("Cannot build game list"), error);
11254         } else if (!ListEmpty(&gameList) &&
11255                    ((ListGame *) gameList.tailPred)->number > 1) {
11256             GameListPopUp(f, title);
11257             return TRUE;
11258         }
11259         GameListDestroy();
11260         n = 1;
11261     }
11262     if (n == 0) n = 1;
11263     return LoadGame(f, n, title, FALSE);
11264 }
11265
11266
11267 void
11268 MakeRegisteredMove ()
11269 {
11270     int fromX, fromY, toX, toY;
11271     char promoChar;
11272     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11273         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11274           case CMAIL_MOVE:
11275           case CMAIL_DRAW:
11276             if (appData.debugMode)
11277               fprintf(debugFP, "Restoring %s for game %d\n",
11278                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11279
11280             thinkOutput[0] = NULLCHAR;
11281             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11282             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11283             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11284             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11285             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11286             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11287             MakeMove(fromX, fromY, toX, toY, promoChar);
11288             ShowMove(fromX, fromY, toX, toY);
11289
11290             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11291               case MT_NONE:
11292               case MT_CHECK:
11293                 break;
11294
11295               case MT_CHECKMATE:
11296               case MT_STAINMATE:
11297                 if (WhiteOnMove(currentMove)) {
11298                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11299                 } else {
11300                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11301                 }
11302                 break;
11303
11304               case MT_STALEMATE:
11305                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11306                 break;
11307             }
11308
11309             break;
11310
11311           case CMAIL_RESIGN:
11312             if (WhiteOnMove(currentMove)) {
11313                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11314             } else {
11315                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11316             }
11317             break;
11318
11319           case CMAIL_ACCEPT:
11320             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11321             break;
11322
11323           default:
11324             break;
11325         }
11326     }
11327
11328     return;
11329 }
11330
11331 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11332 int
11333 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11334 {
11335     int retVal;
11336
11337     if (gameNumber > nCmailGames) {
11338         DisplayError(_("No more games in this message"), 0);
11339         return FALSE;
11340     }
11341     if (f == lastLoadGameFP) {
11342         int offset = gameNumber - lastLoadGameNumber;
11343         if (offset == 0) {
11344             cmailMsg[0] = NULLCHAR;
11345             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11346                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11347                 nCmailMovesRegistered--;
11348             }
11349             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11350             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11351                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11352             }
11353         } else {
11354             if (! RegisterMove()) return FALSE;
11355         }
11356     }
11357
11358     retVal = LoadGame(f, gameNumber, title, useList);
11359
11360     /* Make move registered during previous look at this game, if any */
11361     MakeRegisteredMove();
11362
11363     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11364         commentList[currentMove]
11365           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11366         DisplayComment(currentMove - 1, commentList[currentMove]);
11367     }
11368
11369     return retVal;
11370 }
11371
11372 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11373 int
11374 ReloadGame (int offset)
11375 {
11376     int gameNumber = lastLoadGameNumber + offset;
11377     if (lastLoadGameFP == NULL) {
11378         DisplayError(_("No game has been loaded yet"), 0);
11379         return FALSE;
11380     }
11381     if (gameNumber <= 0) {
11382         DisplayError(_("Can't back up any further"), 0);
11383         return FALSE;
11384     }
11385     if (cmailMsgLoaded) {
11386         return CmailLoadGame(lastLoadGameFP, gameNumber,
11387                              lastLoadGameTitle, lastLoadGameUseList);
11388     } else {
11389         return LoadGame(lastLoadGameFP, gameNumber,
11390                         lastLoadGameTitle, lastLoadGameUseList);
11391     }
11392 }
11393
11394 int keys[EmptySquare+1];
11395
11396 int
11397 PositionMatches (Board b1, Board b2)
11398 {
11399     int r, f, sum=0;
11400     switch(appData.searchMode) {
11401         case 1: return CompareWithRights(b1, b2);
11402         case 2:
11403             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11404                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11405             }
11406             return TRUE;
11407         case 3:
11408             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11409               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11410                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11411             }
11412             return sum==0;
11413         case 4:
11414             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11415                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11416             }
11417             return sum==0;
11418     }
11419     return TRUE;
11420 }
11421
11422 #define Q_PROMO  4
11423 #define Q_EP     3
11424 #define Q_BCASTL 2
11425 #define Q_WCASTL 1
11426
11427 int pieceList[256], quickBoard[256];
11428 ChessSquare pieceType[256] = { EmptySquare };
11429 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11430 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11431 int soughtTotal, turn;
11432 Boolean epOK, flipSearch;
11433
11434 typedef struct {
11435     unsigned char piece, to;
11436 } Move;
11437
11438 #define DSIZE (250000)
11439
11440 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11441 Move *moveDatabase = initialSpace;
11442 unsigned int movePtr, dataSize = DSIZE;
11443
11444 int
11445 MakePieceList (Board board, int *counts)
11446 {
11447     int r, f, n=Q_PROMO, total=0;
11448     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11449     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11450         int sq = f + (r<<4);
11451         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11452             quickBoard[sq] = ++n;
11453             pieceList[n] = sq;
11454             pieceType[n] = board[r][f];
11455             counts[board[r][f]]++;
11456             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11457             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11458             total++;
11459         }
11460     }
11461     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11462     return total;
11463 }
11464
11465 void
11466 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11467 {
11468     int sq = fromX + (fromY<<4);
11469     int piece = quickBoard[sq];
11470     quickBoard[sq] = 0;
11471     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11472     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11473         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11474         moveDatabase[movePtr++].piece = Q_WCASTL;
11475         quickBoard[sq] = piece;
11476         piece = quickBoard[from]; quickBoard[from] = 0;
11477         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11478     } else
11479     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11480         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11481         moveDatabase[movePtr++].piece = Q_BCASTL;
11482         quickBoard[sq] = piece;
11483         piece = quickBoard[from]; quickBoard[from] = 0;
11484         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11485     } else
11486     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11487         quickBoard[(fromY<<4)+toX] = 0;
11488         moveDatabase[movePtr].piece = Q_EP;
11489         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11490         moveDatabase[movePtr].to = sq;
11491     } else
11492     if(promoPiece != pieceType[piece]) {
11493         moveDatabase[movePtr++].piece = Q_PROMO;
11494         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11495     }
11496     moveDatabase[movePtr].piece = piece;
11497     quickBoard[sq] = piece;
11498     movePtr++;
11499 }
11500
11501 int
11502 PackGame (Board board)
11503 {
11504     Move *newSpace = NULL;
11505     moveDatabase[movePtr].piece = 0; // terminate previous game
11506     if(movePtr > dataSize) {
11507         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11508         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11509         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11510         if(newSpace) {
11511             int i;
11512             Move *p = moveDatabase, *q = newSpace;
11513             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11514             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11515             moveDatabase = newSpace;
11516         } else { // calloc failed, we must be out of memory. Too bad...
11517             dataSize = 0; // prevent calloc events for all subsequent games
11518             return 0;     // and signal this one isn't cached
11519         }
11520     }
11521     movePtr++;
11522     MakePieceList(board, counts);
11523     return movePtr;
11524 }
11525
11526 int
11527 QuickCompare (Board board, int *minCounts, int *maxCounts)
11528 {   // compare according to search mode
11529     int r, f;
11530     switch(appData.searchMode)
11531     {
11532       case 1: // exact position match
11533         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11534         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11535             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11536         }
11537         break;
11538       case 2: // can have extra material on empty squares
11539         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11540             if(board[r][f] == EmptySquare) continue;
11541             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11542         }
11543         break;
11544       case 3: // material with exact Pawn structure
11545         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11546             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11547             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11548         } // fall through to material comparison
11549       case 4: // exact material
11550         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11551         break;
11552       case 6: // material range with given imbalance
11553         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11554         // fall through to range comparison
11555       case 5: // material range
11556         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11557     }
11558     return TRUE;
11559 }
11560
11561 int
11562 QuickScan (Board board, Move *move)
11563 {   // reconstruct game,and compare all positions in it
11564     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11565     do {
11566         int piece = move->piece;
11567         int to = move->to, from = pieceList[piece];
11568         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11569           if(!piece) return -1;
11570           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11571             piece = (++move)->piece;
11572             from = pieceList[piece];
11573             counts[pieceType[piece]]--;
11574             pieceType[piece] = (ChessSquare) move->to;
11575             counts[move->to]++;
11576           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11577             counts[pieceType[quickBoard[to]]]--;
11578             quickBoard[to] = 0; total--;
11579             move++;
11580             continue;
11581           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11582             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11583             from  = pieceList[piece]; // so this must be King
11584             quickBoard[from] = 0;
11585             pieceList[piece] = to;
11586             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11587             quickBoard[from] = 0; // rook
11588             quickBoard[to] = piece;
11589             to = move->to; piece = move->piece;
11590             goto aftercastle;
11591           }
11592         }
11593         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11594         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11595         quickBoard[from] = 0;
11596       aftercastle:
11597         quickBoard[to] = piece;
11598         pieceList[piece] = to;
11599         cnt++; turn ^= 3;
11600         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11601            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11602            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11603                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11604           ) {
11605             static int lastCounts[EmptySquare+1];
11606             int i;
11607             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11608             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11609         } else stretch = 0;
11610         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11611         move++;
11612     } while(1);
11613 }
11614
11615 void
11616 InitSearch ()
11617 {
11618     int r, f;
11619     flipSearch = FALSE;
11620     CopyBoard(soughtBoard, boards[currentMove]);
11621     soughtTotal = MakePieceList(soughtBoard, maxSought);
11622     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11623     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11624     CopyBoard(reverseBoard, boards[currentMove]);
11625     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11626         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11627         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11628         reverseBoard[r][f] = piece;
11629     }
11630     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11631     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11632     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11633                  || (boards[currentMove][CASTLING][2] == NoRights || 
11634                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11635                  && (boards[currentMove][CASTLING][5] == NoRights || 
11636                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11637       ) {
11638         flipSearch = TRUE;
11639         CopyBoard(flipBoard, soughtBoard);
11640         CopyBoard(rotateBoard, reverseBoard);
11641         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11642             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11643             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11644         }
11645     }
11646     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11647     if(appData.searchMode >= 5) {
11648         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11649         MakePieceList(soughtBoard, minSought);
11650         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11651     }
11652     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11653         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11654 }
11655
11656 GameInfo dummyInfo;
11657
11658 int
11659 GameContainsPosition (FILE *f, ListGame *lg)
11660 {
11661     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11662     int fromX, fromY, toX, toY;
11663     char promoChar;
11664     static int initDone=FALSE;
11665
11666     // weed out games based on numerical tag comparison
11667     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11668     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11669     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11670     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11671     if(!initDone) {
11672         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11673         initDone = TRUE;
11674     }
11675     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11676     else CopyBoard(boards[scratch], initialPosition); // default start position
11677     if(lg->moves) {
11678         turn = btm + 1;
11679         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11680         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11681     }
11682     if(btm) plyNr++;
11683     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11684     fseek(f, lg->offset, 0);
11685     yynewfile(f);
11686     while(1) {
11687         yyboardindex = scratch;
11688         quickFlag = plyNr+1;
11689         next = Myylex();
11690         quickFlag = 0;
11691         switch(next) {
11692             case PGNTag:
11693                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11694             default:
11695                 continue;
11696
11697             case XBoardGame:
11698             case GNUChessGame:
11699                 if(plyNr) return -1; // after we have seen moves, this is for new game
11700               continue;
11701
11702             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11703             case ImpossibleMove:
11704             case WhiteWins: // game ends here with these four
11705             case BlackWins:
11706             case GameIsDrawn:
11707             case GameUnfinished:
11708                 return -1;
11709
11710             case IllegalMove:
11711                 if(appData.testLegality) return -1;
11712             case WhiteCapturesEnPassant:
11713             case BlackCapturesEnPassant:
11714             case WhitePromotion:
11715             case BlackPromotion:
11716             case WhiteNonPromotion:
11717             case BlackNonPromotion:
11718             case NormalMove:
11719             case WhiteKingSideCastle:
11720             case WhiteQueenSideCastle:
11721             case BlackKingSideCastle:
11722             case BlackQueenSideCastle:
11723             case WhiteKingSideCastleWild:
11724             case WhiteQueenSideCastleWild:
11725             case BlackKingSideCastleWild:
11726             case BlackQueenSideCastleWild:
11727             case WhiteHSideCastleFR:
11728             case WhiteASideCastleFR:
11729             case BlackHSideCastleFR:
11730             case BlackASideCastleFR:
11731                 fromX = currentMoveString[0] - AAA;
11732                 fromY = currentMoveString[1] - ONE;
11733                 toX = currentMoveString[2] - AAA;
11734                 toY = currentMoveString[3] - ONE;
11735                 promoChar = currentMoveString[4];
11736                 break;
11737             case WhiteDrop:
11738             case BlackDrop:
11739                 fromX = next == WhiteDrop ?
11740                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11741                   (int) CharToPiece(ToLower(currentMoveString[0]));
11742                 fromY = DROP_RANK;
11743                 toX = currentMoveString[2] - AAA;
11744                 toY = currentMoveString[3] - ONE;
11745                 promoChar = 0;
11746                 break;
11747         }
11748         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11749         plyNr++;
11750         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11751         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11752         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11753         if(appData.findMirror) {
11754             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11755             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11756         }
11757     }
11758 }
11759
11760 /* Load the nth game from open file f */
11761 int
11762 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11763 {
11764     ChessMove cm;
11765     char buf[MSG_SIZ];
11766     int gn = gameNumber;
11767     ListGame *lg = NULL;
11768     int numPGNTags = 0;
11769     int err, pos = -1;
11770     GameMode oldGameMode;
11771     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11772
11773     if (appData.debugMode)
11774         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11775
11776     if (gameMode == Training )
11777         SetTrainingModeOff();
11778
11779     oldGameMode = gameMode;
11780     if (gameMode != BeginningOfGame) {
11781       Reset(FALSE, TRUE);
11782     }
11783
11784     gameFileFP = f;
11785     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11786         fclose(lastLoadGameFP);
11787     }
11788
11789     if (useList) {
11790         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11791
11792         if (lg) {
11793             fseek(f, lg->offset, 0);
11794             GameListHighlight(gameNumber);
11795             pos = lg->position;
11796             gn = 1;
11797         }
11798         else {
11799             DisplayError(_("Game number out of range"), 0);
11800             return FALSE;
11801         }
11802     } else {
11803         GameListDestroy();
11804         if (fseek(f, 0, 0) == -1) {
11805             if (f == lastLoadGameFP ?
11806                 gameNumber == lastLoadGameNumber + 1 :
11807                 gameNumber == 1) {
11808                 gn = 1;
11809             } else {
11810                 DisplayError(_("Can't seek on game file"), 0);
11811                 return FALSE;
11812             }
11813         }
11814     }
11815     lastLoadGameFP = f;
11816     lastLoadGameNumber = gameNumber;
11817     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11818     lastLoadGameUseList = useList;
11819
11820     yynewfile(f);
11821
11822     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11823       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11824                 lg->gameInfo.black);
11825             DisplayTitle(buf);
11826     } else if (*title != NULLCHAR) {
11827         if (gameNumber > 1) {
11828           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11829             DisplayTitle(buf);
11830         } else {
11831             DisplayTitle(title);
11832         }
11833     }
11834
11835     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11836         gameMode = PlayFromGameFile;
11837         ModeHighlight();
11838     }
11839
11840     currentMove = forwardMostMove = backwardMostMove = 0;
11841     CopyBoard(boards[0], initialPosition);
11842     StopClocks();
11843
11844     /*
11845      * Skip the first gn-1 games in the file.
11846      * Also skip over anything that precedes an identifiable
11847      * start of game marker, to avoid being confused by
11848      * garbage at the start of the file.  Currently
11849      * recognized start of game markers are the move number "1",
11850      * the pattern "gnuchess .* game", the pattern
11851      * "^[#;%] [^ ]* game file", and a PGN tag block.
11852      * A game that starts with one of the latter two patterns
11853      * will also have a move number 1, possibly
11854      * following a position diagram.
11855      * 5-4-02: Let's try being more lenient and allowing a game to
11856      * start with an unnumbered move.  Does that break anything?
11857      */
11858     cm = lastLoadGameStart = EndOfFile;
11859     while (gn > 0) {
11860         yyboardindex = forwardMostMove;
11861         cm = (ChessMove) Myylex();
11862         switch (cm) {
11863           case EndOfFile:
11864             if (cmailMsgLoaded) {
11865                 nCmailGames = CMAIL_MAX_GAMES - gn;
11866             } else {
11867                 Reset(TRUE, TRUE);
11868                 DisplayError(_("Game not found in file"), 0);
11869             }
11870             return FALSE;
11871
11872           case GNUChessGame:
11873           case XBoardGame:
11874             gn--;
11875             lastLoadGameStart = cm;
11876             break;
11877
11878           case MoveNumberOne:
11879             switch (lastLoadGameStart) {
11880               case GNUChessGame:
11881               case XBoardGame:
11882               case PGNTag:
11883                 break;
11884               case MoveNumberOne:
11885               case EndOfFile:
11886                 gn--;           /* count this game */
11887                 lastLoadGameStart = cm;
11888                 break;
11889               default:
11890                 /* impossible */
11891                 break;
11892             }
11893             break;
11894
11895           case PGNTag:
11896             switch (lastLoadGameStart) {
11897               case GNUChessGame:
11898               case PGNTag:
11899               case MoveNumberOne:
11900               case EndOfFile:
11901                 gn--;           /* count this game */
11902                 lastLoadGameStart = cm;
11903                 break;
11904               case XBoardGame:
11905                 lastLoadGameStart = cm; /* game counted already */
11906                 break;
11907               default:
11908                 /* impossible */
11909                 break;
11910             }
11911             if (gn > 0) {
11912                 do {
11913                     yyboardindex = forwardMostMove;
11914                     cm = (ChessMove) Myylex();
11915                 } while (cm == PGNTag || cm == Comment);
11916             }
11917             break;
11918
11919           case WhiteWins:
11920           case BlackWins:
11921           case GameIsDrawn:
11922             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11923                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11924                     != CMAIL_OLD_RESULT) {
11925                     nCmailResults ++ ;
11926                     cmailResult[  CMAIL_MAX_GAMES
11927                                 - gn - 1] = CMAIL_OLD_RESULT;
11928                 }
11929             }
11930             break;
11931
11932           case NormalMove:
11933             /* Only a NormalMove can be at the start of a game
11934              * without a position diagram. */
11935             if (lastLoadGameStart == EndOfFile ) {
11936               gn--;
11937               lastLoadGameStart = MoveNumberOne;
11938             }
11939             break;
11940
11941           default:
11942             break;
11943         }
11944     }
11945
11946     if (appData.debugMode)
11947       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11948
11949     if (cm == XBoardGame) {
11950         /* Skip any header junk before position diagram and/or move 1 */
11951         for (;;) {
11952             yyboardindex = forwardMostMove;
11953             cm = (ChessMove) Myylex();
11954
11955             if (cm == EndOfFile ||
11956                 cm == GNUChessGame || cm == XBoardGame) {
11957                 /* Empty game; pretend end-of-file and handle later */
11958                 cm = EndOfFile;
11959                 break;
11960             }
11961
11962             if (cm == MoveNumberOne || cm == PositionDiagram ||
11963                 cm == PGNTag || cm == Comment)
11964               break;
11965         }
11966     } else if (cm == GNUChessGame) {
11967         if (gameInfo.event != NULL) {
11968             free(gameInfo.event);
11969         }
11970         gameInfo.event = StrSave(yy_text);
11971     }
11972
11973     startedFromSetupPosition = FALSE;
11974     while (cm == PGNTag) {
11975         if (appData.debugMode)
11976           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11977         err = ParsePGNTag(yy_text, &gameInfo);
11978         if (!err) numPGNTags++;
11979
11980         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11981         if(gameInfo.variant != oldVariant) {
11982             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11983             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11984             InitPosition(TRUE);
11985             oldVariant = gameInfo.variant;
11986             if (appData.debugMode)
11987               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11988         }
11989
11990
11991         if (gameInfo.fen != NULL) {
11992           Board initial_position;
11993           startedFromSetupPosition = TRUE;
11994           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11995             Reset(TRUE, TRUE);
11996             DisplayError(_("Bad FEN position in file"), 0);
11997             return FALSE;
11998           }
11999           CopyBoard(boards[0], initial_position);
12000           if (blackPlaysFirst) {
12001             currentMove = forwardMostMove = backwardMostMove = 1;
12002             CopyBoard(boards[1], initial_position);
12003             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12004             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12005             timeRemaining[0][1] = whiteTimeRemaining;
12006             timeRemaining[1][1] = blackTimeRemaining;
12007             if (commentList[0] != NULL) {
12008               commentList[1] = commentList[0];
12009               commentList[0] = NULL;
12010             }
12011           } else {
12012             currentMove = forwardMostMove = backwardMostMove = 0;
12013           }
12014           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12015           {   int i;
12016               initialRulePlies = FENrulePlies;
12017               for( i=0; i< nrCastlingRights; i++ )
12018                   initialRights[i] = initial_position[CASTLING][i];
12019           }
12020           yyboardindex = forwardMostMove;
12021           free(gameInfo.fen);
12022           gameInfo.fen = NULL;
12023         }
12024
12025         yyboardindex = forwardMostMove;
12026         cm = (ChessMove) Myylex();
12027
12028         /* Handle comments interspersed among the tags */
12029         while (cm == Comment) {
12030             char *p;
12031             if (appData.debugMode)
12032               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12033             p = yy_text;
12034             AppendComment(currentMove, p, FALSE);
12035             yyboardindex = forwardMostMove;
12036             cm = (ChessMove) Myylex();
12037         }
12038     }
12039
12040     /* don't rely on existence of Event tag since if game was
12041      * pasted from clipboard the Event tag may not exist
12042      */
12043     if (numPGNTags > 0){
12044         char *tags;
12045         if (gameInfo.variant == VariantNormal) {
12046           VariantClass v = StringToVariant(gameInfo.event);
12047           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12048           if(v < VariantShogi) gameInfo.variant = v;
12049         }
12050         if (!matchMode) {
12051           if( appData.autoDisplayTags ) {
12052             tags = PGNTags(&gameInfo);
12053             TagsPopUp(tags, CmailMsg());
12054             free(tags);
12055           }
12056         }
12057     } else {
12058         /* Make something up, but don't display it now */
12059         SetGameInfo();
12060         TagsPopDown();
12061     }
12062
12063     if (cm == PositionDiagram) {
12064         int i, j;
12065         char *p;
12066         Board initial_position;
12067
12068         if (appData.debugMode)
12069           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12070
12071         if (!startedFromSetupPosition) {
12072             p = yy_text;
12073             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12074               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12075                 switch (*p) {
12076                   case '{':
12077                   case '[':
12078                   case '-':
12079                   case ' ':
12080                   case '\t':
12081                   case '\n':
12082                   case '\r':
12083                     break;
12084                   default:
12085                     initial_position[i][j++] = CharToPiece(*p);
12086                     break;
12087                 }
12088             while (*p == ' ' || *p == '\t' ||
12089                    *p == '\n' || *p == '\r') p++;
12090
12091             if (strncmp(p, "black", strlen("black"))==0)
12092               blackPlaysFirst = TRUE;
12093             else
12094               blackPlaysFirst = FALSE;
12095             startedFromSetupPosition = TRUE;
12096
12097             CopyBoard(boards[0], initial_position);
12098             if (blackPlaysFirst) {
12099                 currentMove = forwardMostMove = backwardMostMove = 1;
12100                 CopyBoard(boards[1], initial_position);
12101                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12102                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12103                 timeRemaining[0][1] = whiteTimeRemaining;
12104                 timeRemaining[1][1] = blackTimeRemaining;
12105                 if (commentList[0] != NULL) {
12106                     commentList[1] = commentList[0];
12107                     commentList[0] = NULL;
12108                 }
12109             } else {
12110                 currentMove = forwardMostMove = backwardMostMove = 0;
12111             }
12112         }
12113         yyboardindex = forwardMostMove;
12114         cm = (ChessMove) Myylex();
12115     }
12116
12117     if (first.pr == NoProc) {
12118         StartChessProgram(&first);
12119     }
12120     InitChessProgram(&first, FALSE);
12121     SendToProgram("force\n", &first);
12122     if (startedFromSetupPosition) {
12123         SendBoard(&first, forwardMostMove);
12124     if (appData.debugMode) {
12125         fprintf(debugFP, "Load Game\n");
12126     }
12127         DisplayBothClocks();
12128     }
12129
12130     /* [HGM] server: flag to write setup moves in broadcast file as one */
12131     loadFlag = appData.suppressLoadMoves;
12132
12133     while (cm == Comment) {
12134         char *p;
12135         if (appData.debugMode)
12136           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12137         p = yy_text;
12138         AppendComment(currentMove, p, FALSE);
12139         yyboardindex = forwardMostMove;
12140         cm = (ChessMove) Myylex();
12141     }
12142
12143     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12144         cm == WhiteWins || cm == BlackWins ||
12145         cm == GameIsDrawn || cm == GameUnfinished) {
12146         DisplayMessage("", _("No moves in game"));
12147         if (cmailMsgLoaded) {
12148             if (appData.debugMode)
12149               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12150             ClearHighlights();
12151             flipView = FALSE;
12152         }
12153         DrawPosition(FALSE, boards[currentMove]);
12154         DisplayBothClocks();
12155         gameMode = EditGame;
12156         ModeHighlight();
12157         gameFileFP = NULL;
12158         cmailOldMove = 0;
12159         return TRUE;
12160     }
12161
12162     // [HGM] PV info: routine tests if comment empty
12163     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12164         DisplayComment(currentMove - 1, commentList[currentMove]);
12165     }
12166     if (!matchMode && appData.timeDelay != 0)
12167       DrawPosition(FALSE, boards[currentMove]);
12168
12169     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12170       programStats.ok_to_send = 1;
12171     }
12172
12173     /* if the first token after the PGN tags is a move
12174      * and not move number 1, retrieve it from the parser
12175      */
12176     if (cm != MoveNumberOne)
12177         LoadGameOneMove(cm);
12178
12179     /* load the remaining moves from the file */
12180     while (LoadGameOneMove(EndOfFile)) {
12181       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12182       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12183     }
12184
12185     /* rewind to the start of the game */
12186     currentMove = backwardMostMove;
12187
12188     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12189
12190     if (oldGameMode == AnalyzeFile ||
12191         oldGameMode == AnalyzeMode) {
12192       AnalyzeFileEvent();
12193     }
12194
12195     if (!matchMode && pos > 0) {
12196         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12197     } else
12198     if (matchMode || appData.timeDelay == 0) {
12199       ToEndEvent();
12200     } else if (appData.timeDelay > 0) {
12201       AutoPlayGameLoop();
12202     }
12203
12204     if (appData.debugMode)
12205         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12206
12207     loadFlag = 0; /* [HGM] true game starts */
12208     return TRUE;
12209 }
12210
12211 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12212 int
12213 ReloadPosition (int offset)
12214 {
12215     int positionNumber = lastLoadPositionNumber + offset;
12216     if (lastLoadPositionFP == NULL) {
12217         DisplayError(_("No position has been loaded yet"), 0);
12218         return FALSE;
12219     }
12220     if (positionNumber <= 0) {
12221         DisplayError(_("Can't back up any further"), 0);
12222         return FALSE;
12223     }
12224     return LoadPosition(lastLoadPositionFP, positionNumber,
12225                         lastLoadPositionTitle);
12226 }
12227
12228 /* Load the nth position from the given file */
12229 int
12230 LoadPositionFromFile (char *filename, int n, char *title)
12231 {
12232     FILE *f;
12233     char buf[MSG_SIZ];
12234
12235     if (strcmp(filename, "-") == 0) {
12236         return LoadPosition(stdin, n, "stdin");
12237     } else {
12238         f = fopen(filename, "rb");
12239         if (f == NULL) {
12240             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12241             DisplayError(buf, errno);
12242             return FALSE;
12243         } else {
12244             return LoadPosition(f, n, title);
12245         }
12246     }
12247 }
12248
12249 /* Load the nth position from the given open file, and close it */
12250 int
12251 LoadPosition (FILE *f, int positionNumber, char *title)
12252 {
12253     char *p, line[MSG_SIZ];
12254     Board initial_position;
12255     int i, j, fenMode, pn;
12256
12257     if (gameMode == Training )
12258         SetTrainingModeOff();
12259
12260     if (gameMode != BeginningOfGame) {
12261         Reset(FALSE, TRUE);
12262     }
12263     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12264         fclose(lastLoadPositionFP);
12265     }
12266     if (positionNumber == 0) positionNumber = 1;
12267     lastLoadPositionFP = f;
12268     lastLoadPositionNumber = positionNumber;
12269     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12270     if (first.pr == NoProc && !appData.noChessProgram) {
12271       StartChessProgram(&first);
12272       InitChessProgram(&first, FALSE);
12273     }
12274     pn = positionNumber;
12275     if (positionNumber < 0) {
12276         /* Negative position number means to seek to that byte offset */
12277         if (fseek(f, -positionNumber, 0) == -1) {
12278             DisplayError(_("Can't seek on position file"), 0);
12279             return FALSE;
12280         };
12281         pn = 1;
12282     } else {
12283         if (fseek(f, 0, 0) == -1) {
12284             if (f == lastLoadPositionFP ?
12285                 positionNumber == lastLoadPositionNumber + 1 :
12286                 positionNumber == 1) {
12287                 pn = 1;
12288             } else {
12289                 DisplayError(_("Can't seek on position file"), 0);
12290                 return FALSE;
12291             }
12292         }
12293     }
12294     /* See if this file is FEN or old-style xboard */
12295     if (fgets(line, MSG_SIZ, f) == NULL) {
12296         DisplayError(_("Position not found in file"), 0);
12297         return FALSE;
12298     }
12299     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12300     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12301
12302     if (pn >= 2) {
12303         if (fenMode || line[0] == '#') pn--;
12304         while (pn > 0) {
12305             /* skip positions before number pn */
12306             if (fgets(line, MSG_SIZ, f) == NULL) {
12307                 Reset(TRUE, TRUE);
12308                 DisplayError(_("Position not found in file"), 0);
12309                 return FALSE;
12310             }
12311             if (fenMode || line[0] == '#') pn--;
12312         }
12313     }
12314
12315     if (fenMode) {
12316         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12317             DisplayError(_("Bad FEN position in file"), 0);
12318             return FALSE;
12319         }
12320     } else {
12321         (void) fgets(line, MSG_SIZ, f);
12322         (void) fgets(line, MSG_SIZ, f);
12323
12324         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12325             (void) fgets(line, MSG_SIZ, f);
12326             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12327                 if (*p == ' ')
12328                   continue;
12329                 initial_position[i][j++] = CharToPiece(*p);
12330             }
12331         }
12332
12333         blackPlaysFirst = FALSE;
12334         if (!feof(f)) {
12335             (void) fgets(line, MSG_SIZ, f);
12336             if (strncmp(line, "black", strlen("black"))==0)
12337               blackPlaysFirst = TRUE;
12338         }
12339     }
12340     startedFromSetupPosition = TRUE;
12341
12342     CopyBoard(boards[0], initial_position);
12343     if (blackPlaysFirst) {
12344         currentMove = forwardMostMove = backwardMostMove = 1;
12345         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12346         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12347         CopyBoard(boards[1], initial_position);
12348         DisplayMessage("", _("Black to play"));
12349     } else {
12350         currentMove = forwardMostMove = backwardMostMove = 0;
12351         DisplayMessage("", _("White to play"));
12352     }
12353     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12354     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12355         SendToProgram("force\n", &first);
12356         SendBoard(&first, forwardMostMove);
12357     }
12358     if (appData.debugMode) {
12359 int i, j;
12360   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12361   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12362         fprintf(debugFP, "Load Position\n");
12363     }
12364
12365     if (positionNumber > 1) {
12366       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12367         DisplayTitle(line);
12368     } else {
12369         DisplayTitle(title);
12370     }
12371     gameMode = EditGame;
12372     ModeHighlight();
12373     ResetClocks();
12374     timeRemaining[0][1] = whiteTimeRemaining;
12375     timeRemaining[1][1] = blackTimeRemaining;
12376     DrawPosition(FALSE, boards[currentMove]);
12377
12378     return TRUE;
12379 }
12380
12381
12382 void
12383 CopyPlayerNameIntoFileName (char **dest, char *src)
12384 {
12385     while (*src != NULLCHAR && *src != ',') {
12386         if (*src == ' ') {
12387             *(*dest)++ = '_';
12388             src++;
12389         } else {
12390             *(*dest)++ = *src++;
12391         }
12392     }
12393 }
12394
12395 char *
12396 DefaultFileName (char *ext)
12397 {
12398     static char def[MSG_SIZ];
12399     char *p;
12400
12401     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12402         p = def;
12403         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12404         *p++ = '-';
12405         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12406         *p++ = '.';
12407         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12408     } else {
12409         def[0] = NULLCHAR;
12410     }
12411     return def;
12412 }
12413
12414 /* Save the current game to the given file */
12415 int
12416 SaveGameToFile (char *filename, int append)
12417 {
12418     FILE *f;
12419     char buf[MSG_SIZ];
12420     int result, i, t,tot=0;
12421
12422     if (strcmp(filename, "-") == 0) {
12423         return SaveGame(stdout, 0, NULL);
12424     } else {
12425         for(i=0; i<10; i++) { // upto 10 tries
12426              f = fopen(filename, append ? "a" : "w");
12427              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12428              if(f || errno != 13) break;
12429              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12430              tot += t;
12431         }
12432         if (f == NULL) {
12433             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12434             DisplayError(buf, errno);
12435             return FALSE;
12436         } else {
12437             safeStrCpy(buf, lastMsg, MSG_SIZ);
12438             DisplayMessage(_("Waiting for access to save file"), "");
12439             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12440             DisplayMessage(_("Saving game"), "");
12441             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12442             result = SaveGame(f, 0, NULL);
12443             DisplayMessage(buf, "");
12444             return result;
12445         }
12446     }
12447 }
12448
12449 char *
12450 SavePart (char *str)
12451 {
12452     static char buf[MSG_SIZ];
12453     char *p;
12454
12455     p = strchr(str, ' ');
12456     if (p == NULL) return str;
12457     strncpy(buf, str, p - str);
12458     buf[p - str] = NULLCHAR;
12459     return buf;
12460 }
12461
12462 #define PGN_MAX_LINE 75
12463
12464 #define PGN_SIDE_WHITE  0
12465 #define PGN_SIDE_BLACK  1
12466
12467 static int
12468 FindFirstMoveOutOfBook (int side)
12469 {
12470     int result = -1;
12471
12472     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12473         int index = backwardMostMove;
12474         int has_book_hit = 0;
12475
12476         if( (index % 2) != side ) {
12477             index++;
12478         }
12479
12480         while( index < forwardMostMove ) {
12481             /* Check to see if engine is in book */
12482             int depth = pvInfoList[index].depth;
12483             int score = pvInfoList[index].score;
12484             int in_book = 0;
12485
12486             if( depth <= 2 ) {
12487                 in_book = 1;
12488             }
12489             else if( score == 0 && depth == 63 ) {
12490                 in_book = 1; /* Zappa */
12491             }
12492             else if( score == 2 && depth == 99 ) {
12493                 in_book = 1; /* Abrok */
12494             }
12495
12496             has_book_hit += in_book;
12497
12498             if( ! in_book ) {
12499                 result = index;
12500
12501                 break;
12502             }
12503
12504             index += 2;
12505         }
12506     }
12507
12508     return result;
12509 }
12510
12511 void
12512 GetOutOfBookInfo (char * buf)
12513 {
12514     int oob[2];
12515     int i;
12516     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12517
12518     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12519     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12520
12521     *buf = '\0';
12522
12523     if( oob[0] >= 0 || oob[1] >= 0 ) {
12524         for( i=0; i<2; i++ ) {
12525             int idx = oob[i];
12526
12527             if( idx >= 0 ) {
12528                 if( i > 0 && oob[0] >= 0 ) {
12529                     strcat( buf, "   " );
12530                 }
12531
12532                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12533                 sprintf( buf+strlen(buf), "%s%.2f",
12534                     pvInfoList[idx].score >= 0 ? "+" : "",
12535                     pvInfoList[idx].score / 100.0 );
12536             }
12537         }
12538     }
12539 }
12540
12541 /* Save game in PGN style and close the file */
12542 int
12543 SaveGamePGN (FILE *f)
12544 {
12545     int i, offset, linelen, newblock;
12546 //    char *movetext;
12547     char numtext[32];
12548     int movelen, numlen, blank;
12549     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12550
12551     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12552
12553     PrintPGNTags(f, &gameInfo);
12554
12555     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12556
12557     if (backwardMostMove > 0 || startedFromSetupPosition) {
12558         char *fen = PositionToFEN(backwardMostMove, NULL);
12559         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12560         fprintf(f, "\n{--------------\n");
12561         PrintPosition(f, backwardMostMove);
12562         fprintf(f, "--------------}\n");
12563         free(fen);
12564     }
12565     else {
12566         /* [AS] Out of book annotation */
12567         if( appData.saveOutOfBookInfo ) {
12568             char buf[64];
12569
12570             GetOutOfBookInfo( buf );
12571
12572             if( buf[0] != '\0' ) {
12573                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12574             }
12575         }
12576
12577         fprintf(f, "\n");
12578     }
12579
12580     i = backwardMostMove;
12581     linelen = 0;
12582     newblock = TRUE;
12583
12584     while (i < forwardMostMove) {
12585         /* Print comments preceding this move */
12586         if (commentList[i] != NULL) {
12587             if (linelen > 0) fprintf(f, "\n");
12588             fprintf(f, "%s", commentList[i]);
12589             linelen = 0;
12590             newblock = TRUE;
12591         }
12592
12593         /* Format move number */
12594         if ((i % 2) == 0)
12595           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12596         else
12597           if (newblock)
12598             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12599           else
12600             numtext[0] = NULLCHAR;
12601
12602         numlen = strlen(numtext);
12603         newblock = FALSE;
12604
12605         /* Print move number */
12606         blank = linelen > 0 && numlen > 0;
12607         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12608             fprintf(f, "\n");
12609             linelen = 0;
12610             blank = 0;
12611         }
12612         if (blank) {
12613             fprintf(f, " ");
12614             linelen++;
12615         }
12616         fprintf(f, "%s", numtext);
12617         linelen += numlen;
12618
12619         /* Get move */
12620         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12621         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12622
12623         /* Print move */
12624         blank = linelen > 0 && movelen > 0;
12625         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12626             fprintf(f, "\n");
12627             linelen = 0;
12628             blank = 0;
12629         }
12630         if (blank) {
12631             fprintf(f, " ");
12632             linelen++;
12633         }
12634         fprintf(f, "%s", move_buffer);
12635         linelen += movelen;
12636
12637         /* [AS] Add PV info if present */
12638         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12639             /* [HGM] add time */
12640             char buf[MSG_SIZ]; int seconds;
12641
12642             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12643
12644             if( seconds <= 0)
12645               buf[0] = 0;
12646             else
12647               if( seconds < 30 )
12648                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12649               else
12650                 {
12651                   seconds = (seconds + 4)/10; // round to full seconds
12652                   if( seconds < 60 )
12653                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12654                   else
12655                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12656                 }
12657
12658             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12659                       pvInfoList[i].score >= 0 ? "+" : "",
12660                       pvInfoList[i].score / 100.0,
12661                       pvInfoList[i].depth,
12662                       buf );
12663
12664             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12665
12666             /* Print score/depth */
12667             blank = linelen > 0 && movelen > 0;
12668             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12669                 fprintf(f, "\n");
12670                 linelen = 0;
12671                 blank = 0;
12672             }
12673             if (blank) {
12674                 fprintf(f, " ");
12675                 linelen++;
12676             }
12677             fprintf(f, "%s", move_buffer);
12678             linelen += movelen;
12679         }
12680
12681         i++;
12682     }
12683
12684     /* Start a new line */
12685     if (linelen > 0) fprintf(f, "\n");
12686
12687     /* Print comments after last move */
12688     if (commentList[i] != NULL) {
12689         fprintf(f, "%s\n", commentList[i]);
12690     }
12691
12692     /* Print result */
12693     if (gameInfo.resultDetails != NULL &&
12694         gameInfo.resultDetails[0] != NULLCHAR) {
12695         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12696                 PGNResult(gameInfo.result));
12697     } else {
12698         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12699     }
12700
12701     fclose(f);
12702     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12703     return TRUE;
12704 }
12705
12706 /* Save game in old style and close the file */
12707 int
12708 SaveGameOldStyle (FILE *f)
12709 {
12710     int i, offset;
12711     time_t tm;
12712
12713     tm = time((time_t *) NULL);
12714
12715     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12716     PrintOpponents(f);
12717
12718     if (backwardMostMove > 0 || startedFromSetupPosition) {
12719         fprintf(f, "\n[--------------\n");
12720         PrintPosition(f, backwardMostMove);
12721         fprintf(f, "--------------]\n");
12722     } else {
12723         fprintf(f, "\n");
12724     }
12725
12726     i = backwardMostMove;
12727     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12728
12729     while (i < forwardMostMove) {
12730         if (commentList[i] != NULL) {
12731             fprintf(f, "[%s]\n", commentList[i]);
12732         }
12733
12734         if ((i % 2) == 1) {
12735             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12736             i++;
12737         } else {
12738             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12739             i++;
12740             if (commentList[i] != NULL) {
12741                 fprintf(f, "\n");
12742                 continue;
12743             }
12744             if (i >= forwardMostMove) {
12745                 fprintf(f, "\n");
12746                 break;
12747             }
12748             fprintf(f, "%s\n", parseList[i]);
12749             i++;
12750         }
12751     }
12752
12753     if (commentList[i] != NULL) {
12754         fprintf(f, "[%s]\n", commentList[i]);
12755     }
12756
12757     /* This isn't really the old style, but it's close enough */
12758     if (gameInfo.resultDetails != NULL &&
12759         gameInfo.resultDetails[0] != NULLCHAR) {
12760         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12761                 gameInfo.resultDetails);
12762     } else {
12763         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12764     }
12765
12766     fclose(f);
12767     return TRUE;
12768 }
12769
12770 /* Save the current game to open file f and close the file */
12771 int
12772 SaveGame (FILE *f, int dummy, char *dummy2)
12773 {
12774     if (gameMode == EditPosition) EditPositionDone(TRUE);
12775     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12776     if (appData.oldSaveStyle)
12777       return SaveGameOldStyle(f);
12778     else
12779       return SaveGamePGN(f);
12780 }
12781
12782 /* Save the current position to the given file */
12783 int
12784 SavePositionToFile (char *filename)
12785 {
12786     FILE *f;
12787     char buf[MSG_SIZ];
12788
12789     if (strcmp(filename, "-") == 0) {
12790         return SavePosition(stdout, 0, NULL);
12791     } else {
12792         f = fopen(filename, "a");
12793         if (f == NULL) {
12794             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12795             DisplayError(buf, errno);
12796             return FALSE;
12797         } else {
12798             safeStrCpy(buf, lastMsg, MSG_SIZ);
12799             DisplayMessage(_("Waiting for access to save file"), "");
12800             flock(fileno(f), LOCK_EX); // [HGM] lock
12801             DisplayMessage(_("Saving position"), "");
12802             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12803             SavePosition(f, 0, NULL);
12804             DisplayMessage(buf, "");
12805             return TRUE;
12806         }
12807     }
12808 }
12809
12810 /* Save the current position to the given open file and close the file */
12811 int
12812 SavePosition (FILE *f, int dummy, char *dummy2)
12813 {
12814     time_t tm;
12815     char *fen;
12816
12817     if (gameMode == EditPosition) EditPositionDone(TRUE);
12818     if (appData.oldSaveStyle) {
12819         tm = time((time_t *) NULL);
12820
12821         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12822         PrintOpponents(f);
12823         fprintf(f, "[--------------\n");
12824         PrintPosition(f, currentMove);
12825         fprintf(f, "--------------]\n");
12826     } else {
12827         fen = PositionToFEN(currentMove, NULL);
12828         fprintf(f, "%s\n", fen);
12829         free(fen);
12830     }
12831     fclose(f);
12832     return TRUE;
12833 }
12834
12835 void
12836 ReloadCmailMsgEvent (int unregister)
12837 {
12838 #if !WIN32
12839     static char *inFilename = NULL;
12840     static char *outFilename;
12841     int i;
12842     struct stat inbuf, outbuf;
12843     int status;
12844
12845     /* Any registered moves are unregistered if unregister is set, */
12846     /* i.e. invoked by the signal handler */
12847     if (unregister) {
12848         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12849             cmailMoveRegistered[i] = FALSE;
12850             if (cmailCommentList[i] != NULL) {
12851                 free(cmailCommentList[i]);
12852                 cmailCommentList[i] = NULL;
12853             }
12854         }
12855         nCmailMovesRegistered = 0;
12856     }
12857
12858     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12859         cmailResult[i] = CMAIL_NOT_RESULT;
12860     }
12861     nCmailResults = 0;
12862
12863     if (inFilename == NULL) {
12864         /* Because the filenames are static they only get malloced once  */
12865         /* and they never get freed                                      */
12866         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12867         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12868
12869         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12870         sprintf(outFilename, "%s.out", appData.cmailGameName);
12871     }
12872
12873     status = stat(outFilename, &outbuf);
12874     if (status < 0) {
12875         cmailMailedMove = FALSE;
12876     } else {
12877         status = stat(inFilename, &inbuf);
12878         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12879     }
12880
12881     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12882        counts the games, notes how each one terminated, etc.
12883
12884        It would be nice to remove this kludge and instead gather all
12885        the information while building the game list.  (And to keep it
12886        in the game list nodes instead of having a bunch of fixed-size
12887        parallel arrays.)  Note this will require getting each game's
12888        termination from the PGN tags, as the game list builder does
12889        not process the game moves.  --mann
12890        */
12891     cmailMsgLoaded = TRUE;
12892     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12893
12894     /* Load first game in the file or popup game menu */
12895     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12896
12897 #endif /* !WIN32 */
12898     return;
12899 }
12900
12901 int
12902 RegisterMove ()
12903 {
12904     FILE *f;
12905     char string[MSG_SIZ];
12906
12907     if (   cmailMailedMove
12908         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12909         return TRUE;            /* Allow free viewing  */
12910     }
12911
12912     /* Unregister move to ensure that we don't leave RegisterMove        */
12913     /* with the move registered when the conditions for registering no   */
12914     /* longer hold                                                       */
12915     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12916         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12917         nCmailMovesRegistered --;
12918
12919         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12920           {
12921               free(cmailCommentList[lastLoadGameNumber - 1]);
12922               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12923           }
12924     }
12925
12926     if (cmailOldMove == -1) {
12927         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12928         return FALSE;
12929     }
12930
12931     if (currentMove > cmailOldMove + 1) {
12932         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12933         return FALSE;
12934     }
12935
12936     if (currentMove < cmailOldMove) {
12937         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12938         return FALSE;
12939     }
12940
12941     if (forwardMostMove > currentMove) {
12942         /* Silently truncate extra moves */
12943         TruncateGame();
12944     }
12945
12946     if (   (currentMove == cmailOldMove + 1)
12947         || (   (currentMove == cmailOldMove)
12948             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12949                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12950         if (gameInfo.result != GameUnfinished) {
12951             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12952         }
12953
12954         if (commentList[currentMove] != NULL) {
12955             cmailCommentList[lastLoadGameNumber - 1]
12956               = StrSave(commentList[currentMove]);
12957         }
12958         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12959
12960         if (appData.debugMode)
12961           fprintf(debugFP, "Saving %s for game %d\n",
12962                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12963
12964         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12965
12966         f = fopen(string, "w");
12967         if (appData.oldSaveStyle) {
12968             SaveGameOldStyle(f); /* also closes the file */
12969
12970             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12971             f = fopen(string, "w");
12972             SavePosition(f, 0, NULL); /* also closes the file */
12973         } else {
12974             fprintf(f, "{--------------\n");
12975             PrintPosition(f, currentMove);
12976             fprintf(f, "--------------}\n\n");
12977
12978             SaveGame(f, 0, NULL); /* also closes the file*/
12979         }
12980
12981         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12982         nCmailMovesRegistered ++;
12983     } else if (nCmailGames == 1) {
12984         DisplayError(_("You have not made a move yet"), 0);
12985         return FALSE;
12986     }
12987
12988     return TRUE;
12989 }
12990
12991 void
12992 MailMoveEvent ()
12993 {
12994 #if !WIN32
12995     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12996     FILE *commandOutput;
12997     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12998     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12999     int nBuffers;
13000     int i;
13001     int archived;
13002     char *arcDir;
13003
13004     if (! cmailMsgLoaded) {
13005         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13006         return;
13007     }
13008
13009     if (nCmailGames == nCmailResults) {
13010         DisplayError(_("No unfinished games"), 0);
13011         return;
13012     }
13013
13014 #if CMAIL_PROHIBIT_REMAIL
13015     if (cmailMailedMove) {
13016       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);
13017         DisplayError(msg, 0);
13018         return;
13019     }
13020 #endif
13021
13022     if (! (cmailMailedMove || RegisterMove())) return;
13023
13024     if (   cmailMailedMove
13025         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13026       snprintf(string, MSG_SIZ, partCommandString,
13027                appData.debugMode ? " -v" : "", appData.cmailGameName);
13028         commandOutput = popen(string, "r");
13029
13030         if (commandOutput == NULL) {
13031             DisplayError(_("Failed to invoke cmail"), 0);
13032         } else {
13033             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13034                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13035             }
13036             if (nBuffers > 1) {
13037                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13038                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13039                 nBytes = MSG_SIZ - 1;
13040             } else {
13041                 (void) memcpy(msg, buffer, nBytes);
13042             }
13043             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13044
13045             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13046                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13047
13048                 archived = TRUE;
13049                 for (i = 0; i < nCmailGames; i ++) {
13050                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13051                         archived = FALSE;
13052                     }
13053                 }
13054                 if (   archived
13055                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13056                         != NULL)) {
13057                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13058                            arcDir,
13059                            appData.cmailGameName,
13060                            gameInfo.date);
13061                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13062                     cmailMsgLoaded = FALSE;
13063                 }
13064             }
13065
13066             DisplayInformation(msg);
13067             pclose(commandOutput);
13068         }
13069     } else {
13070         if ((*cmailMsg) != '\0') {
13071             DisplayInformation(cmailMsg);
13072         }
13073     }
13074
13075     return;
13076 #endif /* !WIN32 */
13077 }
13078
13079 char *
13080 CmailMsg ()
13081 {
13082 #if WIN32
13083     return NULL;
13084 #else
13085     int  prependComma = 0;
13086     char number[5];
13087     char string[MSG_SIZ];       /* Space for game-list */
13088     int  i;
13089
13090     if (!cmailMsgLoaded) return "";
13091
13092     if (cmailMailedMove) {
13093       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13094     } else {
13095         /* Create a list of games left */
13096       snprintf(string, MSG_SIZ, "[");
13097         for (i = 0; i < nCmailGames; i ++) {
13098             if (! (   cmailMoveRegistered[i]
13099                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13100                 if (prependComma) {
13101                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13102                 } else {
13103                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13104                     prependComma = 1;
13105                 }
13106
13107                 strcat(string, number);
13108             }
13109         }
13110         strcat(string, "]");
13111
13112         if (nCmailMovesRegistered + nCmailResults == 0) {
13113             switch (nCmailGames) {
13114               case 1:
13115                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13116                 break;
13117
13118               case 2:
13119                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13120                 break;
13121
13122               default:
13123                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13124                          nCmailGames);
13125                 break;
13126             }
13127         } else {
13128             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13129               case 1:
13130                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13131                          string);
13132                 break;
13133
13134               case 0:
13135                 if (nCmailResults == nCmailGames) {
13136                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13137                 } else {
13138                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13139                 }
13140                 break;
13141
13142               default:
13143                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13144                          string);
13145             }
13146         }
13147     }
13148     return cmailMsg;
13149 #endif /* WIN32 */
13150 }
13151
13152 void
13153 ResetGameEvent ()
13154 {
13155     if (gameMode == Training)
13156       SetTrainingModeOff();
13157
13158     Reset(TRUE, TRUE);
13159     cmailMsgLoaded = FALSE;
13160     if (appData.icsActive) {
13161       SendToICS(ics_prefix);
13162       SendToICS("refresh\n");
13163     }
13164 }
13165
13166 void
13167 ExitEvent (int status)
13168 {
13169     exiting++;
13170     if (exiting > 2) {
13171       /* Give up on clean exit */
13172       exit(status);
13173     }
13174     if (exiting > 1) {
13175       /* Keep trying for clean exit */
13176       return;
13177     }
13178
13179     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13180
13181     if (telnetISR != NULL) {
13182       RemoveInputSource(telnetISR);
13183     }
13184     if (icsPR != NoProc) {
13185       DestroyChildProcess(icsPR, TRUE);
13186     }
13187
13188     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13189     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13190
13191     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13192     /* make sure this other one finishes before killing it!                  */
13193     if(endingGame) { int count = 0;
13194         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13195         while(endingGame && count++ < 10) DoSleep(1);
13196         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13197     }
13198
13199     /* Kill off chess programs */
13200     if (first.pr != NoProc) {
13201         ExitAnalyzeMode();
13202
13203         DoSleep( appData.delayBeforeQuit );
13204         SendToProgram("quit\n", &first);
13205         DoSleep( appData.delayAfterQuit );
13206         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13207     }
13208     if (second.pr != NoProc) {
13209         DoSleep( appData.delayBeforeQuit );
13210         SendToProgram("quit\n", &second);
13211         DoSleep( appData.delayAfterQuit );
13212         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13213     }
13214     if (first.isr != NULL) {
13215         RemoveInputSource(first.isr);
13216     }
13217     if (second.isr != NULL) {
13218         RemoveInputSource(second.isr);
13219     }
13220
13221     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13222     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13223
13224     ShutDownFrontEnd();
13225     exit(status);
13226 }
13227
13228 void
13229 PauseEvent ()
13230 {
13231     if (appData.debugMode)
13232         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13233     if (pausing) {
13234         pausing = FALSE;
13235         ModeHighlight();
13236         if (gameMode == MachinePlaysWhite ||
13237             gameMode == MachinePlaysBlack) {
13238             StartClocks();
13239         } else {
13240             DisplayBothClocks();
13241         }
13242         if (gameMode == PlayFromGameFile) {
13243             if (appData.timeDelay >= 0)
13244                 AutoPlayGameLoop();
13245         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13246             Reset(FALSE, TRUE);
13247             SendToICS(ics_prefix);
13248             SendToICS("refresh\n");
13249         } else if (currentMove < forwardMostMove) {
13250             ForwardInner(forwardMostMove);
13251         }
13252         pauseExamInvalid = FALSE;
13253     } else {
13254         switch (gameMode) {
13255           default:
13256             return;
13257           case IcsExamining:
13258             pauseExamForwardMostMove = forwardMostMove;
13259             pauseExamInvalid = FALSE;
13260             /* fall through */
13261           case IcsObserving:
13262           case IcsPlayingWhite:
13263           case IcsPlayingBlack:
13264             pausing = TRUE;
13265             ModeHighlight();
13266             return;
13267           case PlayFromGameFile:
13268             (void) StopLoadGameTimer();
13269             pausing = TRUE;
13270             ModeHighlight();
13271             break;
13272           case BeginningOfGame:
13273             if (appData.icsActive) return;
13274             /* else fall through */
13275           case MachinePlaysWhite:
13276           case MachinePlaysBlack:
13277           case TwoMachinesPlay:
13278             if (forwardMostMove == 0)
13279               return;           /* don't pause if no one has moved */
13280             if ((gameMode == MachinePlaysWhite &&
13281                  !WhiteOnMove(forwardMostMove)) ||
13282                 (gameMode == MachinePlaysBlack &&
13283                  WhiteOnMove(forwardMostMove))) {
13284                 StopClocks();
13285             }
13286             pausing = TRUE;
13287             ModeHighlight();
13288             break;
13289         }
13290     }
13291 }
13292
13293 void
13294 EditCommentEvent ()
13295 {
13296     char title[MSG_SIZ];
13297
13298     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13299       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13300     } else {
13301       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13302                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13303                parseList[currentMove - 1]);
13304     }
13305
13306     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13307 }
13308
13309
13310 void
13311 EditTagsEvent ()
13312 {
13313     char *tags = PGNTags(&gameInfo);
13314     bookUp = FALSE;
13315     EditTagsPopUp(tags, NULL);
13316     free(tags);
13317 }
13318
13319 void
13320 AnalyzeModeEvent ()
13321 {
13322     if (appData.noChessProgram || gameMode == AnalyzeMode)
13323       return;
13324
13325     if (gameMode != AnalyzeFile) {
13326         if (!appData.icsEngineAnalyze) {
13327                EditGameEvent();
13328                if (gameMode != EditGame) return;
13329         }
13330         ResurrectChessProgram();
13331         SendToProgram("analyze\n", &first);
13332         first.analyzing = TRUE;
13333         /*first.maybeThinking = TRUE;*/
13334         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13335         EngineOutputPopUp();
13336     }
13337     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13338     pausing = FALSE;
13339     ModeHighlight();
13340     SetGameInfo();
13341
13342     StartAnalysisClock();
13343     GetTimeMark(&lastNodeCountTime);
13344     lastNodeCount = 0;
13345 }
13346
13347 void
13348 AnalyzeFileEvent ()
13349 {
13350     if (appData.noChessProgram || gameMode == AnalyzeFile)
13351       return;
13352
13353     if (gameMode != AnalyzeMode) {
13354         EditGameEvent();
13355         if (gameMode != EditGame) return;
13356         ResurrectChessProgram();
13357         SendToProgram("analyze\n", &first);
13358         first.analyzing = TRUE;
13359         /*first.maybeThinking = TRUE;*/
13360         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13361         EngineOutputPopUp();
13362     }
13363     gameMode = AnalyzeFile;
13364     pausing = FALSE;
13365     ModeHighlight();
13366     SetGameInfo();
13367
13368     StartAnalysisClock();
13369     GetTimeMark(&lastNodeCountTime);
13370     lastNodeCount = 0;
13371     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13372 }
13373
13374 void
13375 MachineWhiteEvent ()
13376 {
13377     char buf[MSG_SIZ];
13378     char *bookHit = NULL;
13379
13380     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13381       return;
13382
13383
13384     if (gameMode == PlayFromGameFile ||
13385         gameMode == TwoMachinesPlay  ||
13386         gameMode == Training         ||
13387         gameMode == AnalyzeMode      ||
13388         gameMode == EndOfGame)
13389         EditGameEvent();
13390
13391     if (gameMode == EditPosition)
13392         EditPositionDone(TRUE);
13393
13394     if (!WhiteOnMove(currentMove)) {
13395         DisplayError(_("It is not White's turn"), 0);
13396         return;
13397     }
13398
13399     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13400       ExitAnalyzeMode();
13401
13402     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13403         gameMode == AnalyzeFile)
13404         TruncateGame();
13405
13406     ResurrectChessProgram();    /* in case it isn't running */
13407     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13408         gameMode = MachinePlaysWhite;
13409         ResetClocks();
13410     } else
13411     gameMode = MachinePlaysWhite;
13412     pausing = FALSE;
13413     ModeHighlight();
13414     SetGameInfo();
13415     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13416     DisplayTitle(buf);
13417     if (first.sendName) {
13418       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13419       SendToProgram(buf, &first);
13420     }
13421     if (first.sendTime) {
13422       if (first.useColors) {
13423         SendToProgram("black\n", &first); /*gnu kludge*/
13424       }
13425       SendTimeRemaining(&first, TRUE);
13426     }
13427     if (first.useColors) {
13428       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13429     }
13430     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13431     SetMachineThinkingEnables();
13432     first.maybeThinking = TRUE;
13433     StartClocks();
13434     firstMove = FALSE;
13435
13436     if (appData.autoFlipView && !flipView) {
13437       flipView = !flipView;
13438       DrawPosition(FALSE, NULL);
13439       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13440     }
13441
13442     if(bookHit) { // [HGM] book: simulate book reply
13443         static char bookMove[MSG_SIZ]; // a bit generous?
13444
13445         programStats.nodes = programStats.depth = programStats.time =
13446         programStats.score = programStats.got_only_move = 0;
13447         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13448
13449         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13450         strcat(bookMove, bookHit);
13451         HandleMachineMove(bookMove, &first);
13452     }
13453 }
13454
13455 void
13456 MachineBlackEvent ()
13457 {
13458   char buf[MSG_SIZ];
13459   char *bookHit = NULL;
13460
13461     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13462         return;
13463
13464
13465     if (gameMode == PlayFromGameFile ||
13466         gameMode == TwoMachinesPlay  ||
13467         gameMode == Training         ||
13468         gameMode == AnalyzeMode      ||
13469         gameMode == EndOfGame)
13470         EditGameEvent();
13471
13472     if (gameMode == EditPosition)
13473         EditPositionDone(TRUE);
13474
13475     if (WhiteOnMove(currentMove)) {
13476         DisplayError(_("It is not Black's turn"), 0);
13477         return;
13478     }
13479
13480     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13481       ExitAnalyzeMode();
13482
13483     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13484         gameMode == AnalyzeFile)
13485         TruncateGame();
13486
13487     ResurrectChessProgram();    /* in case it isn't running */
13488     gameMode = MachinePlaysBlack;
13489     pausing = FALSE;
13490     ModeHighlight();
13491     SetGameInfo();
13492     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13493     DisplayTitle(buf);
13494     if (first.sendName) {
13495       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13496       SendToProgram(buf, &first);
13497     }
13498     if (first.sendTime) {
13499       if (first.useColors) {
13500         SendToProgram("white\n", &first); /*gnu kludge*/
13501       }
13502       SendTimeRemaining(&first, FALSE);
13503     }
13504     if (first.useColors) {
13505       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13506     }
13507     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13508     SetMachineThinkingEnables();
13509     first.maybeThinking = TRUE;
13510     StartClocks();
13511
13512     if (appData.autoFlipView && flipView) {
13513       flipView = !flipView;
13514       DrawPosition(FALSE, NULL);
13515       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13516     }
13517     if(bookHit) { // [HGM] book: simulate book reply
13518         static char bookMove[MSG_SIZ]; // a bit generous?
13519
13520         programStats.nodes = programStats.depth = programStats.time =
13521         programStats.score = programStats.got_only_move = 0;
13522         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13523
13524         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13525         strcat(bookMove, bookHit);
13526         HandleMachineMove(bookMove, &first);
13527     }
13528 }
13529
13530
13531 void
13532 DisplayTwoMachinesTitle ()
13533 {
13534     char buf[MSG_SIZ];
13535     if (appData.matchGames > 0) {
13536         if(appData.tourneyFile[0]) {
13537           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13538                    gameInfo.white, _("vs."), gameInfo.black,
13539                    nextGame+1, appData.matchGames+1,
13540                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13541         } else 
13542         if (first.twoMachinesColor[0] == 'w') {
13543           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13544                    gameInfo.white, _("vs."),  gameInfo.black,
13545                    first.matchWins, second.matchWins,
13546                    matchGame - 1 - (first.matchWins + second.matchWins));
13547         } else {
13548           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13549                    gameInfo.white, _("vs."), gameInfo.black,
13550                    second.matchWins, first.matchWins,
13551                    matchGame - 1 - (first.matchWins + second.matchWins));
13552         }
13553     } else {
13554       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13555     }
13556     DisplayTitle(buf);
13557 }
13558
13559 void
13560 SettingsMenuIfReady ()
13561 {
13562   if (second.lastPing != second.lastPong) {
13563     DisplayMessage("", _("Waiting for second chess program"));
13564     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13565     return;
13566   }
13567   ThawUI();
13568   DisplayMessage("", "");
13569   SettingsPopUp(&second);
13570 }
13571
13572 int
13573 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13574 {
13575     char buf[MSG_SIZ];
13576     if (cps->pr == NoProc) {
13577         StartChessProgram(cps);
13578         if (cps->protocolVersion == 1) {
13579           retry();
13580         } else {
13581           /* kludge: allow timeout for initial "feature" command */
13582           FreezeUI();
13583           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13584           DisplayMessage("", buf);
13585           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13586         }
13587         return 1;
13588     }
13589     return 0;
13590 }
13591
13592 void
13593 TwoMachinesEvent P((void))
13594 {
13595     int i;
13596     char buf[MSG_SIZ];
13597     ChessProgramState *onmove;
13598     char *bookHit = NULL;
13599     static int stalling = 0;
13600     TimeMark now;
13601     long wait;
13602
13603     if (appData.noChessProgram) return;
13604
13605     switch (gameMode) {
13606       case TwoMachinesPlay:
13607         return;
13608       case MachinePlaysWhite:
13609       case MachinePlaysBlack:
13610         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13611             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13612             return;
13613         }
13614         /* fall through */
13615       case BeginningOfGame:
13616       case PlayFromGameFile:
13617       case EndOfGame:
13618         EditGameEvent();
13619         if (gameMode != EditGame) return;
13620         break;
13621       case EditPosition:
13622         EditPositionDone(TRUE);
13623         break;
13624       case AnalyzeMode:
13625       case AnalyzeFile:
13626         ExitAnalyzeMode();
13627         break;
13628       case EditGame:
13629       default:
13630         break;
13631     }
13632
13633 //    forwardMostMove = currentMove;
13634     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13635
13636     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13637
13638     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13639     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13640       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13641       return;
13642     }
13643
13644     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13645         DisplayError("second engine does not play this", 0);
13646         return;
13647     }
13648
13649     if(!stalling) {
13650       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13651       SendToProgram("force\n", &second);
13652       stalling = 1;
13653       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13654       return;
13655     }
13656     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13657     if(appData.matchPause>10000 || appData.matchPause<10)
13658                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13659     wait = SubtractTimeMarks(&now, &pauseStart);
13660     if(wait < appData.matchPause) {
13661         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13662         return;
13663     }
13664     // we are now committed to starting the game
13665     stalling = 0;
13666     DisplayMessage("", "");
13667     if (startedFromSetupPosition) {
13668         SendBoard(&second, backwardMostMove);
13669     if (appData.debugMode) {
13670         fprintf(debugFP, "Two Machines\n");
13671     }
13672     }
13673     for (i = backwardMostMove; i < forwardMostMove; i++) {
13674         SendMoveToProgram(i, &second);
13675     }
13676
13677     gameMode = TwoMachinesPlay;
13678     pausing = FALSE;
13679     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13680     SetGameInfo();
13681     DisplayTwoMachinesTitle();
13682     firstMove = TRUE;
13683     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13684         onmove = &first;
13685     } else {
13686         onmove = &second;
13687     }
13688     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13689     SendToProgram(first.computerString, &first);
13690     if (first.sendName) {
13691       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13692       SendToProgram(buf, &first);
13693     }
13694     SendToProgram(second.computerString, &second);
13695     if (second.sendName) {
13696       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13697       SendToProgram(buf, &second);
13698     }
13699
13700     ResetClocks();
13701     if (!first.sendTime || !second.sendTime) {
13702         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13703         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13704     }
13705     if (onmove->sendTime) {
13706       if (onmove->useColors) {
13707         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13708       }
13709       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13710     }
13711     if (onmove->useColors) {
13712       SendToProgram(onmove->twoMachinesColor, onmove);
13713     }
13714     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13715 //    SendToProgram("go\n", onmove);
13716     onmove->maybeThinking = TRUE;
13717     SetMachineThinkingEnables();
13718
13719     StartClocks();
13720
13721     if(bookHit) { // [HGM] book: simulate book reply
13722         static char bookMove[MSG_SIZ]; // a bit generous?
13723
13724         programStats.nodes = programStats.depth = programStats.time =
13725         programStats.score = programStats.got_only_move = 0;
13726         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13727
13728         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13729         strcat(bookMove, bookHit);
13730         savedMessage = bookMove; // args for deferred call
13731         savedState = onmove;
13732         ScheduleDelayedEvent(DeferredBookMove, 1);
13733     }
13734 }
13735
13736 void
13737 TrainingEvent ()
13738 {
13739     if (gameMode == Training) {
13740       SetTrainingModeOff();
13741       gameMode = PlayFromGameFile;
13742       DisplayMessage("", _("Training mode off"));
13743     } else {
13744       gameMode = Training;
13745       animateTraining = appData.animate;
13746
13747       /* make sure we are not already at the end of the game */
13748       if (currentMove < forwardMostMove) {
13749         SetTrainingModeOn();
13750         DisplayMessage("", _("Training mode on"));
13751       } else {
13752         gameMode = PlayFromGameFile;
13753         DisplayError(_("Already at end of game"), 0);
13754       }
13755     }
13756     ModeHighlight();
13757 }
13758
13759 void
13760 IcsClientEvent ()
13761 {
13762     if (!appData.icsActive) return;
13763     switch (gameMode) {
13764       case IcsPlayingWhite:
13765       case IcsPlayingBlack:
13766       case IcsObserving:
13767       case IcsIdle:
13768       case BeginningOfGame:
13769       case IcsExamining:
13770         return;
13771
13772       case EditGame:
13773         break;
13774
13775       case EditPosition:
13776         EditPositionDone(TRUE);
13777         break;
13778
13779       case AnalyzeMode:
13780       case AnalyzeFile:
13781         ExitAnalyzeMode();
13782         break;
13783
13784       default:
13785         EditGameEvent();
13786         break;
13787     }
13788
13789     gameMode = IcsIdle;
13790     ModeHighlight();
13791     return;
13792 }
13793
13794 void
13795 EditGameEvent ()
13796 {
13797     int i;
13798
13799     switch (gameMode) {
13800       case Training:
13801         SetTrainingModeOff();
13802         break;
13803       case MachinePlaysWhite:
13804       case MachinePlaysBlack:
13805       case BeginningOfGame:
13806         SendToProgram("force\n", &first);
13807         SetUserThinkingEnables();
13808         break;
13809       case PlayFromGameFile:
13810         (void) StopLoadGameTimer();
13811         if (gameFileFP != NULL) {
13812             gameFileFP = NULL;
13813         }
13814         break;
13815       case EditPosition:
13816         EditPositionDone(TRUE);
13817         break;
13818       case AnalyzeMode:
13819       case AnalyzeFile:
13820         ExitAnalyzeMode();
13821         SendToProgram("force\n", &first);
13822         break;
13823       case TwoMachinesPlay:
13824         GameEnds(EndOfFile, NULL, GE_PLAYER);
13825         ResurrectChessProgram();
13826         SetUserThinkingEnables();
13827         break;
13828       case EndOfGame:
13829         ResurrectChessProgram();
13830         break;
13831       case IcsPlayingBlack:
13832       case IcsPlayingWhite:
13833         DisplayError(_("Warning: You are still playing a game"), 0);
13834         break;
13835       case IcsObserving:
13836         DisplayError(_("Warning: You are still observing a game"), 0);
13837         break;
13838       case IcsExamining:
13839         DisplayError(_("Warning: You are still examining a game"), 0);
13840         break;
13841       case IcsIdle:
13842         break;
13843       case EditGame:
13844       default:
13845         return;
13846     }
13847
13848     pausing = FALSE;
13849     StopClocks();
13850     first.offeredDraw = second.offeredDraw = 0;
13851
13852     if (gameMode == PlayFromGameFile) {
13853         whiteTimeRemaining = timeRemaining[0][currentMove];
13854         blackTimeRemaining = timeRemaining[1][currentMove];
13855         DisplayTitle("");
13856     }
13857
13858     if (gameMode == MachinePlaysWhite ||
13859         gameMode == MachinePlaysBlack ||
13860         gameMode == TwoMachinesPlay ||
13861         gameMode == EndOfGame) {
13862         i = forwardMostMove;
13863         while (i > currentMove) {
13864             SendToProgram("undo\n", &first);
13865             i--;
13866         }
13867         if(!adjustedClock) {
13868         whiteTimeRemaining = timeRemaining[0][currentMove];
13869         blackTimeRemaining = timeRemaining[1][currentMove];
13870         DisplayBothClocks();
13871         }
13872         if (whiteFlag || blackFlag) {
13873             whiteFlag = blackFlag = 0;
13874         }
13875         DisplayTitle("");
13876     }
13877
13878     gameMode = EditGame;
13879     ModeHighlight();
13880     SetGameInfo();
13881 }
13882
13883
13884 void
13885 EditPositionEvent ()
13886 {
13887     if (gameMode == EditPosition) {
13888         EditGameEvent();
13889         return;
13890     }
13891
13892     EditGameEvent();
13893     if (gameMode != EditGame) return;
13894
13895     gameMode = EditPosition;
13896     ModeHighlight();
13897     SetGameInfo();
13898     if (currentMove > 0)
13899       CopyBoard(boards[0], boards[currentMove]);
13900
13901     blackPlaysFirst = !WhiteOnMove(currentMove);
13902     ResetClocks();
13903     currentMove = forwardMostMove = backwardMostMove = 0;
13904     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13905     DisplayMove(-1);
13906     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13907 }
13908
13909 void
13910 ExitAnalyzeMode ()
13911 {
13912     /* [DM] icsEngineAnalyze - possible call from other functions */
13913     if (appData.icsEngineAnalyze) {
13914         appData.icsEngineAnalyze = FALSE;
13915
13916         DisplayMessage("",_("Close ICS engine analyze..."));
13917     }
13918     if (first.analysisSupport && first.analyzing) {
13919       SendToProgram("exit\n", &first);
13920       first.analyzing = FALSE;
13921     }
13922     thinkOutput[0] = NULLCHAR;
13923 }
13924
13925 void
13926 EditPositionDone (Boolean fakeRights)
13927 {
13928     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13929
13930     startedFromSetupPosition = TRUE;
13931     InitChessProgram(&first, FALSE);
13932     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13933       boards[0][EP_STATUS] = EP_NONE;
13934       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13935     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13936         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13937         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13938       } else boards[0][CASTLING][2] = NoRights;
13939     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13940         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13941         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13942       } else boards[0][CASTLING][5] = NoRights;
13943     }
13944     SendToProgram("force\n", &first);
13945     if (blackPlaysFirst) {
13946         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13947         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13948         currentMove = forwardMostMove = backwardMostMove = 1;
13949         CopyBoard(boards[1], boards[0]);
13950     } else {
13951         currentMove = forwardMostMove = backwardMostMove = 0;
13952     }
13953     SendBoard(&first, forwardMostMove);
13954     if (appData.debugMode) {
13955         fprintf(debugFP, "EditPosDone\n");
13956     }
13957     DisplayTitle("");
13958     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13959     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13960     gameMode = EditGame;
13961     ModeHighlight();
13962     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13963     ClearHighlights(); /* [AS] */
13964 }
13965
13966 /* Pause for `ms' milliseconds */
13967 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13968 void
13969 TimeDelay (long ms)
13970 {
13971     TimeMark m1, m2;
13972
13973     GetTimeMark(&m1);
13974     do {
13975         GetTimeMark(&m2);
13976     } while (SubtractTimeMarks(&m2, &m1) < ms);
13977 }
13978
13979 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13980 void
13981 SendMultiLineToICS (char *buf)
13982 {
13983     char temp[MSG_SIZ+1], *p;
13984     int len;
13985
13986     len = strlen(buf);
13987     if (len > MSG_SIZ)
13988       len = MSG_SIZ;
13989
13990     strncpy(temp, buf, len);
13991     temp[len] = 0;
13992
13993     p = temp;
13994     while (*p) {
13995         if (*p == '\n' || *p == '\r')
13996           *p = ' ';
13997         ++p;
13998     }
13999
14000     strcat(temp, "\n");
14001     SendToICS(temp);
14002     SendToPlayer(temp, strlen(temp));
14003 }
14004
14005 void
14006 SetWhiteToPlayEvent ()
14007 {
14008     if (gameMode == EditPosition) {
14009         blackPlaysFirst = FALSE;
14010         DisplayBothClocks();    /* works because currentMove is 0 */
14011     } else if (gameMode == IcsExamining) {
14012         SendToICS(ics_prefix);
14013         SendToICS("tomove white\n");
14014     }
14015 }
14016
14017 void
14018 SetBlackToPlayEvent ()
14019 {
14020     if (gameMode == EditPosition) {
14021         blackPlaysFirst = TRUE;
14022         currentMove = 1;        /* kludge */
14023         DisplayBothClocks();
14024         currentMove = 0;
14025     } else if (gameMode == IcsExamining) {
14026         SendToICS(ics_prefix);
14027         SendToICS("tomove black\n");
14028     }
14029 }
14030
14031 void
14032 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14033 {
14034     char buf[MSG_SIZ];
14035     ChessSquare piece = boards[0][y][x];
14036
14037     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14038
14039     switch (selection) {
14040       case ClearBoard:
14041         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14042             SendToICS(ics_prefix);
14043             SendToICS("bsetup clear\n");
14044         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14045             SendToICS(ics_prefix);
14046             SendToICS("clearboard\n");
14047         } else {
14048             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14049                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14050                 for (y = 0; y < BOARD_HEIGHT; y++) {
14051                     if (gameMode == IcsExamining) {
14052                         if (boards[currentMove][y][x] != EmptySquare) {
14053                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14054                                     AAA + x, ONE + y);
14055                             SendToICS(buf);
14056                         }
14057                     } else {
14058                         boards[0][y][x] = p;
14059                     }
14060                 }
14061             }
14062         }
14063         if (gameMode == EditPosition) {
14064             DrawPosition(FALSE, boards[0]);
14065         }
14066         break;
14067
14068       case WhitePlay:
14069         SetWhiteToPlayEvent();
14070         break;
14071
14072       case BlackPlay:
14073         SetBlackToPlayEvent();
14074         break;
14075
14076       case EmptySquare:
14077         if (gameMode == IcsExamining) {
14078             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14079             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14080             SendToICS(buf);
14081         } else {
14082             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14083                 if(x == BOARD_LEFT-2) {
14084                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14085                     boards[0][y][1] = 0;
14086                 } else
14087                 if(x == BOARD_RGHT+1) {
14088                     if(y >= gameInfo.holdingsSize) break;
14089                     boards[0][y][BOARD_WIDTH-2] = 0;
14090                 } else break;
14091             }
14092             boards[0][y][x] = EmptySquare;
14093             DrawPosition(FALSE, boards[0]);
14094         }
14095         break;
14096
14097       case PromotePiece:
14098         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14099            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14100             selection = (ChessSquare) (PROMOTED piece);
14101         } else if(piece == EmptySquare) selection = WhiteSilver;
14102         else selection = (ChessSquare)((int)piece - 1);
14103         goto defaultlabel;
14104
14105       case DemotePiece:
14106         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14107            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14108             selection = (ChessSquare) (DEMOTED piece);
14109         } else if(piece == EmptySquare) selection = BlackSilver;
14110         else selection = (ChessSquare)((int)piece + 1);
14111         goto defaultlabel;
14112
14113       case WhiteQueen:
14114       case BlackQueen:
14115         if(gameInfo.variant == VariantShatranj ||
14116            gameInfo.variant == VariantXiangqi  ||
14117            gameInfo.variant == VariantCourier  ||
14118            gameInfo.variant == VariantMakruk     )
14119             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14120         goto defaultlabel;
14121
14122       case WhiteKing:
14123       case BlackKing:
14124         if(gameInfo.variant == VariantXiangqi)
14125             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14126         if(gameInfo.variant == VariantKnightmate)
14127             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14128       default:
14129         defaultlabel:
14130         if (gameMode == IcsExamining) {
14131             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14132             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14133                      PieceToChar(selection), AAA + x, ONE + y);
14134             SendToICS(buf);
14135         } else {
14136             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14137                 int n;
14138                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14139                     n = PieceToNumber(selection - BlackPawn);
14140                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14141                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14142                     boards[0][BOARD_HEIGHT-1-n][1]++;
14143                 } else
14144                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14145                     n = PieceToNumber(selection);
14146                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14147                     boards[0][n][BOARD_WIDTH-1] = selection;
14148                     boards[0][n][BOARD_WIDTH-2]++;
14149                 }
14150             } else
14151             boards[0][y][x] = selection;
14152             DrawPosition(TRUE, boards[0]);
14153             ClearHighlights();
14154             fromX = fromY = -1;
14155         }
14156         break;
14157     }
14158 }
14159
14160
14161 void
14162 DropMenuEvent (ChessSquare selection, int x, int y)
14163 {
14164     ChessMove moveType;
14165
14166     switch (gameMode) {
14167       case IcsPlayingWhite:
14168       case MachinePlaysBlack:
14169         if (!WhiteOnMove(currentMove)) {
14170             DisplayMoveError(_("It is Black's turn"));
14171             return;
14172         }
14173         moveType = WhiteDrop;
14174         break;
14175       case IcsPlayingBlack:
14176       case MachinePlaysWhite:
14177         if (WhiteOnMove(currentMove)) {
14178             DisplayMoveError(_("It is White's turn"));
14179             return;
14180         }
14181         moveType = BlackDrop;
14182         break;
14183       case EditGame:
14184         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14185         break;
14186       default:
14187         return;
14188     }
14189
14190     if (moveType == BlackDrop && selection < BlackPawn) {
14191       selection = (ChessSquare) ((int) selection
14192                                  + (int) BlackPawn - (int) WhitePawn);
14193     }
14194     if (boards[currentMove][y][x] != EmptySquare) {
14195         DisplayMoveError(_("That square is occupied"));
14196         return;
14197     }
14198
14199     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14200 }
14201
14202 void
14203 AcceptEvent ()
14204 {
14205     /* Accept a pending offer of any kind from opponent */
14206
14207     if (appData.icsActive) {
14208         SendToICS(ics_prefix);
14209         SendToICS("accept\n");
14210     } else if (cmailMsgLoaded) {
14211         if (currentMove == cmailOldMove &&
14212             commentList[cmailOldMove] != NULL &&
14213             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14214                    "Black offers a draw" : "White offers a draw")) {
14215             TruncateGame();
14216             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14217             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14218         } else {
14219             DisplayError(_("There is no pending offer on this move"), 0);
14220             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14221         }
14222     } else {
14223         /* Not used for offers from chess program */
14224     }
14225 }
14226
14227 void
14228 DeclineEvent ()
14229 {
14230     /* Decline a pending offer of any kind from opponent */
14231
14232     if (appData.icsActive) {
14233         SendToICS(ics_prefix);
14234         SendToICS("decline\n");
14235     } else if (cmailMsgLoaded) {
14236         if (currentMove == cmailOldMove &&
14237             commentList[cmailOldMove] != NULL &&
14238             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14239                    "Black offers a draw" : "White offers a draw")) {
14240 #ifdef NOTDEF
14241             AppendComment(cmailOldMove, "Draw declined", TRUE);
14242             DisplayComment(cmailOldMove - 1, "Draw declined");
14243 #endif /*NOTDEF*/
14244         } else {
14245             DisplayError(_("There is no pending offer on this move"), 0);
14246         }
14247     } else {
14248         /* Not used for offers from chess program */
14249     }
14250 }
14251
14252 void
14253 RematchEvent ()
14254 {
14255     /* Issue ICS rematch command */
14256     if (appData.icsActive) {
14257         SendToICS(ics_prefix);
14258         SendToICS("rematch\n");
14259     }
14260 }
14261
14262 void
14263 CallFlagEvent ()
14264 {
14265     /* Call your opponent's flag (claim a win on time) */
14266     if (appData.icsActive) {
14267         SendToICS(ics_prefix);
14268         SendToICS("flag\n");
14269     } else {
14270         switch (gameMode) {
14271           default:
14272             return;
14273           case MachinePlaysWhite:
14274             if (whiteFlag) {
14275                 if (blackFlag)
14276                   GameEnds(GameIsDrawn, "Both players ran out of time",
14277                            GE_PLAYER);
14278                 else
14279                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14280             } else {
14281                 DisplayError(_("Your opponent is not out of time"), 0);
14282             }
14283             break;
14284           case MachinePlaysBlack:
14285             if (blackFlag) {
14286                 if (whiteFlag)
14287                   GameEnds(GameIsDrawn, "Both players ran out of time",
14288                            GE_PLAYER);
14289                 else
14290                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14291             } else {
14292                 DisplayError(_("Your opponent is not out of time"), 0);
14293             }
14294             break;
14295         }
14296     }
14297 }
14298
14299 void
14300 ClockClick (int which)
14301 {       // [HGM] code moved to back-end from winboard.c
14302         if(which) { // black clock
14303           if (gameMode == EditPosition || gameMode == IcsExamining) {
14304             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14305             SetBlackToPlayEvent();
14306           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14307           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14308           } else if (shiftKey) {
14309             AdjustClock(which, -1);
14310           } else if (gameMode == IcsPlayingWhite ||
14311                      gameMode == MachinePlaysBlack) {
14312             CallFlagEvent();
14313           }
14314         } else { // white clock
14315           if (gameMode == EditPosition || gameMode == IcsExamining) {
14316             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14317             SetWhiteToPlayEvent();
14318           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14319           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14320           } else if (shiftKey) {
14321             AdjustClock(which, -1);
14322           } else if (gameMode == IcsPlayingBlack ||
14323                    gameMode == MachinePlaysWhite) {
14324             CallFlagEvent();
14325           }
14326         }
14327 }
14328
14329 void
14330 DrawEvent ()
14331 {
14332     /* Offer draw or accept pending draw offer from opponent */
14333
14334     if (appData.icsActive) {
14335         /* Note: tournament rules require draw offers to be
14336            made after you make your move but before you punch
14337            your clock.  Currently ICS doesn't let you do that;
14338            instead, you immediately punch your clock after making
14339            a move, but you can offer a draw at any time. */
14340
14341         SendToICS(ics_prefix);
14342         SendToICS("draw\n");
14343         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14344     } else if (cmailMsgLoaded) {
14345         if (currentMove == cmailOldMove &&
14346             commentList[cmailOldMove] != NULL &&
14347             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14348                    "Black offers a draw" : "White offers a draw")) {
14349             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14350             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14351         } else if (currentMove == cmailOldMove + 1) {
14352             char *offer = WhiteOnMove(cmailOldMove) ?
14353               "White offers a draw" : "Black offers a draw";
14354             AppendComment(currentMove, offer, TRUE);
14355             DisplayComment(currentMove - 1, offer);
14356             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14357         } else {
14358             DisplayError(_("You must make your move before offering a draw"), 0);
14359             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14360         }
14361     } else if (first.offeredDraw) {
14362         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14363     } else {
14364         if (first.sendDrawOffers) {
14365             SendToProgram("draw\n", &first);
14366             userOfferedDraw = TRUE;
14367         }
14368     }
14369 }
14370
14371 void
14372 AdjournEvent ()
14373 {
14374     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14375
14376     if (appData.icsActive) {
14377         SendToICS(ics_prefix);
14378         SendToICS("adjourn\n");
14379     } else {
14380         /* Currently GNU Chess doesn't offer or accept Adjourns */
14381     }
14382 }
14383
14384
14385 void
14386 AbortEvent ()
14387 {
14388     /* Offer Abort or accept pending Abort offer from opponent */
14389
14390     if (appData.icsActive) {
14391         SendToICS(ics_prefix);
14392         SendToICS("abort\n");
14393     } else {
14394         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14395     }
14396 }
14397
14398 void
14399 ResignEvent ()
14400 {
14401     /* Resign.  You can do this even if it's not your turn. */
14402
14403     if (appData.icsActive) {
14404         SendToICS(ics_prefix);
14405         SendToICS("resign\n");
14406     } else {
14407         switch (gameMode) {
14408           case MachinePlaysWhite:
14409             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14410             break;
14411           case MachinePlaysBlack:
14412             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14413             break;
14414           case EditGame:
14415             if (cmailMsgLoaded) {
14416                 TruncateGame();
14417                 if (WhiteOnMove(cmailOldMove)) {
14418                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14419                 } else {
14420                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14421                 }
14422                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14423             }
14424             break;
14425           default:
14426             break;
14427         }
14428     }
14429 }
14430
14431
14432 void
14433 StopObservingEvent ()
14434 {
14435     /* Stop observing current games */
14436     SendToICS(ics_prefix);
14437     SendToICS("unobserve\n");
14438 }
14439
14440 void
14441 StopExaminingEvent ()
14442 {
14443     /* Stop observing current game */
14444     SendToICS(ics_prefix);
14445     SendToICS("unexamine\n");
14446 }
14447
14448 void
14449 ForwardInner (int target)
14450 {
14451     int limit; int oldSeekGraphUp = seekGraphUp;
14452
14453     if (appData.debugMode)
14454         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14455                 target, currentMove, forwardMostMove);
14456
14457     if (gameMode == EditPosition)
14458       return;
14459
14460     seekGraphUp = FALSE;
14461     MarkTargetSquares(1);
14462
14463     if (gameMode == PlayFromGameFile && !pausing)
14464       PauseEvent();
14465
14466     if (gameMode == IcsExamining && pausing)
14467       limit = pauseExamForwardMostMove;
14468     else
14469       limit = forwardMostMove;
14470
14471     if (target > limit) target = limit;
14472
14473     if (target > 0 && moveList[target - 1][0]) {
14474         int fromX, fromY, toX, toY;
14475         toX = moveList[target - 1][2] - AAA;
14476         toY = moveList[target - 1][3] - ONE;
14477         if (moveList[target - 1][1] == '@') {
14478             if (appData.highlightLastMove) {
14479                 SetHighlights(-1, -1, toX, toY);
14480             }
14481         } else {
14482             fromX = moveList[target - 1][0] - AAA;
14483             fromY = moveList[target - 1][1] - ONE;
14484             if (target == currentMove + 1) {
14485                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14486             }
14487             if (appData.highlightLastMove) {
14488                 SetHighlights(fromX, fromY, toX, toY);
14489             }
14490         }
14491     }
14492     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14493         gameMode == Training || gameMode == PlayFromGameFile ||
14494         gameMode == AnalyzeFile) {
14495         while (currentMove < target) {
14496             SendMoveToProgram(currentMove++, &first);
14497         }
14498     } else {
14499         currentMove = target;
14500     }
14501
14502     if (gameMode == EditGame || gameMode == EndOfGame) {
14503         whiteTimeRemaining = timeRemaining[0][currentMove];
14504         blackTimeRemaining = timeRemaining[1][currentMove];
14505     }
14506     DisplayBothClocks();
14507     DisplayMove(currentMove - 1);
14508     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14509     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14510     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14511         DisplayComment(currentMove - 1, commentList[currentMove]);
14512     }
14513     ClearMap(); // [HGM] exclude: invalidate map
14514 }
14515
14516
14517 void
14518 ForwardEvent ()
14519 {
14520     if (gameMode == IcsExamining && !pausing) {
14521         SendToICS(ics_prefix);
14522         SendToICS("forward\n");
14523     } else {
14524         ForwardInner(currentMove + 1);
14525     }
14526 }
14527
14528 void
14529 ToEndEvent ()
14530 {
14531     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14532         /* to optimze, we temporarily turn off analysis mode while we feed
14533          * the remaining moves to the engine. Otherwise we get analysis output
14534          * after each move.
14535          */
14536         if (first.analysisSupport) {
14537           SendToProgram("exit\nforce\n", &first);
14538           first.analyzing = FALSE;
14539         }
14540     }
14541
14542     if (gameMode == IcsExamining && !pausing) {
14543         SendToICS(ics_prefix);
14544         SendToICS("forward 999999\n");
14545     } else {
14546         ForwardInner(forwardMostMove);
14547     }
14548
14549     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14550         /* we have fed all the moves, so reactivate analysis mode */
14551         SendToProgram("analyze\n", &first);
14552         first.analyzing = TRUE;
14553         /*first.maybeThinking = TRUE;*/
14554         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14555     }
14556 }
14557
14558 void
14559 BackwardInner (int target)
14560 {
14561     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14562
14563     if (appData.debugMode)
14564         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14565                 target, currentMove, forwardMostMove);
14566
14567     if (gameMode == EditPosition) return;
14568     seekGraphUp = FALSE;
14569     MarkTargetSquares(1);
14570     if (currentMove <= backwardMostMove) {
14571         ClearHighlights();
14572         DrawPosition(full_redraw, boards[currentMove]);
14573         return;
14574     }
14575     if (gameMode == PlayFromGameFile && !pausing)
14576       PauseEvent();
14577
14578     if (moveList[target][0]) {
14579         int fromX, fromY, toX, toY;
14580         toX = moveList[target][2] - AAA;
14581         toY = moveList[target][3] - ONE;
14582         if (moveList[target][1] == '@') {
14583             if (appData.highlightLastMove) {
14584                 SetHighlights(-1, -1, toX, toY);
14585             }
14586         } else {
14587             fromX = moveList[target][0] - AAA;
14588             fromY = moveList[target][1] - ONE;
14589             if (target == currentMove - 1) {
14590                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14591             }
14592             if (appData.highlightLastMove) {
14593                 SetHighlights(fromX, fromY, toX, toY);
14594             }
14595         }
14596     }
14597     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14598         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14599         while (currentMove > target) {
14600             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14601                 // null move cannot be undone. Reload program with move history before it.
14602                 int i;
14603                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14604                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14605                 }
14606                 SendBoard(&first, i); 
14607                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14608                 break;
14609             }
14610             SendToProgram("undo\n", &first);
14611             currentMove--;
14612         }
14613     } else {
14614         currentMove = target;
14615     }
14616
14617     if (gameMode == EditGame || gameMode == EndOfGame) {
14618         whiteTimeRemaining = timeRemaining[0][currentMove];
14619         blackTimeRemaining = timeRemaining[1][currentMove];
14620     }
14621     DisplayBothClocks();
14622     DisplayMove(currentMove - 1);
14623     DrawPosition(full_redraw, boards[currentMove]);
14624     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14625     // [HGM] PV info: routine tests if comment empty
14626     DisplayComment(currentMove - 1, commentList[currentMove]);
14627     ClearMap(); // [HGM] exclude: invalidate map
14628 }
14629
14630 void
14631 BackwardEvent ()
14632 {
14633     if (gameMode == IcsExamining && !pausing) {
14634         SendToICS(ics_prefix);
14635         SendToICS("backward\n");
14636     } else {
14637         BackwardInner(currentMove - 1);
14638     }
14639 }
14640
14641 void
14642 ToStartEvent ()
14643 {
14644     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14645         /* to optimize, we temporarily turn off analysis mode while we undo
14646          * all the moves. Otherwise we get analysis output after each undo.
14647          */
14648         if (first.analysisSupport) {
14649           SendToProgram("exit\nforce\n", &first);
14650           first.analyzing = FALSE;
14651         }
14652     }
14653
14654     if (gameMode == IcsExamining && !pausing) {
14655         SendToICS(ics_prefix);
14656         SendToICS("backward 999999\n");
14657     } else {
14658         BackwardInner(backwardMostMove);
14659     }
14660
14661     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14662         /* we have fed all the moves, so reactivate analysis mode */
14663         SendToProgram("analyze\n", &first);
14664         first.analyzing = TRUE;
14665         /*first.maybeThinking = TRUE;*/
14666         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14667     }
14668 }
14669
14670 void
14671 ToNrEvent (int to)
14672 {
14673   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14674   if (to >= forwardMostMove) to = forwardMostMove;
14675   if (to <= backwardMostMove) to = backwardMostMove;
14676   if (to < currentMove) {
14677     BackwardInner(to);
14678   } else {
14679     ForwardInner(to);
14680   }
14681 }
14682
14683 void
14684 RevertEvent (Boolean annotate)
14685 {
14686     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14687         return;
14688     }
14689     if (gameMode != IcsExamining) {
14690         DisplayError(_("You are not examining a game"), 0);
14691         return;
14692     }
14693     if (pausing) {
14694         DisplayError(_("You can't revert while pausing"), 0);
14695         return;
14696     }
14697     SendToICS(ics_prefix);
14698     SendToICS("revert\n");
14699 }
14700
14701 void
14702 RetractMoveEvent ()
14703 {
14704     switch (gameMode) {
14705       case MachinePlaysWhite:
14706       case MachinePlaysBlack:
14707         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14708             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14709             return;
14710         }
14711         if (forwardMostMove < 2) return;
14712         currentMove = forwardMostMove = forwardMostMove - 2;
14713         whiteTimeRemaining = timeRemaining[0][currentMove];
14714         blackTimeRemaining = timeRemaining[1][currentMove];
14715         DisplayBothClocks();
14716         DisplayMove(currentMove - 1);
14717         ClearHighlights();/*!! could figure this out*/
14718         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14719         SendToProgram("remove\n", &first);
14720         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14721         break;
14722
14723       case BeginningOfGame:
14724       default:
14725         break;
14726
14727       case IcsPlayingWhite:
14728       case IcsPlayingBlack:
14729         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14730             SendToICS(ics_prefix);
14731             SendToICS("takeback 2\n");
14732         } else {
14733             SendToICS(ics_prefix);
14734             SendToICS("takeback 1\n");
14735         }
14736         break;
14737     }
14738 }
14739
14740 void
14741 MoveNowEvent ()
14742 {
14743     ChessProgramState *cps;
14744
14745     switch (gameMode) {
14746       case MachinePlaysWhite:
14747         if (!WhiteOnMove(forwardMostMove)) {
14748             DisplayError(_("It is your turn"), 0);
14749             return;
14750         }
14751         cps = &first;
14752         break;
14753       case MachinePlaysBlack:
14754         if (WhiteOnMove(forwardMostMove)) {
14755             DisplayError(_("It is your turn"), 0);
14756             return;
14757         }
14758         cps = &first;
14759         break;
14760       case TwoMachinesPlay:
14761         if (WhiteOnMove(forwardMostMove) ==
14762             (first.twoMachinesColor[0] == 'w')) {
14763             cps = &first;
14764         } else {
14765             cps = &second;
14766         }
14767         break;
14768       case BeginningOfGame:
14769       default:
14770         return;
14771     }
14772     SendToProgram("?\n", cps);
14773 }
14774
14775 void
14776 TruncateGameEvent ()
14777 {
14778     EditGameEvent();
14779     if (gameMode != EditGame) return;
14780     TruncateGame();
14781 }
14782
14783 void
14784 TruncateGame ()
14785 {
14786     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14787     if (forwardMostMove > currentMove) {
14788         if (gameInfo.resultDetails != NULL) {
14789             free(gameInfo.resultDetails);
14790             gameInfo.resultDetails = NULL;
14791             gameInfo.result = GameUnfinished;
14792         }
14793         forwardMostMove = currentMove;
14794         HistorySet(parseList, backwardMostMove, forwardMostMove,
14795                    currentMove-1);
14796     }
14797 }
14798
14799 void
14800 HintEvent ()
14801 {
14802     if (appData.noChessProgram) return;
14803     switch (gameMode) {
14804       case MachinePlaysWhite:
14805         if (WhiteOnMove(forwardMostMove)) {
14806             DisplayError(_("Wait until your turn"), 0);
14807             return;
14808         }
14809         break;
14810       case BeginningOfGame:
14811       case MachinePlaysBlack:
14812         if (!WhiteOnMove(forwardMostMove)) {
14813             DisplayError(_("Wait until your turn"), 0);
14814             return;
14815         }
14816         break;
14817       default:
14818         DisplayError(_("No hint available"), 0);
14819         return;
14820     }
14821     SendToProgram("hint\n", &first);
14822     hintRequested = TRUE;
14823 }
14824
14825 void
14826 BookEvent ()
14827 {
14828     if (appData.noChessProgram) return;
14829     switch (gameMode) {
14830       case MachinePlaysWhite:
14831         if (WhiteOnMove(forwardMostMove)) {
14832             DisplayError(_("Wait until your turn"), 0);
14833             return;
14834         }
14835         break;
14836       case BeginningOfGame:
14837       case MachinePlaysBlack:
14838         if (!WhiteOnMove(forwardMostMove)) {
14839             DisplayError(_("Wait until your turn"), 0);
14840             return;
14841         }
14842         break;
14843       case EditPosition:
14844         EditPositionDone(TRUE);
14845         break;
14846       case TwoMachinesPlay:
14847         return;
14848       default:
14849         break;
14850     }
14851     SendToProgram("bk\n", &first);
14852     bookOutput[0] = NULLCHAR;
14853     bookRequested = TRUE;
14854 }
14855
14856 void
14857 AboutGameEvent ()
14858 {
14859     char *tags = PGNTags(&gameInfo);
14860     TagsPopUp(tags, CmailMsg());
14861     free(tags);
14862 }
14863
14864 /* end button procedures */
14865
14866 void
14867 PrintPosition (FILE *fp, int move)
14868 {
14869     int i, j;
14870
14871     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14872         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14873             char c = PieceToChar(boards[move][i][j]);
14874             fputc(c == 'x' ? '.' : c, fp);
14875             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14876         }
14877     }
14878     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14879       fprintf(fp, "white to play\n");
14880     else
14881       fprintf(fp, "black to play\n");
14882 }
14883
14884 void
14885 PrintOpponents (FILE *fp)
14886 {
14887     if (gameInfo.white != NULL) {
14888         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14889     } else {
14890         fprintf(fp, "\n");
14891     }
14892 }
14893
14894 /* Find last component of program's own name, using some heuristics */
14895 void
14896 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14897 {
14898     char *p, *q, c;
14899     int local = (strcmp(host, "localhost") == 0);
14900     while (!local && (p = strchr(prog, ';')) != NULL) {
14901         p++;
14902         while (*p == ' ') p++;
14903         prog = p;
14904     }
14905     if (*prog == '"' || *prog == '\'') {
14906         q = strchr(prog + 1, *prog);
14907     } else {
14908         q = strchr(prog, ' ');
14909     }
14910     if (q == NULL) q = prog + strlen(prog);
14911     p = q;
14912     while (p >= prog && *p != '/' && *p != '\\') p--;
14913     p++;
14914     if(p == prog && *p == '"') p++;
14915     c = *q; *q = 0;
14916     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14917     memcpy(buf, p, q - p);
14918     buf[q - p] = NULLCHAR;
14919     if (!local) {
14920         strcat(buf, "@");
14921         strcat(buf, host);
14922     }
14923 }
14924
14925 char *
14926 TimeControlTagValue ()
14927 {
14928     char buf[MSG_SIZ];
14929     if (!appData.clockMode) {
14930       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14931     } else if (movesPerSession > 0) {
14932       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14933     } else if (timeIncrement == 0) {
14934       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14935     } else {
14936       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14937     }
14938     return StrSave(buf);
14939 }
14940
14941 void
14942 SetGameInfo ()
14943 {
14944     /* This routine is used only for certain modes */
14945     VariantClass v = gameInfo.variant;
14946     ChessMove r = GameUnfinished;
14947     char *p = NULL;
14948
14949     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14950         r = gameInfo.result;
14951         p = gameInfo.resultDetails;
14952         gameInfo.resultDetails = NULL;
14953     }
14954     ClearGameInfo(&gameInfo);
14955     gameInfo.variant = v;
14956
14957     switch (gameMode) {
14958       case MachinePlaysWhite:
14959         gameInfo.event = StrSave( appData.pgnEventHeader );
14960         gameInfo.site = StrSave(HostName());
14961         gameInfo.date = PGNDate();
14962         gameInfo.round = StrSave("-");
14963         gameInfo.white = StrSave(first.tidy);
14964         gameInfo.black = StrSave(UserName());
14965         gameInfo.timeControl = TimeControlTagValue();
14966         break;
14967
14968       case MachinePlaysBlack:
14969         gameInfo.event = StrSave( appData.pgnEventHeader );
14970         gameInfo.site = StrSave(HostName());
14971         gameInfo.date = PGNDate();
14972         gameInfo.round = StrSave("-");
14973         gameInfo.white = StrSave(UserName());
14974         gameInfo.black = StrSave(first.tidy);
14975         gameInfo.timeControl = TimeControlTagValue();
14976         break;
14977
14978       case TwoMachinesPlay:
14979         gameInfo.event = StrSave( appData.pgnEventHeader );
14980         gameInfo.site = StrSave(HostName());
14981         gameInfo.date = PGNDate();
14982         if (roundNr > 0) {
14983             char buf[MSG_SIZ];
14984             snprintf(buf, MSG_SIZ, "%d", roundNr);
14985             gameInfo.round = StrSave(buf);
14986         } else {
14987             gameInfo.round = StrSave("-");
14988         }
14989         if (first.twoMachinesColor[0] == 'w') {
14990             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14991             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14992         } else {
14993             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14994             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14995         }
14996         gameInfo.timeControl = TimeControlTagValue();
14997         break;
14998
14999       case EditGame:
15000         gameInfo.event = StrSave("Edited game");
15001         gameInfo.site = StrSave(HostName());
15002         gameInfo.date = PGNDate();
15003         gameInfo.round = StrSave("-");
15004         gameInfo.white = StrSave("-");
15005         gameInfo.black = StrSave("-");
15006         gameInfo.result = r;
15007         gameInfo.resultDetails = p;
15008         break;
15009
15010       case EditPosition:
15011         gameInfo.event = StrSave("Edited position");
15012         gameInfo.site = StrSave(HostName());
15013         gameInfo.date = PGNDate();
15014         gameInfo.round = StrSave("-");
15015         gameInfo.white = StrSave("-");
15016         gameInfo.black = StrSave("-");
15017         break;
15018
15019       case IcsPlayingWhite:
15020       case IcsPlayingBlack:
15021       case IcsObserving:
15022       case IcsExamining:
15023         break;
15024
15025       case PlayFromGameFile:
15026         gameInfo.event = StrSave("Game from non-PGN file");
15027         gameInfo.site = StrSave(HostName());
15028         gameInfo.date = PGNDate();
15029         gameInfo.round = StrSave("-");
15030         gameInfo.white = StrSave("?");
15031         gameInfo.black = StrSave("?");
15032         break;
15033
15034       default:
15035         break;
15036     }
15037 }
15038
15039 void
15040 ReplaceComment (int index, char *text)
15041 {
15042     int len;
15043     char *p;
15044     float score;
15045
15046     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15047        pvInfoList[index-1].depth == len &&
15048        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15049        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15050     while (*text == '\n') text++;
15051     len = strlen(text);
15052     while (len > 0 && text[len - 1] == '\n') len--;
15053
15054     if (commentList[index] != NULL)
15055       free(commentList[index]);
15056
15057     if (len == 0) {
15058         commentList[index] = NULL;
15059         return;
15060     }
15061   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15062       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15063       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15064     commentList[index] = (char *) malloc(len + 2);
15065     strncpy(commentList[index], text, len);
15066     commentList[index][len] = '\n';
15067     commentList[index][len + 1] = NULLCHAR;
15068   } else {
15069     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15070     char *p;
15071     commentList[index] = (char *) malloc(len + 7);
15072     safeStrCpy(commentList[index], "{\n", 3);
15073     safeStrCpy(commentList[index]+2, text, len+1);
15074     commentList[index][len+2] = NULLCHAR;
15075     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15076     strcat(commentList[index], "\n}\n");
15077   }
15078 }
15079
15080 void
15081 CrushCRs (char *text)
15082 {
15083   char *p = text;
15084   char *q = text;
15085   char ch;
15086
15087   do {
15088     ch = *p++;
15089     if (ch == '\r') continue;
15090     *q++ = ch;
15091   } while (ch != '\0');
15092 }
15093
15094 void
15095 AppendComment (int index, char *text, Boolean addBraces)
15096 /* addBraces  tells if we should add {} */
15097 {
15098     int oldlen, len;
15099     char *old;
15100
15101 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15102     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15103
15104     CrushCRs(text);
15105     while (*text == '\n') text++;
15106     len = strlen(text);
15107     while (len > 0 && text[len - 1] == '\n') len--;
15108     text[len] = NULLCHAR;
15109
15110     if (len == 0) return;
15111
15112     if (commentList[index] != NULL) {
15113       Boolean addClosingBrace = addBraces;
15114         old = commentList[index];
15115         oldlen = strlen(old);
15116         while(commentList[index][oldlen-1] ==  '\n')
15117           commentList[index][--oldlen] = NULLCHAR;
15118         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15119         safeStrCpy(commentList[index], old, oldlen + len + 6);
15120         free(old);
15121         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15122         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15123           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15124           while (*text == '\n') { text++; len--; }
15125           commentList[index][--oldlen] = NULLCHAR;
15126       }
15127         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15128         else          strcat(commentList[index], "\n");
15129         strcat(commentList[index], text);
15130         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15131         else          strcat(commentList[index], "\n");
15132     } else {
15133         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15134         if(addBraces)
15135           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15136         else commentList[index][0] = NULLCHAR;
15137         strcat(commentList[index], text);
15138         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15139         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15140     }
15141 }
15142
15143 static char *
15144 FindStr (char * text, char * sub_text)
15145 {
15146     char * result = strstr( text, sub_text );
15147
15148     if( result != NULL ) {
15149         result += strlen( sub_text );
15150     }
15151
15152     return result;
15153 }
15154
15155 /* [AS] Try to extract PV info from PGN comment */
15156 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15157 char *
15158 GetInfoFromComment (int index, char * text)
15159 {
15160     char * sep = text, *p;
15161
15162     if( text != NULL && index > 0 ) {
15163         int score = 0;
15164         int depth = 0;
15165         int time = -1, sec = 0, deci;
15166         char * s_eval = FindStr( text, "[%eval " );
15167         char * s_emt = FindStr( text, "[%emt " );
15168
15169         if( s_eval != NULL || s_emt != NULL ) {
15170             /* New style */
15171             char delim;
15172
15173             if( s_eval != NULL ) {
15174                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15175                     return text;
15176                 }
15177
15178                 if( delim != ']' ) {
15179                     return text;
15180                 }
15181             }
15182
15183             if( s_emt != NULL ) {
15184             }
15185                 return text;
15186         }
15187         else {
15188             /* We expect something like: [+|-]nnn.nn/dd */
15189             int score_lo = 0;
15190
15191             if(*text != '{') return text; // [HGM] braces: must be normal comment
15192
15193             sep = strchr( text, '/' );
15194             if( sep == NULL || sep < (text+4) ) {
15195                 return text;
15196             }
15197
15198             p = text;
15199             if(p[1] == '(') { // comment starts with PV
15200                p = strchr(p, ')'); // locate end of PV
15201                if(p == NULL || sep < p+5) return text;
15202                // at this point we have something like "{(.*) +0.23/6 ..."
15203                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15204                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15205                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15206             }
15207             time = -1; sec = -1; deci = -1;
15208             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15209                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15210                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15211                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15212                 return text;
15213             }
15214
15215             if( score_lo < 0 || score_lo >= 100 ) {
15216                 return text;
15217             }
15218
15219             if(sec >= 0) time = 600*time + 10*sec; else
15220             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15221
15222             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15223
15224             /* [HGM] PV time: now locate end of PV info */
15225             while( *++sep >= '0' && *sep <= '9'); // strip depth
15226             if(time >= 0)
15227             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15228             if(sec >= 0)
15229             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15230             if(deci >= 0)
15231             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15232             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15233         }
15234
15235         if( depth <= 0 ) {
15236             return text;
15237         }
15238
15239         if( time < 0 ) {
15240             time = -1;
15241         }
15242
15243         pvInfoList[index-1].depth = depth;
15244         pvInfoList[index-1].score = score;
15245         pvInfoList[index-1].time  = 10*time; // centi-sec
15246         if(*sep == '}') *sep = 0; else *--sep = '{';
15247         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15248     }
15249     return sep;
15250 }
15251
15252 void
15253 SendToProgram (char *message, ChessProgramState *cps)
15254 {
15255     int count, outCount, error;
15256     char buf[MSG_SIZ];
15257
15258     if (cps->pr == NoProc) return;
15259     Attention(cps);
15260
15261     if (appData.debugMode) {
15262         TimeMark now;
15263         GetTimeMark(&now);
15264         fprintf(debugFP, "%ld >%-6s: %s",
15265                 SubtractTimeMarks(&now, &programStartTime),
15266                 cps->which, message);
15267         if(serverFP)
15268             fprintf(serverFP, "%ld >%-6s: %s",
15269                 SubtractTimeMarks(&now, &programStartTime),
15270                 cps->which, message), fflush(serverFP);
15271     }
15272
15273     count = strlen(message);
15274     outCount = OutputToProcess(cps->pr, message, count, &error);
15275     if (outCount < count && !exiting
15276                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15277       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15278       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15279         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15280             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15281                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15282                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15283                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15284             } else {
15285                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15286                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15287                 gameInfo.result = res;
15288             }
15289             gameInfo.resultDetails = StrSave(buf);
15290         }
15291         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15292         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15293     }
15294 }
15295
15296 void
15297 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15298 {
15299     char *end_str;
15300     char buf[MSG_SIZ];
15301     ChessProgramState *cps = (ChessProgramState *)closure;
15302
15303     if (isr != cps->isr) return; /* Killed intentionally */
15304     if (count <= 0) {
15305         if (count == 0) {
15306             RemoveInputSource(cps->isr);
15307             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15308                     _(cps->which), cps->program);
15309             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15310             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15311                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15312                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15313                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15314                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15315                 } else {
15316                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15317                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15318                     gameInfo.result = res;
15319                 }
15320                 gameInfo.resultDetails = StrSave(buf);
15321             }
15322             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15323             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15324         } else {
15325             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15326                     _(cps->which), cps->program);
15327             RemoveInputSource(cps->isr);
15328
15329             /* [AS] Program is misbehaving badly... kill it */
15330             if( count == -2 ) {
15331                 DestroyChildProcess( cps->pr, 9 );
15332                 cps->pr = NoProc;
15333             }
15334
15335             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15336         }
15337         return;
15338     }
15339
15340     if ((end_str = strchr(message, '\r')) != NULL)
15341       *end_str = NULLCHAR;
15342     if ((end_str = strchr(message, '\n')) != NULL)
15343       *end_str = NULLCHAR;
15344
15345     if (appData.debugMode) {
15346         TimeMark now; int print = 1;
15347         char *quote = ""; char c; int i;
15348
15349         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15350                 char start = message[0];
15351                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15352                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15353                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15354                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15355                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15356                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15357                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15358                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15359                    sscanf(message, "hint: %c", &c)!=1 && 
15360                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15361                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15362                     print = (appData.engineComments >= 2);
15363                 }
15364                 message[0] = start; // restore original message
15365         }
15366         if(print) {
15367                 GetTimeMark(&now);
15368                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15369                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15370                         quote,
15371                         message);
15372                 if(serverFP)
15373                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15374                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15375                         quote,
15376                         message), fflush(serverFP);
15377         }
15378     }
15379
15380     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15381     if (appData.icsEngineAnalyze) {
15382         if (strstr(message, "whisper") != NULL ||
15383              strstr(message, "kibitz") != NULL ||
15384             strstr(message, "tellics") != NULL) return;
15385     }
15386
15387     HandleMachineMove(message, cps);
15388 }
15389
15390
15391 void
15392 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15393 {
15394     char buf[MSG_SIZ];
15395     int seconds;
15396
15397     if( timeControl_2 > 0 ) {
15398         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15399             tc = timeControl_2;
15400         }
15401     }
15402     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15403     inc /= cps->timeOdds;
15404     st  /= cps->timeOdds;
15405
15406     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15407
15408     if (st > 0) {
15409       /* Set exact time per move, normally using st command */
15410       if (cps->stKludge) {
15411         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15412         seconds = st % 60;
15413         if (seconds == 0) {
15414           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15415         } else {
15416           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15417         }
15418       } else {
15419         snprintf(buf, MSG_SIZ, "st %d\n", st);
15420       }
15421     } else {
15422       /* Set conventional or incremental time control, using level command */
15423       if (seconds == 0) {
15424         /* Note old gnuchess bug -- minutes:seconds used to not work.
15425            Fixed in later versions, but still avoid :seconds
15426            when seconds is 0. */
15427         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15428       } else {
15429         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15430                  seconds, inc/1000.);
15431       }
15432     }
15433     SendToProgram(buf, cps);
15434
15435     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15436     /* Orthogonally, limit search to given depth */
15437     if (sd > 0) {
15438       if (cps->sdKludge) {
15439         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15440       } else {
15441         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15442       }
15443       SendToProgram(buf, cps);
15444     }
15445
15446     if(cps->nps >= 0) { /* [HGM] nps */
15447         if(cps->supportsNPS == FALSE)
15448           cps->nps = -1; // don't use if engine explicitly says not supported!
15449         else {
15450           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15451           SendToProgram(buf, cps);
15452         }
15453     }
15454 }
15455
15456 ChessProgramState *
15457 WhitePlayer ()
15458 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15459 {
15460     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15461        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15462         return &second;
15463     return &first;
15464 }
15465
15466 void
15467 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15468 {
15469     char message[MSG_SIZ];
15470     long time, otime;
15471
15472     /* Note: this routine must be called when the clocks are stopped
15473        or when they have *just* been set or switched; otherwise
15474        it will be off by the time since the current tick started.
15475     */
15476     if (machineWhite) {
15477         time = whiteTimeRemaining / 10;
15478         otime = blackTimeRemaining / 10;
15479     } else {
15480         time = blackTimeRemaining / 10;
15481         otime = whiteTimeRemaining / 10;
15482     }
15483     /* [HGM] translate opponent's time by time-odds factor */
15484     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15485
15486     if (time <= 0) time = 1;
15487     if (otime <= 0) otime = 1;
15488
15489     snprintf(message, MSG_SIZ, "time %ld\n", time);
15490     SendToProgram(message, cps);
15491
15492     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15493     SendToProgram(message, cps);
15494 }
15495
15496 int
15497 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15498 {
15499   char buf[MSG_SIZ];
15500   int len = strlen(name);
15501   int val;
15502
15503   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15504     (*p) += len + 1;
15505     sscanf(*p, "%d", &val);
15506     *loc = (val != 0);
15507     while (**p && **p != ' ')
15508       (*p)++;
15509     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15510     SendToProgram(buf, cps);
15511     return TRUE;
15512   }
15513   return FALSE;
15514 }
15515
15516 int
15517 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15518 {
15519   char buf[MSG_SIZ];
15520   int len = strlen(name);
15521   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15522     (*p) += len + 1;
15523     sscanf(*p, "%d", loc);
15524     while (**p && **p != ' ') (*p)++;
15525     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15526     SendToProgram(buf, cps);
15527     return TRUE;
15528   }
15529   return FALSE;
15530 }
15531
15532 int
15533 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15534 {
15535   char buf[MSG_SIZ];
15536   int len = strlen(name);
15537   if (strncmp((*p), name, len) == 0
15538       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15539     (*p) += len + 2;
15540     sscanf(*p, "%[^\"]", loc);
15541     while (**p && **p != '\"') (*p)++;
15542     if (**p == '\"') (*p)++;
15543     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15544     SendToProgram(buf, cps);
15545     return TRUE;
15546   }
15547   return FALSE;
15548 }
15549
15550 int
15551 ParseOption (Option *opt, ChessProgramState *cps)
15552 // [HGM] options: process the string that defines an engine option, and determine
15553 // name, type, default value, and allowed value range
15554 {
15555         char *p, *q, buf[MSG_SIZ];
15556         int n, min = (-1)<<31, max = 1<<31, def;
15557
15558         if(p = strstr(opt->name, " -spin ")) {
15559             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15560             if(max < min) max = min; // enforce consistency
15561             if(def < min) def = min;
15562             if(def > max) def = max;
15563             opt->value = def;
15564             opt->min = min;
15565             opt->max = max;
15566             opt->type = Spin;
15567         } else if((p = strstr(opt->name, " -slider "))) {
15568             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15569             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15570             if(max < min) max = min; // enforce consistency
15571             if(def < min) def = min;
15572             if(def > max) def = max;
15573             opt->value = def;
15574             opt->min = min;
15575             opt->max = max;
15576             opt->type = Spin; // Slider;
15577         } else if((p = strstr(opt->name, " -string "))) {
15578             opt->textValue = p+9;
15579             opt->type = TextBox;
15580         } else if((p = strstr(opt->name, " -file "))) {
15581             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15582             opt->textValue = p+7;
15583             opt->type = FileName; // FileName;
15584         } else if((p = strstr(opt->name, " -path "))) {
15585             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15586             opt->textValue = p+7;
15587             opt->type = PathName; // PathName;
15588         } else if(p = strstr(opt->name, " -check ")) {
15589             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15590             opt->value = (def != 0);
15591             opt->type = CheckBox;
15592         } else if(p = strstr(opt->name, " -combo ")) {
15593             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15594             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15595             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15596             opt->value = n = 0;
15597             while(q = StrStr(q, " /// ")) {
15598                 n++; *q = 0;    // count choices, and null-terminate each of them
15599                 q += 5;
15600                 if(*q == '*') { // remember default, which is marked with * prefix
15601                     q++;
15602                     opt->value = n;
15603                 }
15604                 cps->comboList[cps->comboCnt++] = q;
15605             }
15606             cps->comboList[cps->comboCnt++] = NULL;
15607             opt->max = n + 1;
15608             opt->type = ComboBox;
15609         } else if(p = strstr(opt->name, " -button")) {
15610             opt->type = Button;
15611         } else if(p = strstr(opt->name, " -save")) {
15612             opt->type = SaveButton;
15613         } else return FALSE;
15614         *p = 0; // terminate option name
15615         // now look if the command-line options define a setting for this engine option.
15616         if(cps->optionSettings && cps->optionSettings[0])
15617             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15618         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15619           snprintf(buf, MSG_SIZ, "option %s", p);
15620                 if(p = strstr(buf, ",")) *p = 0;
15621                 if(q = strchr(buf, '=')) switch(opt->type) {
15622                     case ComboBox:
15623                         for(n=0; n<opt->max; n++)
15624                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15625                         break;
15626                     case TextBox:
15627                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15628                         break;
15629                     case Spin:
15630                     case CheckBox:
15631                         opt->value = atoi(q+1);
15632                     default:
15633                         break;
15634                 }
15635                 strcat(buf, "\n");
15636                 SendToProgram(buf, cps);
15637         }
15638         return TRUE;
15639 }
15640
15641 void
15642 FeatureDone (ChessProgramState *cps, int val)
15643 {
15644   DelayedEventCallback cb = GetDelayedEvent();
15645   if ((cb == InitBackEnd3 && cps == &first) ||
15646       (cb == SettingsMenuIfReady && cps == &second) ||
15647       (cb == LoadEngine) ||
15648       (cb == TwoMachinesEventIfReady)) {
15649     CancelDelayedEvent();
15650     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15651   }
15652   cps->initDone = val;
15653 }
15654
15655 /* Parse feature command from engine */
15656 void
15657 ParseFeatures (char *args, ChessProgramState *cps)
15658 {
15659   char *p = args;
15660   char *q;
15661   int val;
15662   char buf[MSG_SIZ];
15663
15664   for (;;) {
15665     while (*p == ' ') p++;
15666     if (*p == NULLCHAR) return;
15667
15668     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15669     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15670     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15671     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15672     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15673     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15674     if (BoolFeature(&p, "reuse", &val, cps)) {
15675       /* Engine can disable reuse, but can't enable it if user said no */
15676       if (!val) cps->reuse = FALSE;
15677       continue;
15678     }
15679     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15680     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15681       if (gameMode == TwoMachinesPlay) {
15682         DisplayTwoMachinesTitle();
15683       } else {
15684         DisplayTitle("");
15685       }
15686       continue;
15687     }
15688     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15689     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15690     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15691     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15692     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15693     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15694     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15695     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15696     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15697     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15698     if (IntFeature(&p, "done", &val, cps)) {
15699       FeatureDone(cps, val);
15700       continue;
15701     }
15702     /* Added by Tord: */
15703     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15704     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15705     /* End of additions by Tord */
15706
15707     /* [HGM] added features: */
15708     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15709     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15710     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15711     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15712     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15713     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15714     if (StringFeature(&p, "option", buf, cps)) {
15715         FREE(cps->option[cps->nrOptions].name);
15716         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15717         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15718         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15719           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15720             SendToProgram(buf, cps);
15721             continue;
15722         }
15723         if(cps->nrOptions >= MAX_OPTIONS) {
15724             cps->nrOptions--;
15725             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15726             DisplayError(buf, 0);
15727         }
15728         continue;
15729     }
15730     /* End of additions by HGM */
15731
15732     /* unknown feature: complain and skip */
15733     q = p;
15734     while (*q && *q != '=') q++;
15735     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15736     SendToProgram(buf, cps);
15737     p = q;
15738     if (*p == '=') {
15739       p++;
15740       if (*p == '\"') {
15741         p++;
15742         while (*p && *p != '\"') p++;
15743         if (*p == '\"') p++;
15744       } else {
15745         while (*p && *p != ' ') p++;
15746       }
15747     }
15748   }
15749
15750 }
15751
15752 void
15753 PeriodicUpdatesEvent (int newState)
15754 {
15755     if (newState == appData.periodicUpdates)
15756       return;
15757
15758     appData.periodicUpdates=newState;
15759
15760     /* Display type changes, so update it now */
15761 //    DisplayAnalysis();
15762
15763     /* Get the ball rolling again... */
15764     if (newState) {
15765         AnalysisPeriodicEvent(1);
15766         StartAnalysisClock();
15767     }
15768 }
15769
15770 void
15771 PonderNextMoveEvent (int newState)
15772 {
15773     if (newState == appData.ponderNextMove) return;
15774     if (gameMode == EditPosition) EditPositionDone(TRUE);
15775     if (newState) {
15776         SendToProgram("hard\n", &first);
15777         if (gameMode == TwoMachinesPlay) {
15778             SendToProgram("hard\n", &second);
15779         }
15780     } else {
15781         SendToProgram("easy\n", &first);
15782         thinkOutput[0] = NULLCHAR;
15783         if (gameMode == TwoMachinesPlay) {
15784             SendToProgram("easy\n", &second);
15785         }
15786     }
15787     appData.ponderNextMove = newState;
15788 }
15789
15790 void
15791 NewSettingEvent (int option, int *feature, char *command, int value)
15792 {
15793     char buf[MSG_SIZ];
15794
15795     if (gameMode == EditPosition) EditPositionDone(TRUE);
15796     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15797     if(feature == NULL || *feature) SendToProgram(buf, &first);
15798     if (gameMode == TwoMachinesPlay) {
15799         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15800     }
15801 }
15802
15803 void
15804 ShowThinkingEvent ()
15805 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15806 {
15807     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15808     int newState = appData.showThinking
15809         // [HGM] thinking: other features now need thinking output as well
15810         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15811
15812     if (oldState == newState) return;
15813     oldState = newState;
15814     if (gameMode == EditPosition) EditPositionDone(TRUE);
15815     if (oldState) {
15816         SendToProgram("post\n", &first);
15817         if (gameMode == TwoMachinesPlay) {
15818             SendToProgram("post\n", &second);
15819         }
15820     } else {
15821         SendToProgram("nopost\n", &first);
15822         thinkOutput[0] = NULLCHAR;
15823         if (gameMode == TwoMachinesPlay) {
15824             SendToProgram("nopost\n", &second);
15825         }
15826     }
15827 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15828 }
15829
15830 void
15831 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15832 {
15833   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15834   if (pr == NoProc) return;
15835   AskQuestion(title, question, replyPrefix, pr);
15836 }
15837
15838 void
15839 TypeInEvent (char firstChar)
15840 {
15841     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15842         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15843         gameMode == AnalyzeMode || gameMode == EditGame || 
15844         gameMode == EditPosition || gameMode == IcsExamining ||
15845         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15846         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15847                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15848                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15849         gameMode == Training) PopUpMoveDialog(firstChar);
15850 }
15851
15852 void
15853 TypeInDoneEvent (char *move)
15854 {
15855         Board board;
15856         int n, fromX, fromY, toX, toY;
15857         char promoChar;
15858         ChessMove moveType;
15859
15860         // [HGM] FENedit
15861         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15862                 EditPositionPasteFEN(move);
15863                 return;
15864         }
15865         // [HGM] movenum: allow move number to be typed in any mode
15866         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15867           ToNrEvent(2*n-1);
15868           return;
15869         }
15870         // undocumented kludge: allow command-line option to be typed in!
15871         // (potentially fatal, and does not implement the effect of the option.)
15872         // should only be used for options that are values on which future decisions will be made,
15873         // and definitely not on options that would be used during initialization.
15874         if(strstr(move, "!!! -") == move) {
15875             ParseArgsFromString(move+4);
15876             return;
15877         }
15878
15879       if (gameMode != EditGame && currentMove != forwardMostMove && 
15880         gameMode != Training) {
15881         DisplayMoveError(_("Displayed move is not current"));
15882       } else {
15883         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15884           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15885         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15886         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15887           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15888           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15889         } else {
15890           DisplayMoveError(_("Could not parse move"));
15891         }
15892       }
15893 }
15894
15895 void
15896 DisplayMove (int moveNumber)
15897 {
15898     char message[MSG_SIZ];
15899     char res[MSG_SIZ];
15900     char cpThinkOutput[MSG_SIZ];
15901
15902     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15903
15904     if (moveNumber == forwardMostMove - 1 ||
15905         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15906
15907         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15908
15909         if (strchr(cpThinkOutput, '\n')) {
15910             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15911         }
15912     } else {
15913         *cpThinkOutput = NULLCHAR;
15914     }
15915
15916     /* [AS] Hide thinking from human user */
15917     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15918         *cpThinkOutput = NULLCHAR;
15919         if( thinkOutput[0] != NULLCHAR ) {
15920             int i;
15921
15922             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15923                 cpThinkOutput[i] = '.';
15924             }
15925             cpThinkOutput[i] = NULLCHAR;
15926             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15927         }
15928     }
15929
15930     if (moveNumber == forwardMostMove - 1 &&
15931         gameInfo.resultDetails != NULL) {
15932         if (gameInfo.resultDetails[0] == NULLCHAR) {
15933           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15934         } else {
15935           snprintf(res, MSG_SIZ, " {%s} %s",
15936                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15937         }
15938     } else {
15939         res[0] = NULLCHAR;
15940     }
15941
15942     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15943         DisplayMessage(res, cpThinkOutput);
15944     } else {
15945       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15946                 WhiteOnMove(moveNumber) ? " " : ".. ",
15947                 parseList[moveNumber], res);
15948         DisplayMessage(message, cpThinkOutput);
15949     }
15950 }
15951
15952 void
15953 DisplayComment (int moveNumber, char *text)
15954 {
15955     char title[MSG_SIZ];
15956
15957     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15958       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15959     } else {
15960       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15961               WhiteOnMove(moveNumber) ? " " : ".. ",
15962               parseList[moveNumber]);
15963     }
15964     if (text != NULL && (appData.autoDisplayComment || commentUp))
15965         CommentPopUp(title, text);
15966 }
15967
15968 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15969  * might be busy thinking or pondering.  It can be omitted if your
15970  * gnuchess is configured to stop thinking immediately on any user
15971  * input.  However, that gnuchess feature depends on the FIONREAD
15972  * ioctl, which does not work properly on some flavors of Unix.
15973  */
15974 void
15975 Attention (ChessProgramState *cps)
15976 {
15977 #if ATTENTION
15978     if (!cps->useSigint) return;
15979     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15980     switch (gameMode) {
15981       case MachinePlaysWhite:
15982       case MachinePlaysBlack:
15983       case TwoMachinesPlay:
15984       case IcsPlayingWhite:
15985       case IcsPlayingBlack:
15986       case AnalyzeMode:
15987       case AnalyzeFile:
15988         /* Skip if we know it isn't thinking */
15989         if (!cps->maybeThinking) return;
15990         if (appData.debugMode)
15991           fprintf(debugFP, "Interrupting %s\n", cps->which);
15992         InterruptChildProcess(cps->pr);
15993         cps->maybeThinking = FALSE;
15994         break;
15995       default:
15996         break;
15997     }
15998 #endif /*ATTENTION*/
15999 }
16000
16001 int
16002 CheckFlags ()
16003 {
16004     if (whiteTimeRemaining <= 0) {
16005         if (!whiteFlag) {
16006             whiteFlag = TRUE;
16007             if (appData.icsActive) {
16008                 if (appData.autoCallFlag &&
16009                     gameMode == IcsPlayingBlack && !blackFlag) {
16010                   SendToICS(ics_prefix);
16011                   SendToICS("flag\n");
16012                 }
16013             } else {
16014                 if (blackFlag) {
16015                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16016                 } else {
16017                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16018                     if (appData.autoCallFlag) {
16019                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16020                         return TRUE;
16021                     }
16022                 }
16023             }
16024         }
16025     }
16026     if (blackTimeRemaining <= 0) {
16027         if (!blackFlag) {
16028             blackFlag = TRUE;
16029             if (appData.icsActive) {
16030                 if (appData.autoCallFlag &&
16031                     gameMode == IcsPlayingWhite && !whiteFlag) {
16032                   SendToICS(ics_prefix);
16033                   SendToICS("flag\n");
16034                 }
16035             } else {
16036                 if (whiteFlag) {
16037                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16038                 } else {
16039                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16040                     if (appData.autoCallFlag) {
16041                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16042                         return TRUE;
16043                     }
16044                 }
16045             }
16046         }
16047     }
16048     return FALSE;
16049 }
16050
16051 void
16052 CheckTimeControl ()
16053 {
16054     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16055         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16056
16057     /*
16058      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16059      */
16060     if ( !WhiteOnMove(forwardMostMove) ) {
16061         /* White made time control */
16062         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16063         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16064         /* [HGM] time odds: correct new time quota for time odds! */
16065                                             / WhitePlayer()->timeOdds;
16066         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16067     } else {
16068         lastBlack -= blackTimeRemaining;
16069         /* Black made time control */
16070         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16071                                             / WhitePlayer()->other->timeOdds;
16072         lastWhite = whiteTimeRemaining;
16073     }
16074 }
16075
16076 void
16077 DisplayBothClocks ()
16078 {
16079     int wom = gameMode == EditPosition ?
16080       !blackPlaysFirst : WhiteOnMove(currentMove);
16081     DisplayWhiteClock(whiteTimeRemaining, wom);
16082     DisplayBlackClock(blackTimeRemaining, !wom);
16083 }
16084
16085
16086 /* Timekeeping seems to be a portability nightmare.  I think everyone
16087    has ftime(), but I'm really not sure, so I'm including some ifdefs
16088    to use other calls if you don't.  Clocks will be less accurate if
16089    you have neither ftime nor gettimeofday.
16090 */
16091
16092 /* VS 2008 requires the #include outside of the function */
16093 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16094 #include <sys/timeb.h>
16095 #endif
16096
16097 /* Get the current time as a TimeMark */
16098 void
16099 GetTimeMark (TimeMark *tm)
16100 {
16101 #if HAVE_GETTIMEOFDAY
16102
16103     struct timeval timeVal;
16104     struct timezone timeZone;
16105
16106     gettimeofday(&timeVal, &timeZone);
16107     tm->sec = (long) timeVal.tv_sec;
16108     tm->ms = (int) (timeVal.tv_usec / 1000L);
16109
16110 #else /*!HAVE_GETTIMEOFDAY*/
16111 #if HAVE_FTIME
16112
16113 // include <sys/timeb.h> / moved to just above start of function
16114     struct timeb timeB;
16115
16116     ftime(&timeB);
16117     tm->sec = (long) timeB.time;
16118     tm->ms = (int) timeB.millitm;
16119
16120 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16121     tm->sec = (long) time(NULL);
16122     tm->ms = 0;
16123 #endif
16124 #endif
16125 }
16126
16127 /* Return the difference in milliseconds between two
16128    time marks.  We assume the difference will fit in a long!
16129 */
16130 long
16131 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16132 {
16133     return 1000L*(tm2->sec - tm1->sec) +
16134            (long) (tm2->ms - tm1->ms);
16135 }
16136
16137
16138 /*
16139  * Code to manage the game clocks.
16140  *
16141  * In tournament play, black starts the clock and then white makes a move.
16142  * We give the human user a slight advantage if he is playing white---the
16143  * clocks don't run until he makes his first move, so it takes zero time.
16144  * Also, we don't account for network lag, so we could get out of sync
16145  * with GNU Chess's clock -- but then, referees are always right.
16146  */
16147
16148 static TimeMark tickStartTM;
16149 static long intendedTickLength;
16150
16151 long
16152 NextTickLength (long timeRemaining)
16153 {
16154     long nominalTickLength, nextTickLength;
16155
16156     if (timeRemaining > 0L && timeRemaining <= 10000L)
16157       nominalTickLength = 100L;
16158     else
16159       nominalTickLength = 1000L;
16160     nextTickLength = timeRemaining % nominalTickLength;
16161     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16162
16163     return nextTickLength;
16164 }
16165
16166 /* Adjust clock one minute up or down */
16167 void
16168 AdjustClock (Boolean which, int dir)
16169 {
16170     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16171     if(which) blackTimeRemaining += 60000*dir;
16172     else      whiteTimeRemaining += 60000*dir;
16173     DisplayBothClocks();
16174     adjustedClock = TRUE;
16175 }
16176
16177 /* Stop clocks and reset to a fresh time control */
16178 void
16179 ResetClocks ()
16180 {
16181     (void) StopClockTimer();
16182     if (appData.icsActive) {
16183         whiteTimeRemaining = blackTimeRemaining = 0;
16184     } else if (searchTime) {
16185         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16186         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16187     } else { /* [HGM] correct new time quote for time odds */
16188         whiteTC = blackTC = fullTimeControlString;
16189         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16190         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16191     }
16192     if (whiteFlag || blackFlag) {
16193         DisplayTitle("");
16194         whiteFlag = blackFlag = FALSE;
16195     }
16196     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16197     DisplayBothClocks();
16198     adjustedClock = FALSE;
16199 }
16200
16201 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16202
16203 /* Decrement running clock by amount of time that has passed */
16204 void
16205 DecrementClocks ()
16206 {
16207     long timeRemaining;
16208     long lastTickLength, fudge;
16209     TimeMark now;
16210
16211     if (!appData.clockMode) return;
16212     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16213
16214     GetTimeMark(&now);
16215
16216     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16217
16218     /* Fudge if we woke up a little too soon */
16219     fudge = intendedTickLength - lastTickLength;
16220     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16221
16222     if (WhiteOnMove(forwardMostMove)) {
16223         if(whiteNPS >= 0) lastTickLength = 0;
16224         timeRemaining = whiteTimeRemaining -= lastTickLength;
16225         if(timeRemaining < 0 && !appData.icsActive) {
16226             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16227             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16228                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16229                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16230             }
16231         }
16232         DisplayWhiteClock(whiteTimeRemaining - fudge,
16233                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16234     } else {
16235         if(blackNPS >= 0) lastTickLength = 0;
16236         timeRemaining = blackTimeRemaining -= lastTickLength;
16237         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16238             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16239             if(suddenDeath) {
16240                 blackStartMove = forwardMostMove;
16241                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16242             }
16243         }
16244         DisplayBlackClock(blackTimeRemaining - fudge,
16245                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16246     }
16247     if (CheckFlags()) return;
16248
16249     if(twoBoards) { // count down secondary board's clocks as well
16250         activePartnerTime -= lastTickLength;
16251         partnerUp = 1;
16252         if(activePartner == 'W')
16253             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16254         else
16255             DisplayBlackClock(activePartnerTime, TRUE);
16256         partnerUp = 0;
16257     }
16258
16259     tickStartTM = now;
16260     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16261     StartClockTimer(intendedTickLength);
16262
16263     /* if the time remaining has fallen below the alarm threshold, sound the
16264      * alarm. if the alarm has sounded and (due to a takeback or time control
16265      * with increment) the time remaining has increased to a level above the
16266      * threshold, reset the alarm so it can sound again.
16267      */
16268
16269     if (appData.icsActive && appData.icsAlarm) {
16270
16271         /* make sure we are dealing with the user's clock */
16272         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16273                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16274            )) return;
16275
16276         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16277             alarmSounded = FALSE;
16278         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16279             PlayAlarmSound();
16280             alarmSounded = TRUE;
16281         }
16282     }
16283 }
16284
16285
16286 /* A player has just moved, so stop the previously running
16287    clock and (if in clock mode) start the other one.
16288    We redisplay both clocks in case we're in ICS mode, because
16289    ICS gives us an update to both clocks after every move.
16290    Note that this routine is called *after* forwardMostMove
16291    is updated, so the last fractional tick must be subtracted
16292    from the color that is *not* on move now.
16293 */
16294 void
16295 SwitchClocks (int newMoveNr)
16296 {
16297     long lastTickLength;
16298     TimeMark now;
16299     int flagged = FALSE;
16300
16301     GetTimeMark(&now);
16302
16303     if (StopClockTimer() && appData.clockMode) {
16304         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16305         if (!WhiteOnMove(forwardMostMove)) {
16306             if(blackNPS >= 0) lastTickLength = 0;
16307             blackTimeRemaining -= lastTickLength;
16308            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16309 //         if(pvInfoList[forwardMostMove].time == -1)
16310                  pvInfoList[forwardMostMove].time =               // use GUI time
16311                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16312         } else {
16313            if(whiteNPS >= 0) lastTickLength = 0;
16314            whiteTimeRemaining -= lastTickLength;
16315            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16316 //         if(pvInfoList[forwardMostMove].time == -1)
16317                  pvInfoList[forwardMostMove].time =
16318                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16319         }
16320         flagged = CheckFlags();
16321     }
16322     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16323     CheckTimeControl();
16324
16325     if (flagged || !appData.clockMode) return;
16326
16327     switch (gameMode) {
16328       case MachinePlaysBlack:
16329       case MachinePlaysWhite:
16330       case BeginningOfGame:
16331         if (pausing) return;
16332         break;
16333
16334       case EditGame:
16335       case PlayFromGameFile:
16336       case IcsExamining:
16337         return;
16338
16339       default:
16340         break;
16341     }
16342
16343     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16344         if(WhiteOnMove(forwardMostMove))
16345              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16346         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16347     }
16348
16349     tickStartTM = now;
16350     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16351       whiteTimeRemaining : blackTimeRemaining);
16352     StartClockTimer(intendedTickLength);
16353 }
16354
16355
16356 /* Stop both clocks */
16357 void
16358 StopClocks ()
16359 {
16360     long lastTickLength;
16361     TimeMark now;
16362
16363     if (!StopClockTimer()) return;
16364     if (!appData.clockMode) return;
16365
16366     GetTimeMark(&now);
16367
16368     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16369     if (WhiteOnMove(forwardMostMove)) {
16370         if(whiteNPS >= 0) lastTickLength = 0;
16371         whiteTimeRemaining -= lastTickLength;
16372         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16373     } else {
16374         if(blackNPS >= 0) lastTickLength = 0;
16375         blackTimeRemaining -= lastTickLength;
16376         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16377     }
16378     CheckFlags();
16379 }
16380
16381 /* Start clock of player on move.  Time may have been reset, so
16382    if clock is already running, stop and restart it. */
16383 void
16384 StartClocks ()
16385 {
16386     (void) StopClockTimer(); /* in case it was running already */
16387     DisplayBothClocks();
16388     if (CheckFlags()) return;
16389
16390     if (!appData.clockMode) return;
16391     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16392
16393     GetTimeMark(&tickStartTM);
16394     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16395       whiteTimeRemaining : blackTimeRemaining);
16396
16397    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16398     whiteNPS = blackNPS = -1;
16399     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16400        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16401         whiteNPS = first.nps;
16402     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16403        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16404         blackNPS = first.nps;
16405     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16406         whiteNPS = second.nps;
16407     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16408         blackNPS = second.nps;
16409     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16410
16411     StartClockTimer(intendedTickLength);
16412 }
16413
16414 char *
16415 TimeString (long ms)
16416 {
16417     long second, minute, hour, day;
16418     char *sign = "";
16419     static char buf[32];
16420
16421     if (ms > 0 && ms <= 9900) {
16422       /* convert milliseconds to tenths, rounding up */
16423       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16424
16425       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16426       return buf;
16427     }
16428
16429     /* convert milliseconds to seconds, rounding up */
16430     /* use floating point to avoid strangeness of integer division
16431        with negative dividends on many machines */
16432     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16433
16434     if (second < 0) {
16435         sign = "-";
16436         second = -second;
16437     }
16438
16439     day = second / (60 * 60 * 24);
16440     second = second % (60 * 60 * 24);
16441     hour = second / (60 * 60);
16442     second = second % (60 * 60);
16443     minute = second / 60;
16444     second = second % 60;
16445
16446     if (day > 0)
16447       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16448               sign, day, hour, minute, second);
16449     else if (hour > 0)
16450       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16451     else
16452       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16453
16454     return buf;
16455 }
16456
16457
16458 /*
16459  * This is necessary because some C libraries aren't ANSI C compliant yet.
16460  */
16461 char *
16462 StrStr (char *string, char *match)
16463 {
16464     int i, length;
16465
16466     length = strlen(match);
16467
16468     for (i = strlen(string) - length; i >= 0; i--, string++)
16469       if (!strncmp(match, string, length))
16470         return string;
16471
16472     return NULL;
16473 }
16474
16475 char *
16476 StrCaseStr (char *string, char *match)
16477 {
16478     int i, j, length;
16479
16480     length = strlen(match);
16481
16482     for (i = strlen(string) - length; i >= 0; i--, string++) {
16483         for (j = 0; j < length; j++) {
16484             if (ToLower(match[j]) != ToLower(string[j]))
16485               break;
16486         }
16487         if (j == length) return string;
16488     }
16489
16490     return NULL;
16491 }
16492
16493 #ifndef _amigados
16494 int
16495 StrCaseCmp (char *s1, char *s2)
16496 {
16497     char c1, c2;
16498
16499     for (;;) {
16500         c1 = ToLower(*s1++);
16501         c2 = ToLower(*s2++);
16502         if (c1 > c2) return 1;
16503         if (c1 < c2) return -1;
16504         if (c1 == NULLCHAR) return 0;
16505     }
16506 }
16507
16508
16509 int
16510 ToLower (int c)
16511 {
16512     return isupper(c) ? tolower(c) : c;
16513 }
16514
16515
16516 int
16517 ToUpper (int c)
16518 {
16519     return islower(c) ? toupper(c) : c;
16520 }
16521 #endif /* !_amigados    */
16522
16523 char *
16524 StrSave (char *s)
16525 {
16526   char *ret;
16527
16528   if ((ret = (char *) malloc(strlen(s) + 1)))
16529     {
16530       safeStrCpy(ret, s, strlen(s)+1);
16531     }
16532   return ret;
16533 }
16534
16535 char *
16536 StrSavePtr (char *s, char **savePtr)
16537 {
16538     if (*savePtr) {
16539         free(*savePtr);
16540     }
16541     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16542       safeStrCpy(*savePtr, s, strlen(s)+1);
16543     }
16544     return(*savePtr);
16545 }
16546
16547 char *
16548 PGNDate ()
16549 {
16550     time_t clock;
16551     struct tm *tm;
16552     char buf[MSG_SIZ];
16553
16554     clock = time((time_t *)NULL);
16555     tm = localtime(&clock);
16556     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16557             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16558     return StrSave(buf);
16559 }
16560
16561
16562 char *
16563 PositionToFEN (int move, char *overrideCastling)
16564 {
16565     int i, j, fromX, fromY, toX, toY;
16566     int whiteToPlay;
16567     char buf[MSG_SIZ];
16568     char *p, *q;
16569     int emptycount;
16570     ChessSquare piece;
16571
16572     whiteToPlay = (gameMode == EditPosition) ?
16573       !blackPlaysFirst : (move % 2 == 0);
16574     p = buf;
16575
16576     /* Piece placement data */
16577     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16578         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16579         emptycount = 0;
16580         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16581             if (boards[move][i][j] == EmptySquare) {
16582                 emptycount++;
16583             } else { ChessSquare piece = boards[move][i][j];
16584                 if (emptycount > 0) {
16585                     if(emptycount<10) /* [HGM] can be >= 10 */
16586                         *p++ = '0' + emptycount;
16587                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16588                     emptycount = 0;
16589                 }
16590                 if(PieceToChar(piece) == '+') {
16591                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16592                     *p++ = '+';
16593                     piece = (ChessSquare)(DEMOTED piece);
16594                 }
16595                 *p++ = PieceToChar(piece);
16596                 if(p[-1] == '~') {
16597                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16598                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16599                     *p++ = '~';
16600                 }
16601             }
16602         }
16603         if (emptycount > 0) {
16604             if(emptycount<10) /* [HGM] can be >= 10 */
16605                 *p++ = '0' + emptycount;
16606             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16607             emptycount = 0;
16608         }
16609         *p++ = '/';
16610     }
16611     *(p - 1) = ' ';
16612
16613     /* [HGM] print Crazyhouse or Shogi holdings */
16614     if( gameInfo.holdingsWidth ) {
16615         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16616         q = p;
16617         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16618             piece = boards[move][i][BOARD_WIDTH-1];
16619             if( piece != EmptySquare )
16620               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16621                   *p++ = PieceToChar(piece);
16622         }
16623         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16624             piece = boards[move][BOARD_HEIGHT-i-1][0];
16625             if( piece != EmptySquare )
16626               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16627                   *p++ = PieceToChar(piece);
16628         }
16629
16630         if( q == p ) *p++ = '-';
16631         *p++ = ']';
16632         *p++ = ' ';
16633     }
16634
16635     /* Active color */
16636     *p++ = whiteToPlay ? 'w' : 'b';
16637     *p++ = ' ';
16638
16639   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16640     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16641   } else {
16642   if(nrCastlingRights) {
16643      q = p;
16644      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16645        /* [HGM] write directly from rights */
16646            if(boards[move][CASTLING][2] != NoRights &&
16647               boards[move][CASTLING][0] != NoRights   )
16648                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16649            if(boards[move][CASTLING][2] != NoRights &&
16650               boards[move][CASTLING][1] != NoRights   )
16651                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16652            if(boards[move][CASTLING][5] != NoRights &&
16653               boards[move][CASTLING][3] != NoRights   )
16654                 *p++ = boards[move][CASTLING][3] + AAA;
16655            if(boards[move][CASTLING][5] != NoRights &&
16656               boards[move][CASTLING][4] != NoRights   )
16657                 *p++ = boards[move][CASTLING][4] + AAA;
16658      } else {
16659
16660         /* [HGM] write true castling rights */
16661         if( nrCastlingRights == 6 ) {
16662             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16663                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16664             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16665                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16666             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16667                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16668             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16669                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16670         }
16671      }
16672      if (q == p) *p++ = '-'; /* No castling rights */
16673      *p++ = ' ';
16674   }
16675
16676   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16677      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16678     /* En passant target square */
16679     if (move > backwardMostMove) {
16680         fromX = moveList[move - 1][0] - AAA;
16681         fromY = moveList[move - 1][1] - ONE;
16682         toX = moveList[move - 1][2] - AAA;
16683         toY = moveList[move - 1][3] - ONE;
16684         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16685             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16686             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16687             fromX == toX) {
16688             /* 2-square pawn move just happened */
16689             *p++ = toX + AAA;
16690             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16691         } else {
16692             *p++ = '-';
16693         }
16694     } else if(move == backwardMostMove) {
16695         // [HGM] perhaps we should always do it like this, and forget the above?
16696         if((signed char)boards[move][EP_STATUS] >= 0) {
16697             *p++ = boards[move][EP_STATUS] + AAA;
16698             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16699         } else {
16700             *p++ = '-';
16701         }
16702     } else {
16703         *p++ = '-';
16704     }
16705     *p++ = ' ';
16706   }
16707   }
16708
16709     /* [HGM] find reversible plies */
16710     {   int i = 0, j=move;
16711
16712         if (appData.debugMode) { int k;
16713             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16714             for(k=backwardMostMove; k<=forwardMostMove; k++)
16715                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16716
16717         }
16718
16719         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16720         if( j == backwardMostMove ) i += initialRulePlies;
16721         sprintf(p, "%d ", i);
16722         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16723     }
16724     /* Fullmove number */
16725     sprintf(p, "%d", (move / 2) + 1);
16726
16727     return StrSave(buf);
16728 }
16729
16730 Boolean
16731 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16732 {
16733     int i, j;
16734     char *p, c;
16735     int emptycount;
16736     ChessSquare piece;
16737
16738     p = fen;
16739
16740     /* [HGM] by default clear Crazyhouse holdings, if present */
16741     if(gameInfo.holdingsWidth) {
16742        for(i=0; i<BOARD_HEIGHT; i++) {
16743            board[i][0]             = EmptySquare; /* black holdings */
16744            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16745            board[i][1]             = (ChessSquare) 0; /* black counts */
16746            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16747        }
16748     }
16749
16750     /* Piece placement data */
16751     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16752         j = 0;
16753         for (;;) {
16754             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16755                 if (*p == '/') p++;
16756                 emptycount = gameInfo.boardWidth - j;
16757                 while (emptycount--)
16758                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16759                 break;
16760 #if(BOARD_FILES >= 10)
16761             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16762                 p++; emptycount=10;
16763                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16764                 while (emptycount--)
16765                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16766 #endif
16767             } else if (isdigit(*p)) {
16768                 emptycount = *p++ - '0';
16769                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16770                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16771                 while (emptycount--)
16772                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16773             } else if (*p == '+' || isalpha(*p)) {
16774                 if (j >= gameInfo.boardWidth) return FALSE;
16775                 if(*p=='+') {
16776                     piece = CharToPiece(*++p);
16777                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16778                     piece = (ChessSquare) (PROMOTED piece ); p++;
16779                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16780                 } else piece = CharToPiece(*p++);
16781
16782                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16783                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16784                     piece = (ChessSquare) (PROMOTED piece);
16785                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16786                     p++;
16787                 }
16788                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16789             } else {
16790                 return FALSE;
16791             }
16792         }
16793     }
16794     while (*p == '/' || *p == ' ') p++;
16795
16796     /* [HGM] look for Crazyhouse holdings here */
16797     while(*p==' ') p++;
16798     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16799         if(*p == '[') p++;
16800         if(*p == '-' ) p++; /* empty holdings */ else {
16801             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16802             /* if we would allow FEN reading to set board size, we would   */
16803             /* have to add holdings and shift the board read so far here   */
16804             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16805                 p++;
16806                 if((int) piece >= (int) BlackPawn ) {
16807                     i = (int)piece - (int)BlackPawn;
16808                     i = PieceToNumber((ChessSquare)i);
16809                     if( i >= gameInfo.holdingsSize ) return FALSE;
16810                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16811                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16812                 } else {
16813                     i = (int)piece - (int)WhitePawn;
16814                     i = PieceToNumber((ChessSquare)i);
16815                     if( i >= gameInfo.holdingsSize ) return FALSE;
16816                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16817                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16818                 }
16819             }
16820         }
16821         if(*p == ']') p++;
16822     }
16823
16824     while(*p == ' ') p++;
16825
16826     /* Active color */
16827     c = *p++;
16828     if(appData.colorNickNames) {
16829       if( c == appData.colorNickNames[0] ) c = 'w'; else
16830       if( c == appData.colorNickNames[1] ) c = 'b';
16831     }
16832     switch (c) {
16833       case 'w':
16834         *blackPlaysFirst = FALSE;
16835         break;
16836       case 'b':
16837         *blackPlaysFirst = TRUE;
16838         break;
16839       default:
16840         return FALSE;
16841     }
16842
16843     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16844     /* return the extra info in global variiables             */
16845
16846     /* set defaults in case FEN is incomplete */
16847     board[EP_STATUS] = EP_UNKNOWN;
16848     for(i=0; i<nrCastlingRights; i++ ) {
16849         board[CASTLING][i] =
16850             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16851     }   /* assume possible unless obviously impossible */
16852     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16853     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16854     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16855                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16856     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16857     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16858     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16859                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16860     FENrulePlies = 0;
16861
16862     while(*p==' ') p++;
16863     if(nrCastlingRights) {
16864       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16865           /* castling indicator present, so default becomes no castlings */
16866           for(i=0; i<nrCastlingRights; i++ ) {
16867                  board[CASTLING][i] = NoRights;
16868           }
16869       }
16870       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16871              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16872              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16873              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16874         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16875
16876         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16877             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16878             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16879         }
16880         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16881             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16882         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16883                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16884         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16885                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16886         switch(c) {
16887           case'K':
16888               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16889               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16890               board[CASTLING][2] = whiteKingFile;
16891               break;
16892           case'Q':
16893               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16894               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16895               board[CASTLING][2] = whiteKingFile;
16896               break;
16897           case'k':
16898               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16899               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16900               board[CASTLING][5] = blackKingFile;
16901               break;
16902           case'q':
16903               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16904               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16905               board[CASTLING][5] = blackKingFile;
16906           case '-':
16907               break;
16908           default: /* FRC castlings */
16909               if(c >= 'a') { /* black rights */
16910                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16911                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16912                   if(i == BOARD_RGHT) break;
16913                   board[CASTLING][5] = i;
16914                   c -= AAA;
16915                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16916                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16917                   if(c > i)
16918                       board[CASTLING][3] = c;
16919                   else
16920                       board[CASTLING][4] = c;
16921               } else { /* white rights */
16922                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16923                     if(board[0][i] == WhiteKing) break;
16924                   if(i == BOARD_RGHT) break;
16925                   board[CASTLING][2] = i;
16926                   c -= AAA - 'a' + 'A';
16927                   if(board[0][c] >= WhiteKing) break;
16928                   if(c > i)
16929                       board[CASTLING][0] = c;
16930                   else
16931                       board[CASTLING][1] = c;
16932               }
16933         }
16934       }
16935       for(i=0; i<nrCastlingRights; i++)
16936         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16937     if (appData.debugMode) {
16938         fprintf(debugFP, "FEN castling rights:");
16939         for(i=0; i<nrCastlingRights; i++)
16940         fprintf(debugFP, " %d", board[CASTLING][i]);
16941         fprintf(debugFP, "\n");
16942     }
16943
16944       while(*p==' ') p++;
16945     }
16946
16947     /* read e.p. field in games that know e.p. capture */
16948     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16949        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16950       if(*p=='-') {
16951         p++; board[EP_STATUS] = EP_NONE;
16952       } else {
16953          char c = *p++ - AAA;
16954
16955          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16956          if(*p >= '0' && *p <='9') p++;
16957          board[EP_STATUS] = c;
16958       }
16959     }
16960
16961
16962     if(sscanf(p, "%d", &i) == 1) {
16963         FENrulePlies = i; /* 50-move ply counter */
16964         /* (The move number is still ignored)    */
16965     }
16966
16967     return TRUE;
16968 }
16969
16970 void
16971 EditPositionPasteFEN (char *fen)
16972 {
16973   if (fen != NULL) {
16974     Board initial_position;
16975
16976     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16977       DisplayError(_("Bad FEN position in clipboard"), 0);
16978       return ;
16979     } else {
16980       int savedBlackPlaysFirst = blackPlaysFirst;
16981       EditPositionEvent();
16982       blackPlaysFirst = savedBlackPlaysFirst;
16983       CopyBoard(boards[0], initial_position);
16984       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16985       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16986       DisplayBothClocks();
16987       DrawPosition(FALSE, boards[currentMove]);
16988     }
16989   }
16990 }
16991
16992 static char cseq[12] = "\\   ";
16993
16994 Boolean
16995 set_cont_sequence (char *new_seq)
16996 {
16997     int len;
16998     Boolean ret;
16999
17000     // handle bad attempts to set the sequence
17001         if (!new_seq)
17002                 return 0; // acceptable error - no debug
17003
17004     len = strlen(new_seq);
17005     ret = (len > 0) && (len < sizeof(cseq));
17006     if (ret)
17007       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17008     else if (appData.debugMode)
17009       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17010     return ret;
17011 }
17012
17013 /*
17014     reformat a source message so words don't cross the width boundary.  internal
17015     newlines are not removed.  returns the wrapped size (no null character unless
17016     included in source message).  If dest is NULL, only calculate the size required
17017     for the dest buffer.  lp argument indicats line position upon entry, and it's
17018     passed back upon exit.
17019 */
17020 int
17021 wrap (char *dest, char *src, int count, int width, int *lp)
17022 {
17023     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17024
17025     cseq_len = strlen(cseq);
17026     old_line = line = *lp;
17027     ansi = len = clen = 0;
17028
17029     for (i=0; i < count; i++)
17030     {
17031         if (src[i] == '\033')
17032             ansi = 1;
17033
17034         // if we hit the width, back up
17035         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17036         {
17037             // store i & len in case the word is too long
17038             old_i = i, old_len = len;
17039
17040             // find the end of the last word
17041             while (i && src[i] != ' ' && src[i] != '\n')
17042             {
17043                 i--;
17044                 len--;
17045             }
17046
17047             // word too long?  restore i & len before splitting it
17048             if ((old_i-i+clen) >= width)
17049             {
17050                 i = old_i;
17051                 len = old_len;
17052             }
17053
17054             // extra space?
17055             if (i && src[i-1] == ' ')
17056                 len--;
17057
17058             if (src[i] != ' ' && src[i] != '\n')
17059             {
17060                 i--;
17061                 if (len)
17062                     len--;
17063             }
17064
17065             // now append the newline and continuation sequence
17066             if (dest)
17067                 dest[len] = '\n';
17068             len++;
17069             if (dest)
17070                 strncpy(dest+len, cseq, cseq_len);
17071             len += cseq_len;
17072             line = cseq_len;
17073             clen = cseq_len;
17074             continue;
17075         }
17076
17077         if (dest)
17078             dest[len] = src[i];
17079         len++;
17080         if (!ansi)
17081             line++;
17082         if (src[i] == '\n')
17083             line = 0;
17084         if (src[i] == 'm')
17085             ansi = 0;
17086     }
17087     if (dest && appData.debugMode)
17088     {
17089         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17090             count, width, line, len, *lp);
17091         show_bytes(debugFP, src, count);
17092         fprintf(debugFP, "\ndest: ");
17093         show_bytes(debugFP, dest, len);
17094         fprintf(debugFP, "\n");
17095     }
17096     *lp = dest ? line : old_line;
17097
17098     return len;
17099 }
17100
17101 // [HGM] vari: routines for shelving variations
17102 Boolean modeRestore = FALSE;
17103
17104 void
17105 PushInner (int firstMove, int lastMove)
17106 {
17107         int i, j, nrMoves = lastMove - firstMove;
17108
17109         // push current tail of game on stack
17110         savedResult[storedGames] = gameInfo.result;
17111         savedDetails[storedGames] = gameInfo.resultDetails;
17112         gameInfo.resultDetails = NULL;
17113         savedFirst[storedGames] = firstMove;
17114         savedLast [storedGames] = lastMove;
17115         savedFramePtr[storedGames] = framePtr;
17116         framePtr -= nrMoves; // reserve space for the boards
17117         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17118             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17119             for(j=0; j<MOVE_LEN; j++)
17120                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17121             for(j=0; j<2*MOVE_LEN; j++)
17122                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17123             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17124             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17125             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17126             pvInfoList[firstMove+i-1].depth = 0;
17127             commentList[framePtr+i] = commentList[firstMove+i];
17128             commentList[firstMove+i] = NULL;
17129         }
17130
17131         storedGames++;
17132         forwardMostMove = firstMove; // truncate game so we can start variation
17133 }
17134
17135 void
17136 PushTail (int firstMove, int lastMove)
17137 {
17138         if(appData.icsActive) { // only in local mode
17139                 forwardMostMove = currentMove; // mimic old ICS behavior
17140                 return;
17141         }
17142         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17143
17144         PushInner(firstMove, lastMove);
17145         if(storedGames == 1) GreyRevert(FALSE);
17146         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17147 }
17148
17149 void
17150 PopInner (Boolean annotate)
17151 {
17152         int i, j, nrMoves;
17153         char buf[8000], moveBuf[20];
17154
17155         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17156         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17157         nrMoves = savedLast[storedGames] - currentMove;
17158         if(annotate) {
17159                 int cnt = 10;
17160                 if(!WhiteOnMove(currentMove))
17161                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17162                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17163                 for(i=currentMove; i<forwardMostMove; i++) {
17164                         if(WhiteOnMove(i))
17165                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17166                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17167                         strcat(buf, moveBuf);
17168                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17169                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17170                 }
17171                 strcat(buf, ")");
17172         }
17173         for(i=1; i<=nrMoves; i++) { // copy last variation back
17174             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17175             for(j=0; j<MOVE_LEN; j++)
17176                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17177             for(j=0; j<2*MOVE_LEN; j++)
17178                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17179             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17180             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17181             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17182             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17183             commentList[currentMove+i] = commentList[framePtr+i];
17184             commentList[framePtr+i] = NULL;
17185         }
17186         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17187         framePtr = savedFramePtr[storedGames];
17188         gameInfo.result = savedResult[storedGames];
17189         if(gameInfo.resultDetails != NULL) {
17190             free(gameInfo.resultDetails);
17191       }
17192         gameInfo.resultDetails = savedDetails[storedGames];
17193         forwardMostMove = currentMove + nrMoves;
17194 }
17195
17196 Boolean
17197 PopTail (Boolean annotate)
17198 {
17199         if(appData.icsActive) return FALSE; // only in local mode
17200         if(!storedGames) return FALSE; // sanity
17201         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17202
17203         PopInner(annotate);
17204         if(currentMove < forwardMostMove) ForwardEvent(); else
17205         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17206
17207         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17208         return TRUE;
17209 }
17210
17211 void
17212 CleanupTail ()
17213 {       // remove all shelved variations
17214         int i;
17215         for(i=0; i<storedGames; i++) {
17216             if(savedDetails[i])
17217                 free(savedDetails[i]);
17218             savedDetails[i] = NULL;
17219         }
17220         for(i=framePtr; i<MAX_MOVES; i++) {
17221                 if(commentList[i]) free(commentList[i]);
17222                 commentList[i] = NULL;
17223         }
17224         framePtr = MAX_MOVES-1;
17225         storedGames = 0;
17226 }
17227
17228 void
17229 LoadVariation (int index, char *text)
17230 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17231         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17232         int level = 0, move;
17233
17234         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17235         // first find outermost bracketing variation
17236         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17237             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17238                 if(*p == '{') wait = '}'; else
17239                 if(*p == '[') wait = ']'; else
17240                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17241                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17242             }
17243             if(*p == wait) wait = NULLCHAR; // closing ]} found
17244             p++;
17245         }
17246         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17247         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17248         end[1] = NULLCHAR; // clip off comment beyond variation
17249         ToNrEvent(currentMove-1);
17250         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17251         // kludge: use ParsePV() to append variation to game
17252         move = currentMove;
17253         ParsePV(start, TRUE, TRUE);
17254         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17255         ClearPremoveHighlights();
17256         CommentPopDown();
17257         ToNrEvent(currentMove+1);
17258 }
17259