Make writing of move counts in PositionToFEN optional
[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, 2013 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 "evalgraph.h"
133 #include "gettext.h"
134
135 #ifdef ENABLE_NLS
136 # define _(s) gettext (s)
137 # define N_(s) gettext_noop (s)
138 # define T_(s) gettext(s)
139 #else
140 # ifdef WIN32
141 #   define _(s) T_(s)
142 #   define N_(s) s
143 # else
144 #   define _(s) (s)
145 #   define N_(s) s
146 #   define T_(s) s
147 # endif
148 #endif
149
150
151 int establish P((void));
152 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
153                          char *buf, int count, int error));
154 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
155                       char *buf, int count, int error));
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 void ToggleSecond P((void));
227 void PauseEngine P((ChessProgramState *cps));
228 static int NonStandardBoardSize P((void));
229
230 #ifdef WIN32
231        extern void ConsoleCreate();
232 #endif
233
234 ChessProgramState *WhitePlayer();
235 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
236 int VerifyDisplayMode P(());
237
238 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
239 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
240 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
241 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
242 void ics_update_width P((int new_width));
243 extern char installDir[MSG_SIZ];
244 VariantClass startVariant; /* [HGM] nicks: initial variant */
245 Boolean abortMatch;
246
247 extern int tinyLayout, smallLayout;
248 ChessProgramStats programStats;
249 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
250 int endPV = -1;
251 static int exiting = 0; /* [HGM] moved to top */
252 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
253 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
254 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
255 int partnerHighlight[2];
256 Boolean partnerBoardValid = 0;
257 char partnerStatus[MSG_SIZ];
258 Boolean partnerUp;
259 Boolean originalFlip;
260 Boolean twoBoards = 0;
261 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
262 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
263 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
264 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
265 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
266 int opponentKibitzes;
267 int lastSavedGame; /* [HGM] save: ID of game */
268 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
269 extern int chatCount;
270 int chattingPartner;
271 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
272 char lastMsg[MSG_SIZ];
273 ChessSquare pieceSweep = EmptySquare;
274 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
275 int promoDefaultAltered;
276 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
277
278 /* States for ics_getting_history */
279 #define H_FALSE 0
280 #define H_REQUESTED 1
281 #define H_GOT_REQ_HEADER 2
282 #define H_GOT_UNREQ_HEADER 3
283 #define H_GETTING_MOVES 4
284 #define H_GOT_UNWANTED_HEADER 5
285
286 /* whosays values for GameEnds */
287 #define GE_ICS 0
288 #define GE_ENGINE 1
289 #define GE_PLAYER 2
290 #define GE_FILE 3
291 #define GE_XBOARD 4
292 #define GE_ENGINE1 5
293 #define GE_ENGINE2 6
294
295 /* Maximum number of games in a cmail message */
296 #define CMAIL_MAX_GAMES 20
297
298 /* Different types of move when calling RegisterMove */
299 #define CMAIL_MOVE   0
300 #define CMAIL_RESIGN 1
301 #define CMAIL_DRAW   2
302 #define CMAIL_ACCEPT 3
303
304 /* Different types of result to remember for each game */
305 #define CMAIL_NOT_RESULT 0
306 #define CMAIL_OLD_RESULT 1
307 #define CMAIL_NEW_RESULT 2
308
309 /* Telnet protocol constants */
310 #define TN_WILL 0373
311 #define TN_WONT 0374
312 #define TN_DO   0375
313 #define TN_DONT 0376
314 #define TN_IAC  0377
315 #define TN_ECHO 0001
316 #define TN_SGA  0003
317 #define TN_PORT 23
318
319 char*
320 safeStrCpy (char *dst, const char *src, size_t count)
321 { // [HGM] made safe
322   int i;
323   assert( dst != NULL );
324   assert( src != NULL );
325   assert( count > 0 );
326
327   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
328   if(  i == count && dst[count-1] != NULLCHAR)
329     {
330       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
331       if(appData.debugMode)
332         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
333     }
334
335   return dst;
336 }
337
338 /* Some compiler can't cast u64 to double
339  * This function do the job for us:
340
341  * We use the highest bit for cast, this only
342  * works if the highest bit is not
343  * in use (This should not happen)
344  *
345  * We used this for all compiler
346  */
347 double
348 u64ToDouble (u64 value)
349 {
350   double r;
351   u64 tmp = value & u64Const(0x7fffffffffffffff);
352   r = (double)(s64)tmp;
353   if (value & u64Const(0x8000000000000000))
354        r +=  9.2233720368547758080e18; /* 2^63 */
355  return r;
356 }
357
358 /* Fake up flags for now, as we aren't keeping track of castling
359    availability yet. [HGM] Change of logic: the flag now only
360    indicates the type of castlings allowed by the rule of the game.
361    The actual rights themselves are maintained in the array
362    castlingRights, as part of the game history, and are not probed
363    by this function.
364  */
365 int
366 PosFlags (index)
367 {
368   int flags = F_ALL_CASTLE_OK;
369   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
370   switch (gameInfo.variant) {
371   case VariantSuicide:
372     flags &= ~F_ALL_CASTLE_OK;
373   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
374     flags |= F_IGNORE_CHECK;
375   case VariantLosers:
376     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
377     break;
378   case VariantAtomic:
379     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
380     break;
381   case VariantKriegspiel:
382     flags |= F_KRIEGSPIEL_CAPTURE;
383     break;
384   case VariantCapaRandom:
385   case VariantFischeRandom:
386     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
387   case VariantNoCastle:
388   case VariantShatranj:
389   case VariantCourier:
390   case VariantMakruk:
391   case VariantASEAN:
392   case VariantGrand:
393     flags &= ~F_ALL_CASTLE_OK;
394     break;
395   default:
396     break;
397   }
398   return flags;
399 }
400
401 FILE *gameFileFP, *debugFP, *serverFP;
402 char *currentDebugFile; // [HGM] debug split: to remember name
403
404 /*
405     [AS] Note: sometimes, the sscanf() function is used to parse the input
406     into a fixed-size buffer. Because of this, we must be prepared to
407     receive strings as long as the size of the input buffer, which is currently
408     set to 4K for Windows and 8K for the rest.
409     So, we must either allocate sufficiently large buffers here, or
410     reduce the size of the input buffer in the input reading part.
411 */
412
413 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
414 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
415 char thinkOutput1[MSG_SIZ*10];
416
417 ChessProgramState first, second, pairing;
418
419 /* premove variables */
420 int premoveToX = 0;
421 int premoveToY = 0;
422 int premoveFromX = 0;
423 int premoveFromY = 0;
424 int premovePromoChar = 0;
425 int gotPremove = 0;
426 Boolean alarmSounded;
427 /* end premove variables */
428
429 char *ics_prefix = "$";
430 enum ICS_TYPE ics_type = ICS_GENERIC;
431
432 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
433 int pauseExamForwardMostMove = 0;
434 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
435 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
436 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
437 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
438 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
439 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
440 int whiteFlag = FALSE, blackFlag = FALSE;
441 int userOfferedDraw = FALSE;
442 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
443 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
444 int cmailMoveType[CMAIL_MAX_GAMES];
445 long ics_clock_paused = 0;
446 ProcRef icsPR = NoProc, cmailPR = NoProc;
447 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
448 GameMode gameMode = BeginningOfGame;
449 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
450 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
451 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
452 int hiddenThinkOutputState = 0; /* [AS] */
453 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
454 int adjudicateLossPlies = 6;
455 char white_holding[64], black_holding[64];
456 TimeMark lastNodeCountTime;
457 long lastNodeCount=0;
458 int shiftKey, controlKey; // [HGM] set by mouse handler
459
460 int have_sent_ICS_logon = 0;
461 int movesPerSession;
462 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
463 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
464 Boolean adjustedClock;
465 long timeControl_2; /* [AS] Allow separate time controls */
466 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
467 long timeRemaining[2][MAX_MOVES];
468 int matchGame = 0, nextGame = 0, roundNr = 0;
469 Boolean waitingForGame = FALSE, startingEngine = FALSE;
470 TimeMark programStartTime, pauseStart;
471 char ics_handle[MSG_SIZ];
472 int have_set_title = 0;
473
474 /* animateTraining preserves the state of appData.animate
475  * when Training mode is activated. This allows the
476  * response to be animated when appData.animate == TRUE and
477  * appData.animateDragging == TRUE.
478  */
479 Boolean animateTraining;
480
481 GameInfo gameInfo;
482
483 AppData appData;
484
485 Board boards[MAX_MOVES];
486 /* [HGM] Following 7 needed for accurate legality tests: */
487 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
488 signed char  initialRights[BOARD_FILES];
489 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
490 int   initialRulePlies, FENrulePlies;
491 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
492 int loadFlag = 0;
493 Boolean shuffleOpenings;
494 int mute; // mute all sounds
495
496 // [HGM] vari: next 12 to save and restore variations
497 #define MAX_VARIATIONS 10
498 int framePtr = MAX_MOVES-1; // points to free stack entry
499 int storedGames = 0;
500 int savedFirst[MAX_VARIATIONS];
501 int savedLast[MAX_VARIATIONS];
502 int savedFramePtr[MAX_VARIATIONS];
503 char *savedDetails[MAX_VARIATIONS];
504 ChessMove savedResult[MAX_VARIATIONS];
505
506 void PushTail P((int firstMove, int lastMove));
507 Boolean PopTail P((Boolean annotate));
508 void PushInner P((int firstMove, int lastMove));
509 void PopInner P((Boolean annotate));
510 void CleanupTail P((void));
511
512 ChessSquare  FIDEArray[2][BOARD_FILES] = {
513     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
514         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
515     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
516         BlackKing, BlackBishop, BlackKnight, BlackRook }
517 };
518
519 ChessSquare twoKingsArray[2][BOARD_FILES] = {
520     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
521         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
522     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
523         BlackKing, BlackKing, BlackKnight, BlackRook }
524 };
525
526 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
527     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
528         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
529     { BlackRook, BlackMan, BlackBishop, BlackQueen,
530         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
531 };
532
533 ChessSquare SpartanArray[2][BOARD_FILES] = {
534     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
535         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
536     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
537         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
538 };
539
540 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
542         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
543     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
544         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
545 };
546
547 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
548     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
549         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
551         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
552 };
553
554 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
555     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
556         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
557     { BlackRook, BlackKnight, BlackMan, BlackFerz,
558         BlackKing, BlackMan, BlackKnight, BlackRook }
559 };
560
561 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
562     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
563         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
564     { BlackRook, BlackKnight, BlackMan, BlackFerz,
565         BlackKing, BlackMan, BlackKnight, BlackRook }
566 };
567
568
569 #if (BOARD_FILES>=10)
570 ChessSquare ShogiArray[2][BOARD_FILES] = {
571     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
572         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
573     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
574         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
575 };
576
577 ChessSquare XiangqiArray[2][BOARD_FILES] = {
578     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
579         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
580     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
581         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
582 };
583
584 ChessSquare CapablancaArray[2][BOARD_FILES] = {
585     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
586         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
587     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
588         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
589 };
590
591 ChessSquare GreatArray[2][BOARD_FILES] = {
592     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
593         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
594     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
595         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
596 };
597
598 ChessSquare JanusArray[2][BOARD_FILES] = {
599     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
600         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
601     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
602         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
603 };
604
605 ChessSquare GrandArray[2][BOARD_FILES] = {
606     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
607         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
608     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
609         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
610 };
611
612 #ifdef GOTHIC
613 ChessSquare GothicArray[2][BOARD_FILES] = {
614     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
615         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
616     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
617         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
618 };
619 #else // !GOTHIC
620 #define GothicArray CapablancaArray
621 #endif // !GOTHIC
622
623 #ifdef FALCON
624 ChessSquare FalconArray[2][BOARD_FILES] = {
625     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
626         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
627     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
628         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
629 };
630 #else // !FALCON
631 #define FalconArray CapablancaArray
632 #endif // !FALCON
633
634 #else // !(BOARD_FILES>=10)
635 #define XiangqiPosition FIDEArray
636 #define CapablancaArray FIDEArray
637 #define GothicArray FIDEArray
638 #define GreatArray FIDEArray
639 #endif // !(BOARD_FILES>=10)
640
641 #if (BOARD_FILES>=12)
642 ChessSquare CourierArray[2][BOARD_FILES] = {
643     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
644         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
645     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
646         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
647 };
648 #else // !(BOARD_FILES>=12)
649 #define CourierArray CapablancaArray
650 #endif // !(BOARD_FILES>=12)
651
652
653 Board initialPosition;
654
655
656 /* Convert str to a rating. Checks for special cases of "----",
657
658    "++++", etc. Also strips ()'s */
659 int
660 string_to_rating (char *str)
661 {
662   while(*str && !isdigit(*str)) ++str;
663   if (!*str)
664     return 0;   /* One of the special "no rating" cases */
665   else
666     return atoi(str);
667 }
668
669 void
670 ClearProgramStats ()
671 {
672     /* Init programStats */
673     programStats.movelist[0] = 0;
674     programStats.depth = 0;
675     programStats.nr_moves = 0;
676     programStats.moves_left = 0;
677     programStats.nodes = 0;
678     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
679     programStats.score = 0;
680     programStats.got_only_move = 0;
681     programStats.got_fail = 0;
682     programStats.line_is_book = 0;
683 }
684
685 void
686 CommonEngineInit ()
687 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
688     if (appData.firstPlaysBlack) {
689         first.twoMachinesColor = "black\n";
690         second.twoMachinesColor = "white\n";
691     } else {
692         first.twoMachinesColor = "white\n";
693         second.twoMachinesColor = "black\n";
694     }
695
696     first.other = &second;
697     second.other = &first;
698
699     { float norm = 1;
700         if(appData.timeOddsMode) {
701             norm = appData.timeOdds[0];
702             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
703         }
704         first.timeOdds  = appData.timeOdds[0]/norm;
705         second.timeOdds = appData.timeOdds[1]/norm;
706     }
707
708     if(programVersion) free(programVersion);
709     if (appData.noChessProgram) {
710         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
711         sprintf(programVersion, "%s", PACKAGE_STRING);
712     } else {
713       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
714       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
715       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
716     }
717 }
718
719 void
720 UnloadEngine (ChessProgramState *cps)
721 {
722         /* Kill off first chess program */
723         if (cps->isr != NULL)
724           RemoveInputSource(cps->isr);
725         cps->isr = NULL;
726
727         if (cps->pr != NoProc) {
728             ExitAnalyzeMode();
729             DoSleep( appData.delayBeforeQuit );
730             SendToProgram("quit\n", cps);
731             DoSleep( appData.delayAfterQuit );
732             DestroyChildProcess(cps->pr, cps->useSigterm);
733         }
734         cps->pr = NoProc;
735         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
736 }
737
738 void
739 ClearOptions (ChessProgramState *cps)
740 {
741     int i;
742     cps->nrOptions = cps->comboCnt = 0;
743     for(i=0; i<MAX_OPTIONS; i++) {
744         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
745         cps->option[i].textValue = 0;
746     }
747 }
748
749 char *engineNames[] = {
750   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
751      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
752 N_("first"),
753   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
754      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
755 N_("second")
756 };
757
758 void
759 InitEngine (ChessProgramState *cps, int n)
760 {   // [HGM] all engine initialiation put in a function that does one engine
761
762     ClearOptions(cps);
763
764     cps->which = engineNames[n];
765     cps->maybeThinking = FALSE;
766     cps->pr = NoProc;
767     cps->isr = NULL;
768     cps->sendTime = 2;
769     cps->sendDrawOffers = 1;
770
771     cps->program = appData.chessProgram[n];
772     cps->host = appData.host[n];
773     cps->dir = appData.directory[n];
774     cps->initString = appData.engInitString[n];
775     cps->computerString = appData.computerString[n];
776     cps->useSigint  = TRUE;
777     cps->useSigterm = TRUE;
778     cps->reuse = appData.reuse[n];
779     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
780     cps->useSetboard = FALSE;
781     cps->useSAN = FALSE;
782     cps->usePing = FALSE;
783     cps->lastPing = 0;
784     cps->lastPong = 0;
785     cps->usePlayother = FALSE;
786     cps->useColors = TRUE;
787     cps->useUsermove = FALSE;
788     cps->sendICS = FALSE;
789     cps->sendName = appData.icsActive;
790     cps->sdKludge = FALSE;
791     cps->stKludge = FALSE;
792     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
793     TidyProgramName(cps->program, cps->host, cps->tidy);
794     cps->matchWins = 0;
795     ASSIGN(cps->variants, appData.variant);
796     cps->analysisSupport = 2; /* detect */
797     cps->analyzing = FALSE;
798     cps->initDone = FALSE;
799     cps->reload = FALSE;
800
801     /* New features added by Tord: */
802     cps->useFEN960 = FALSE;
803     cps->useOOCastle = TRUE;
804     /* End of new features added by Tord. */
805     cps->fenOverride  = appData.fenOverride[n];
806
807     /* [HGM] time odds: set factor for each machine */
808     cps->timeOdds  = appData.timeOdds[n];
809
810     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
811     cps->accumulateTC = appData.accumulateTC[n];
812     cps->maxNrOfSessions = 1;
813
814     /* [HGM] debug */
815     cps->debug = FALSE;
816
817     cps->supportsNPS = UNKNOWN;
818     cps->memSize = FALSE;
819     cps->maxCores = FALSE;
820     ASSIGN(cps->egtFormats, "");
821
822     /* [HGM] options */
823     cps->optionSettings  = appData.engOptions[n];
824
825     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
826     cps->isUCI = appData.isUCI[n]; /* [AS] */
827     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
828
829     if (appData.protocolVersion[n] > PROTOVER
830         || appData.protocolVersion[n] < 1)
831       {
832         char buf[MSG_SIZ];
833         int len;
834
835         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
836                        appData.protocolVersion[n]);
837         if( (len >= MSG_SIZ) && appData.debugMode )
838           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
839
840         DisplayFatalError(buf, 0, 2);
841       }
842     else
843       {
844         cps->protocolVersion = appData.protocolVersion[n];
845       }
846
847     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
848     ParseFeatures(appData.featureDefaults, cps);
849 }
850
851 ChessProgramState *savCps;
852
853 GameMode oldMode;
854
855 void
856 LoadEngine ()
857 {
858     int i;
859     if(WaitForEngine(savCps, LoadEngine)) return;
860     CommonEngineInit(); // recalculate time odds
861     if(gameInfo.variant != StringToVariant(appData.variant)) {
862         // we changed variant when loading the engine; this forces us to reset
863         Reset(TRUE, savCps != &first);
864         oldMode = BeginningOfGame; // to prevent restoring old mode
865     }
866     InitChessProgram(savCps, FALSE);
867     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
868     DisplayMessage("", "");
869     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
870     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
871     ThawUI();
872     SetGNUMode();
873     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
874 }
875
876 void
877 ReplaceEngine (ChessProgramState *cps, int n)
878 {
879     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
880     keepInfo = 1;
881     if(oldMode != BeginningOfGame) EditGameEvent();
882     keepInfo = 0;
883     UnloadEngine(cps);
884     appData.noChessProgram = FALSE;
885     appData.clockMode = TRUE;
886     InitEngine(cps, n);
887     UpdateLogos(TRUE);
888     if(n) return; // only startup first engine immediately; second can wait
889     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
890     LoadEngine();
891 }
892
893 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
894 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
895
896 static char resetOptions[] =
897         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
898         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
899         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
900         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
901
902 void
903 FloatToFront(char **list, char *engineLine)
904 {
905     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
906     int i=0;
907     if(appData.recentEngines <= 0) return;
908     TidyProgramName(engineLine, "localhost", tidy+1);
909     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
910     strncpy(buf+1, *list, MSG_SIZ-50);
911     if(p = strstr(buf, tidy)) { // tidy name appears in list
912         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
913         while(*p++ = *++q); // squeeze out
914     }
915     strcat(tidy, buf+1); // put list behind tidy name
916     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
917     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
918     ASSIGN(*list, tidy+1);
919 }
920
921 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
922
923 void
924 Load (ChessProgramState *cps, int i)
925 {
926     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
927     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
928         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
929         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
930         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
931         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
932         appData.firstProtocolVersion = PROTOVER;
933         ParseArgsFromString(buf);
934         SwapEngines(i);
935         ReplaceEngine(cps, i);
936         FloatToFront(&appData.recentEngineList, engineLine);
937         return;
938     }
939     p = engineName;
940     while(q = strchr(p, SLASH)) p = q+1;
941     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
942     if(engineDir[0] != NULLCHAR) {
943         ASSIGN(appData.directory[i], engineDir); p = engineName;
944     } else if(p != engineName) { // derive directory from engine path, when not given
945         p[-1] = 0;
946         ASSIGN(appData.directory[i], engineName);
947         p[-1] = SLASH;
948         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
949     } else { ASSIGN(appData.directory[i], "."); }
950     if(params[0]) {
951         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
952         snprintf(command, MSG_SIZ, "%s %s", p, params);
953         p = command;
954     }
955     ASSIGN(appData.chessProgram[i], p);
956     appData.isUCI[i] = isUCI;
957     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
958     appData.hasOwnBookUCI[i] = hasBook;
959     if(!nickName[0]) useNick = FALSE;
960     if(useNick) ASSIGN(appData.pgnName[i], nickName);
961     if(addToList) {
962         int len;
963         char quote;
964         q = firstChessProgramNames;
965         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
966         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
967         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
968                         quote, p, quote, appData.directory[i],
969                         useNick ? " -fn \"" : "",
970                         useNick ? nickName : "",
971                         useNick ? "\"" : "",
972                         v1 ? " -firstProtocolVersion 1" : "",
973                         hasBook ? "" : " -fNoOwnBookUCI",
974                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
975                         storeVariant ? " -variant " : "",
976                         storeVariant ? VariantName(gameInfo.variant) : "");
977         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
978         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
979         if(insert != q) insert[-1] = NULLCHAR;
980         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
981         if(q)   free(q);
982         FloatToFront(&appData.recentEngineList, buf);
983     }
984     ReplaceEngine(cps, i);
985 }
986
987 void
988 InitTimeControls ()
989 {
990     int matched, min, sec;
991     /*
992      * Parse timeControl resource
993      */
994     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
995                           appData.movesPerSession)) {
996         char buf[MSG_SIZ];
997         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
998         DisplayFatalError(buf, 0, 2);
999     }
1000
1001     /*
1002      * Parse searchTime resource
1003      */
1004     if (*appData.searchTime != NULLCHAR) {
1005         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1006         if (matched == 1) {
1007             searchTime = min * 60;
1008         } else if (matched == 2) {
1009             searchTime = min * 60 + sec;
1010         } else {
1011             char buf[MSG_SIZ];
1012             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1013             DisplayFatalError(buf, 0, 2);
1014         }
1015     }
1016 }
1017
1018 void
1019 InitBackEnd1 ()
1020 {
1021
1022     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1023     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1024
1025     GetTimeMark(&programStartTime);
1026     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1027     appData.seedBase = random() + (random()<<15);
1028     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1029
1030     ClearProgramStats();
1031     programStats.ok_to_send = 1;
1032     programStats.seen_stat = 0;
1033
1034     /*
1035      * Initialize game list
1036      */
1037     ListNew(&gameList);
1038
1039
1040     /*
1041      * Internet chess server status
1042      */
1043     if (appData.icsActive) {
1044         appData.matchMode = FALSE;
1045         appData.matchGames = 0;
1046 #if ZIPPY
1047         appData.noChessProgram = !appData.zippyPlay;
1048 #else
1049         appData.zippyPlay = FALSE;
1050         appData.zippyTalk = FALSE;
1051         appData.noChessProgram = TRUE;
1052 #endif
1053         if (*appData.icsHelper != NULLCHAR) {
1054             appData.useTelnet = TRUE;
1055             appData.telnetProgram = appData.icsHelper;
1056         }
1057     } else {
1058         appData.zippyTalk = appData.zippyPlay = FALSE;
1059     }
1060
1061     /* [AS] Initialize pv info list [HGM] and game state */
1062     {
1063         int i, j;
1064
1065         for( i=0; i<=framePtr; i++ ) {
1066             pvInfoList[i].depth = -1;
1067             boards[i][EP_STATUS] = EP_NONE;
1068             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1069         }
1070     }
1071
1072     InitTimeControls();
1073
1074     /* [AS] Adjudication threshold */
1075     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1076
1077     InitEngine(&first, 0);
1078     InitEngine(&second, 1);
1079     CommonEngineInit();
1080
1081     pairing.which = "pairing"; // pairing engine
1082     pairing.pr = NoProc;
1083     pairing.isr = NULL;
1084     pairing.program = appData.pairingEngine;
1085     pairing.host = "localhost";
1086     pairing.dir = ".";
1087
1088     if (appData.icsActive) {
1089         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1090     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1091         appData.clockMode = FALSE;
1092         first.sendTime = second.sendTime = 0;
1093     }
1094
1095 #if ZIPPY
1096     /* Override some settings from environment variables, for backward
1097        compatibility.  Unfortunately it's not feasible to have the env
1098        vars just set defaults, at least in xboard.  Ugh.
1099     */
1100     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1101       ZippyInit();
1102     }
1103 #endif
1104
1105     if (!appData.icsActive) {
1106       char buf[MSG_SIZ];
1107       int len;
1108
1109       /* Check for variants that are supported only in ICS mode,
1110          or not at all.  Some that are accepted here nevertheless
1111          have bugs; see comments below.
1112       */
1113       VariantClass variant = StringToVariant(appData.variant);
1114       switch (variant) {
1115       case VariantBughouse:     /* need four players and two boards */
1116       case VariantKriegspiel:   /* need to hide pieces and move details */
1117         /* case VariantFischeRandom: (Fabien: moved below) */
1118         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1119         if( (len >= MSG_SIZ) && appData.debugMode )
1120           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1121
1122         DisplayFatalError(buf, 0, 2);
1123         return;
1124
1125       case VariantUnknown:
1126       case VariantLoadable:
1127       case Variant29:
1128       case Variant30:
1129       case Variant31:
1130       case Variant32:
1131       case Variant33:
1132       case Variant34:
1133       case Variant35:
1134       case Variant36:
1135       default:
1136         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1137         if( (len >= MSG_SIZ) && appData.debugMode )
1138           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1139
1140         DisplayFatalError(buf, 0, 2);
1141         return;
1142
1143       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1144       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1145       case VariantGothic:     /* [HGM] should work */
1146       case VariantCapablanca: /* [HGM] should work */
1147       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1148       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1149       case VariantKnightmate: /* [HGM] should work */
1150       case VariantCylinder:   /* [HGM] untested */
1151       case VariantFalcon:     /* [HGM] untested */
1152       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1153                                  offboard interposition not understood */
1154       case VariantNormal:     /* definitely works! */
1155       case VariantWildCastle: /* pieces not automatically shuffled */
1156       case VariantNoCastle:   /* pieces not automatically shuffled */
1157       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1158       case VariantLosers:     /* should work except for win condition,
1159                                  and doesn't know captures are mandatory */
1160       case VariantSuicide:    /* should work except for win condition,
1161                                  and doesn't know captures are mandatory */
1162       case VariantGiveaway:   /* should work except for win condition,
1163                                  and doesn't know captures are mandatory */
1164       case VariantTwoKings:   /* should work */
1165       case VariantAtomic:     /* should work except for win condition */
1166       case Variant3Check:     /* should work except for win condition */
1167       case VariantShatranj:   /* should work except for all win conditions */
1168       case VariantMakruk:     /* should work except for draw countdown */
1169       case VariantASEAN :     /* should work except for draw countdown */
1170       case VariantBerolina:   /* might work if TestLegality is off */
1171       case VariantCapaRandom: /* should work */
1172       case VariantJanus:      /* should work */
1173       case VariantSuper:      /* experimental */
1174       case VariantGreat:      /* experimental, requires legality testing to be off */
1175       case VariantSChess:     /* S-Chess, should work */
1176       case VariantGrand:      /* should work */
1177       case VariantSpartan:    /* should work */
1178         break;
1179       }
1180     }
1181
1182 }
1183
1184 int
1185 NextIntegerFromString (char ** str, long * value)
1186 {
1187     int result = -1;
1188     char * s = *str;
1189
1190     while( *s == ' ' || *s == '\t' ) {
1191         s++;
1192     }
1193
1194     *value = 0;
1195
1196     if( *s >= '0' && *s <= '9' ) {
1197         while( *s >= '0' && *s <= '9' ) {
1198             *value = *value * 10 + (*s - '0');
1199             s++;
1200         }
1201
1202         result = 0;
1203     }
1204
1205     *str = s;
1206
1207     return result;
1208 }
1209
1210 int
1211 NextTimeControlFromString (char ** str, long * value)
1212 {
1213     long temp;
1214     int result = NextIntegerFromString( str, &temp );
1215
1216     if( result == 0 ) {
1217         *value = temp * 60; /* Minutes */
1218         if( **str == ':' ) {
1219             (*str)++;
1220             result = NextIntegerFromString( str, &temp );
1221             *value += temp; /* Seconds */
1222         }
1223     }
1224
1225     return result;
1226 }
1227
1228 int
1229 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1230 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1231     int result = -1, type = 0; long temp, temp2;
1232
1233     if(**str != ':') return -1; // old params remain in force!
1234     (*str)++;
1235     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1236     if( NextIntegerFromString( str, &temp ) ) return -1;
1237     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1238
1239     if(**str != '/') {
1240         /* time only: incremental or sudden-death time control */
1241         if(**str == '+') { /* increment follows; read it */
1242             (*str)++;
1243             if(**str == '!') type = *(*str)++; // Bronstein TC
1244             if(result = NextIntegerFromString( str, &temp2)) return -1;
1245             *inc = temp2 * 1000;
1246             if(**str == '.') { // read fraction of increment
1247                 char *start = ++(*str);
1248                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1249                 temp2 *= 1000;
1250                 while(start++ < *str) temp2 /= 10;
1251                 *inc += temp2;
1252             }
1253         } else *inc = 0;
1254         *moves = 0; *tc = temp * 1000; *incType = type;
1255         return 0;
1256     }
1257
1258     (*str)++; /* classical time control */
1259     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1260
1261     if(result == 0) {
1262         *moves = temp;
1263         *tc    = temp2 * 1000;
1264         *inc   = 0;
1265         *incType = type;
1266     }
1267     return result;
1268 }
1269
1270 int
1271 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1272 {   /* [HGM] get time to add from the multi-session time-control string */
1273     int incType, moves=1; /* kludge to force reading of first session */
1274     long time, increment;
1275     char *s = tcString;
1276
1277     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1278     do {
1279         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1280         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1281         if(movenr == -1) return time;    /* last move before new session     */
1282         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1283         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1284         if(!moves) return increment;     /* current session is incremental   */
1285         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1286     } while(movenr >= -1);               /* try again for next session       */
1287
1288     return 0; // no new time quota on this move
1289 }
1290
1291 int
1292 ParseTimeControl (char *tc, float ti, int mps)
1293 {
1294   long tc1;
1295   long tc2;
1296   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1297   int min, sec=0;
1298
1299   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1300   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1301       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1302   if(ti > 0) {
1303
1304     if(mps)
1305       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1306     else
1307       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1308   } else {
1309     if(mps)
1310       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1311     else
1312       snprintf(buf, MSG_SIZ, ":%s", mytc);
1313   }
1314   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1315
1316   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1317     return FALSE;
1318   }
1319
1320   if( *tc == '/' ) {
1321     /* Parse second time control */
1322     tc++;
1323
1324     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1325       return FALSE;
1326     }
1327
1328     if( tc2 == 0 ) {
1329       return FALSE;
1330     }
1331
1332     timeControl_2 = tc2 * 1000;
1333   }
1334   else {
1335     timeControl_2 = 0;
1336   }
1337
1338   if( tc1 == 0 ) {
1339     return FALSE;
1340   }
1341
1342   timeControl = tc1 * 1000;
1343
1344   if (ti >= 0) {
1345     timeIncrement = ti * 1000;  /* convert to ms */
1346     movesPerSession = 0;
1347   } else {
1348     timeIncrement = 0;
1349     movesPerSession = mps;
1350   }
1351   return TRUE;
1352 }
1353
1354 void
1355 InitBackEnd2 ()
1356 {
1357     if (appData.debugMode) {
1358 #    ifdef __GIT_VERSION
1359       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1360 #    else
1361       fprintf(debugFP, "Version: %s\n", programVersion);
1362 #    endif
1363     }
1364     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1365
1366     set_cont_sequence(appData.wrapContSeq);
1367     if (appData.matchGames > 0) {
1368         appData.matchMode = TRUE;
1369     } else if (appData.matchMode) {
1370         appData.matchGames = 1;
1371     }
1372     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1373         appData.matchGames = appData.sameColorGames;
1374     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1375         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1376         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1377     }
1378     Reset(TRUE, FALSE);
1379     if (appData.noChessProgram || first.protocolVersion == 1) {
1380       InitBackEnd3();
1381     } else {
1382       /* kludge: allow timeout for initial "feature" commands */
1383       FreezeUI();
1384       DisplayMessage("", _("Starting chess program"));
1385       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1386     }
1387 }
1388
1389 int
1390 CalculateIndex (int index, int gameNr)
1391 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1392     int res;
1393     if(index > 0) return index; // fixed nmber
1394     if(index == 0) return 1;
1395     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1396     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1397     return res;
1398 }
1399
1400 int
1401 LoadGameOrPosition (int gameNr)
1402 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1403     if (*appData.loadGameFile != NULLCHAR) {
1404         if (!LoadGameFromFile(appData.loadGameFile,
1405                 CalculateIndex(appData.loadGameIndex, gameNr),
1406                               appData.loadGameFile, FALSE)) {
1407             DisplayFatalError(_("Bad game file"), 0, 1);
1408             return 0;
1409         }
1410     } else if (*appData.loadPositionFile != NULLCHAR) {
1411         if (!LoadPositionFromFile(appData.loadPositionFile,
1412                 CalculateIndex(appData.loadPositionIndex, gameNr),
1413                                   appData.loadPositionFile)) {
1414             DisplayFatalError(_("Bad position file"), 0, 1);
1415             return 0;
1416         }
1417     }
1418     return 1;
1419 }
1420
1421 void
1422 ReserveGame (int gameNr, char resChar)
1423 {
1424     FILE *tf = fopen(appData.tourneyFile, "r+");
1425     char *p, *q, c, buf[MSG_SIZ];
1426     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1427     safeStrCpy(buf, lastMsg, MSG_SIZ);
1428     DisplayMessage(_("Pick new game"), "");
1429     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1430     ParseArgsFromFile(tf);
1431     p = q = appData.results;
1432     if(appData.debugMode) {
1433       char *r = appData.participants;
1434       fprintf(debugFP, "results = '%s'\n", p);
1435       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1436       fprintf(debugFP, "\n");
1437     }
1438     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1439     nextGame = q - p;
1440     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1441     safeStrCpy(q, p, strlen(p) + 2);
1442     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1443     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1444     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1445         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1446         q[nextGame] = '*';
1447     }
1448     fseek(tf, -(strlen(p)+4), SEEK_END);
1449     c = fgetc(tf);
1450     if(c != '"') // depending on DOS or Unix line endings we can be one off
1451          fseek(tf, -(strlen(p)+2), SEEK_END);
1452     else fseek(tf, -(strlen(p)+3), SEEK_END);
1453     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1454     DisplayMessage(buf, "");
1455     free(p); appData.results = q;
1456     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1457        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1458       int round = appData.defaultMatchGames * appData.tourneyType;
1459       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1460          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1461         UnloadEngine(&first);  // next game belongs to other pairing;
1462         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1463     }
1464     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1465 }
1466
1467 void
1468 MatchEvent (int mode)
1469 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1470         int dummy;
1471         if(matchMode) { // already in match mode: switch it off
1472             abortMatch = TRUE;
1473             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1474             return;
1475         }
1476 //      if(gameMode != BeginningOfGame) {
1477 //          DisplayError(_("You can only start a match from the initial position."), 0);
1478 //          return;
1479 //      }
1480         abortMatch = FALSE;
1481         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1482         /* Set up machine vs. machine match */
1483         nextGame = 0;
1484         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1485         if(appData.tourneyFile[0]) {
1486             ReserveGame(-1, 0);
1487             if(nextGame > appData.matchGames) {
1488                 char buf[MSG_SIZ];
1489                 if(strchr(appData.results, '*') == NULL) {
1490                     FILE *f;
1491                     appData.tourneyCycles++;
1492                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1493                         fclose(f);
1494                         NextTourneyGame(-1, &dummy);
1495                         ReserveGame(-1, 0);
1496                         if(nextGame <= appData.matchGames) {
1497                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1498                             matchMode = mode;
1499                             ScheduleDelayedEvent(NextMatchGame, 10000);
1500                             return;
1501                         }
1502                     }
1503                 }
1504                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1505                 DisplayError(buf, 0);
1506                 appData.tourneyFile[0] = 0;
1507                 return;
1508             }
1509         } else
1510         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1511             DisplayFatalError(_("Can't have a match with no chess programs"),
1512                               0, 2);
1513             return;
1514         }
1515         matchMode = mode;
1516         matchGame = roundNr = 1;
1517         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1518         NextMatchGame();
1519 }
1520
1521 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1522
1523 void
1524 InitBackEnd3 P((void))
1525 {
1526     GameMode initialMode;
1527     char buf[MSG_SIZ];
1528     int err, len;
1529
1530     InitChessProgram(&first, startedFromSetupPosition);
1531
1532     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1533         free(programVersion);
1534         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1535         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1536         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1537     }
1538
1539     if (appData.icsActive) {
1540 #ifdef WIN32
1541         /* [DM] Make a console window if needed [HGM] merged ifs */
1542         ConsoleCreate();
1543 #endif
1544         err = establish();
1545         if (err != 0)
1546           {
1547             if (*appData.icsCommPort != NULLCHAR)
1548               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1549                              appData.icsCommPort);
1550             else
1551               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1552                         appData.icsHost, appData.icsPort);
1553
1554             if( (len >= MSG_SIZ) && appData.debugMode )
1555               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1556
1557             DisplayFatalError(buf, err, 1);
1558             return;
1559         }
1560         SetICSMode();
1561         telnetISR =
1562           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1563         fromUserISR =
1564           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1565         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1566             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1567     } else if (appData.noChessProgram) {
1568         SetNCPMode();
1569     } else {
1570         SetGNUMode();
1571     }
1572
1573     if (*appData.cmailGameName != NULLCHAR) {
1574         SetCmailMode();
1575         OpenLoopback(&cmailPR);
1576         cmailISR =
1577           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1578     }
1579
1580     ThawUI();
1581     DisplayMessage("", "");
1582     if (StrCaseCmp(appData.initialMode, "") == 0) {
1583       initialMode = BeginningOfGame;
1584       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1585         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1586         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1587         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1588         ModeHighlight();
1589       }
1590     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1591       initialMode = TwoMachinesPlay;
1592     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1593       initialMode = AnalyzeFile;
1594     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1595       initialMode = AnalyzeMode;
1596     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1597       initialMode = MachinePlaysWhite;
1598     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1599       initialMode = MachinePlaysBlack;
1600     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1601       initialMode = EditGame;
1602     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1603       initialMode = EditPosition;
1604     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1605       initialMode = Training;
1606     } else {
1607       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1608       if( (len >= MSG_SIZ) && appData.debugMode )
1609         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1610
1611       DisplayFatalError(buf, 0, 2);
1612       return;
1613     }
1614
1615     if (appData.matchMode) {
1616         if(appData.tourneyFile[0]) { // start tourney from command line
1617             FILE *f;
1618             if(f = fopen(appData.tourneyFile, "r")) {
1619                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1620                 fclose(f);
1621                 appData.clockMode = TRUE;
1622                 SetGNUMode();
1623             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1624         }
1625         MatchEvent(TRUE);
1626     } else if (*appData.cmailGameName != NULLCHAR) {
1627         /* Set up cmail mode */
1628         ReloadCmailMsgEvent(TRUE);
1629     } else {
1630         /* Set up other modes */
1631         if (initialMode == AnalyzeFile) {
1632           if (*appData.loadGameFile == NULLCHAR) {
1633             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1634             return;
1635           }
1636         }
1637         if (*appData.loadGameFile != NULLCHAR) {
1638             (void) LoadGameFromFile(appData.loadGameFile,
1639                                     appData.loadGameIndex,
1640                                     appData.loadGameFile, TRUE);
1641         } else if (*appData.loadPositionFile != NULLCHAR) {
1642             (void) LoadPositionFromFile(appData.loadPositionFile,
1643                                         appData.loadPositionIndex,
1644                                         appData.loadPositionFile);
1645             /* [HGM] try to make self-starting even after FEN load */
1646             /* to allow automatic setup of fairy variants with wtm */
1647             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1648                 gameMode = BeginningOfGame;
1649                 setboardSpoiledMachineBlack = 1;
1650             }
1651             /* [HGM] loadPos: make that every new game uses the setup */
1652             /* from file as long as we do not switch variant          */
1653             if(!blackPlaysFirst) {
1654                 startedFromPositionFile = TRUE;
1655                 CopyBoard(filePosition, boards[0]);
1656             }
1657         }
1658         if (initialMode == AnalyzeMode) {
1659           if (appData.noChessProgram) {
1660             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1661             return;
1662           }
1663           if (appData.icsActive) {
1664             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1665             return;
1666           }
1667           AnalyzeModeEvent();
1668         } else if (initialMode == AnalyzeFile) {
1669           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1670           ShowThinkingEvent();
1671           AnalyzeFileEvent();
1672           AnalysisPeriodicEvent(1);
1673         } else if (initialMode == MachinePlaysWhite) {
1674           if (appData.noChessProgram) {
1675             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1676                               0, 2);
1677             return;
1678           }
1679           if (appData.icsActive) {
1680             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1681                               0, 2);
1682             return;
1683           }
1684           MachineWhiteEvent();
1685         } else if (initialMode == MachinePlaysBlack) {
1686           if (appData.noChessProgram) {
1687             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1688                               0, 2);
1689             return;
1690           }
1691           if (appData.icsActive) {
1692             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1693                               0, 2);
1694             return;
1695           }
1696           MachineBlackEvent();
1697         } else if (initialMode == TwoMachinesPlay) {
1698           if (appData.noChessProgram) {
1699             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1700                               0, 2);
1701             return;
1702           }
1703           if (appData.icsActive) {
1704             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1705                               0, 2);
1706             return;
1707           }
1708           TwoMachinesEvent();
1709         } else if (initialMode == EditGame) {
1710           EditGameEvent();
1711         } else if (initialMode == EditPosition) {
1712           EditPositionEvent();
1713         } else if (initialMode == Training) {
1714           if (*appData.loadGameFile == NULLCHAR) {
1715             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1716             return;
1717           }
1718           TrainingEvent();
1719         }
1720     }
1721 }
1722
1723 void
1724 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1725 {
1726     DisplayBook(current+1);
1727
1728     MoveHistorySet( movelist, first, last, current, pvInfoList );
1729
1730     EvalGraphSet( first, last, current, pvInfoList );
1731
1732     MakeEngineOutputTitle();
1733 }
1734
1735 /*
1736  * Establish will establish a contact to a remote host.port.
1737  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1738  *  used to talk to the host.
1739  * Returns 0 if okay, error code if not.
1740  */
1741 int
1742 establish ()
1743 {
1744     char buf[MSG_SIZ];
1745
1746     if (*appData.icsCommPort != NULLCHAR) {
1747         /* Talk to the host through a serial comm port */
1748         return OpenCommPort(appData.icsCommPort, &icsPR);
1749
1750     } else if (*appData.gateway != NULLCHAR) {
1751         if (*appData.remoteShell == NULLCHAR) {
1752             /* Use the rcmd protocol to run telnet program on a gateway host */
1753             snprintf(buf, sizeof(buf), "%s %s %s",
1754                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1755             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1756
1757         } else {
1758             /* Use the rsh program to run telnet program on a gateway host */
1759             if (*appData.remoteUser == NULLCHAR) {
1760                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1761                         appData.gateway, appData.telnetProgram,
1762                         appData.icsHost, appData.icsPort);
1763             } else {
1764                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1765                         appData.remoteShell, appData.gateway,
1766                         appData.remoteUser, appData.telnetProgram,
1767                         appData.icsHost, appData.icsPort);
1768             }
1769             return StartChildProcess(buf, "", &icsPR);
1770
1771         }
1772     } else if (appData.useTelnet) {
1773         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1774
1775     } else {
1776         /* TCP socket interface differs somewhat between
1777            Unix and NT; handle details in the front end.
1778            */
1779         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1780     }
1781 }
1782
1783 void
1784 EscapeExpand (char *p, char *q)
1785 {       // [HGM] initstring: routine to shape up string arguments
1786         while(*p++ = *q++) if(p[-1] == '\\')
1787             switch(*q++) {
1788                 case 'n': p[-1] = '\n'; break;
1789                 case 'r': p[-1] = '\r'; break;
1790                 case 't': p[-1] = '\t'; break;
1791                 case '\\': p[-1] = '\\'; break;
1792                 case 0: *p = 0; return;
1793                 default: p[-1] = q[-1]; break;
1794             }
1795 }
1796
1797 void
1798 show_bytes (FILE *fp, char *buf, int count)
1799 {
1800     while (count--) {
1801         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1802             fprintf(fp, "\\%03o", *buf & 0xff);
1803         } else {
1804             putc(*buf, fp);
1805         }
1806         buf++;
1807     }
1808     fflush(fp);
1809 }
1810
1811 /* Returns an errno value */
1812 int
1813 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1814 {
1815     char buf[8192], *p, *q, *buflim;
1816     int left, newcount, outcount;
1817
1818     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1819         *appData.gateway != NULLCHAR) {
1820         if (appData.debugMode) {
1821             fprintf(debugFP, ">ICS: ");
1822             show_bytes(debugFP, message, count);
1823             fprintf(debugFP, "\n");
1824         }
1825         return OutputToProcess(pr, message, count, outError);
1826     }
1827
1828     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1829     p = message;
1830     q = buf;
1831     left = count;
1832     newcount = 0;
1833     while (left) {
1834         if (q >= buflim) {
1835             if (appData.debugMode) {
1836                 fprintf(debugFP, ">ICS: ");
1837                 show_bytes(debugFP, buf, newcount);
1838                 fprintf(debugFP, "\n");
1839             }
1840             outcount = OutputToProcess(pr, buf, newcount, outError);
1841             if (outcount < newcount) return -1; /* to be sure */
1842             q = buf;
1843             newcount = 0;
1844         }
1845         if (*p == '\n') {
1846             *q++ = '\r';
1847             newcount++;
1848         } else if (((unsigned char) *p) == TN_IAC) {
1849             *q++ = (char) TN_IAC;
1850             newcount ++;
1851         }
1852         *q++ = *p++;
1853         newcount++;
1854         left--;
1855     }
1856     if (appData.debugMode) {
1857         fprintf(debugFP, ">ICS: ");
1858         show_bytes(debugFP, buf, newcount);
1859         fprintf(debugFP, "\n");
1860     }
1861     outcount = OutputToProcess(pr, buf, newcount, outError);
1862     if (outcount < newcount) return -1; /* to be sure */
1863     return count;
1864 }
1865
1866 void
1867 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1868 {
1869     int outError, outCount;
1870     static int gotEof = 0;
1871     static FILE *ini;
1872
1873     /* Pass data read from player on to ICS */
1874     if (count > 0) {
1875         gotEof = 0;
1876         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1877         if (outCount < count) {
1878             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1879         }
1880         if(have_sent_ICS_logon == 2) {
1881           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1882             fprintf(ini, "%s", message);
1883             have_sent_ICS_logon = 3;
1884           } else
1885             have_sent_ICS_logon = 1;
1886         } else if(have_sent_ICS_logon == 3) {
1887             fprintf(ini, "%s", message);
1888             fclose(ini);
1889           have_sent_ICS_logon = 1;
1890         }
1891     } else if (count < 0) {
1892         RemoveInputSource(isr);
1893         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1894     } else if (gotEof++ > 0) {
1895         RemoveInputSource(isr);
1896         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1897     }
1898 }
1899
1900 void
1901 KeepAlive ()
1902 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1903     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1904     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1905     SendToICS("date\n");
1906     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1907 }
1908
1909 /* added routine for printf style output to ics */
1910 void
1911 ics_printf (char *format, ...)
1912 {
1913     char buffer[MSG_SIZ];
1914     va_list args;
1915
1916     va_start(args, format);
1917     vsnprintf(buffer, sizeof(buffer), format, args);
1918     buffer[sizeof(buffer)-1] = '\0';
1919     SendToICS(buffer);
1920     va_end(args);
1921 }
1922
1923 void
1924 SendToICS (char *s)
1925 {
1926     int count, outCount, outError;
1927
1928     if (icsPR == NoProc) return;
1929
1930     count = strlen(s);
1931     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1932     if (outCount < count) {
1933         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1934     }
1935 }
1936
1937 /* This is used for sending logon scripts to the ICS. Sending
1938    without a delay causes problems when using timestamp on ICC
1939    (at least on my machine). */
1940 void
1941 SendToICSDelayed (char *s, long msdelay)
1942 {
1943     int count, outCount, outError;
1944
1945     if (icsPR == NoProc) return;
1946
1947     count = strlen(s);
1948     if (appData.debugMode) {
1949         fprintf(debugFP, ">ICS: ");
1950         show_bytes(debugFP, s, count);
1951         fprintf(debugFP, "\n");
1952     }
1953     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1954                                       msdelay);
1955     if (outCount < count) {
1956         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1957     }
1958 }
1959
1960
1961 /* Remove all highlighting escape sequences in s
1962    Also deletes any suffix starting with '('
1963    */
1964 char *
1965 StripHighlightAndTitle (char *s)
1966 {
1967     static char retbuf[MSG_SIZ];
1968     char *p = retbuf;
1969
1970     while (*s != NULLCHAR) {
1971         while (*s == '\033') {
1972             while (*s != NULLCHAR && !isalpha(*s)) s++;
1973             if (*s != NULLCHAR) s++;
1974         }
1975         while (*s != NULLCHAR && *s != '\033') {
1976             if (*s == '(' || *s == '[') {
1977                 *p = NULLCHAR;
1978                 return retbuf;
1979             }
1980             *p++ = *s++;
1981         }
1982     }
1983     *p = NULLCHAR;
1984     return retbuf;
1985 }
1986
1987 /* Remove all highlighting escape sequences in s */
1988 char *
1989 StripHighlight (char *s)
1990 {
1991     static char retbuf[MSG_SIZ];
1992     char *p = retbuf;
1993
1994     while (*s != NULLCHAR) {
1995         while (*s == '\033') {
1996             while (*s != NULLCHAR && !isalpha(*s)) s++;
1997             if (*s != NULLCHAR) s++;
1998         }
1999         while (*s != NULLCHAR && *s != '\033') {
2000             *p++ = *s++;
2001         }
2002     }
2003     *p = NULLCHAR;
2004     return retbuf;
2005 }
2006
2007 char *variantNames[] = VARIANT_NAMES;
2008 char *
2009 VariantName (VariantClass v)
2010 {
2011     return variantNames[v];
2012 }
2013
2014
2015 /* Identify a variant from the strings the chess servers use or the
2016    PGN Variant tag names we use. */
2017 VariantClass
2018 StringToVariant (char *e)
2019 {
2020     char *p;
2021     int wnum = -1;
2022     VariantClass v = VariantNormal;
2023     int i, found = FALSE;
2024     char buf[MSG_SIZ];
2025     int len;
2026
2027     if (!e) return v;
2028
2029     /* [HGM] skip over optional board-size prefixes */
2030     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2031         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2032         while( *e++ != '_');
2033     }
2034
2035     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2036         v = VariantNormal;
2037         found = TRUE;
2038     } else
2039     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2040       if (StrCaseStr(e, variantNames[i])) {
2041         v = (VariantClass) i;
2042         found = TRUE;
2043         break;
2044       }
2045     }
2046
2047     if (!found) {
2048       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2049           || StrCaseStr(e, "wild/fr")
2050           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2051         v = VariantFischeRandom;
2052       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2053                  (i = 1, p = StrCaseStr(e, "w"))) {
2054         p += i;
2055         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2056         if (isdigit(*p)) {
2057           wnum = atoi(p);
2058         } else {
2059           wnum = -1;
2060         }
2061         switch (wnum) {
2062         case 0: /* FICS only, actually */
2063         case 1:
2064           /* Castling legal even if K starts on d-file */
2065           v = VariantWildCastle;
2066           break;
2067         case 2:
2068         case 3:
2069         case 4:
2070           /* Castling illegal even if K & R happen to start in
2071              normal positions. */
2072           v = VariantNoCastle;
2073           break;
2074         case 5:
2075         case 7:
2076         case 8:
2077         case 10:
2078         case 11:
2079         case 12:
2080         case 13:
2081         case 14:
2082         case 15:
2083         case 18:
2084         case 19:
2085           /* Castling legal iff K & R start in normal positions */
2086           v = VariantNormal;
2087           break;
2088         case 6:
2089         case 20:
2090         case 21:
2091           /* Special wilds for position setup; unclear what to do here */
2092           v = VariantLoadable;
2093           break;
2094         case 9:
2095           /* Bizarre ICC game */
2096           v = VariantTwoKings;
2097           break;
2098         case 16:
2099           v = VariantKriegspiel;
2100           break;
2101         case 17:
2102           v = VariantLosers;
2103           break;
2104         case 22:
2105           v = VariantFischeRandom;
2106           break;
2107         case 23:
2108           v = VariantCrazyhouse;
2109           break;
2110         case 24:
2111           v = VariantBughouse;
2112           break;
2113         case 25:
2114           v = Variant3Check;
2115           break;
2116         case 26:
2117           /* Not quite the same as FICS suicide! */
2118           v = VariantGiveaway;
2119           break;
2120         case 27:
2121           v = VariantAtomic;
2122           break;
2123         case 28:
2124           v = VariantShatranj;
2125           break;
2126
2127         /* Temporary names for future ICC types.  The name *will* change in
2128            the next xboard/WinBoard release after ICC defines it. */
2129         case 29:
2130           v = Variant29;
2131           break;
2132         case 30:
2133           v = Variant30;
2134           break;
2135         case 31:
2136           v = Variant31;
2137           break;
2138         case 32:
2139           v = Variant32;
2140           break;
2141         case 33:
2142           v = Variant33;
2143           break;
2144         case 34:
2145           v = Variant34;
2146           break;
2147         case 35:
2148           v = Variant35;
2149           break;
2150         case 36:
2151           v = Variant36;
2152           break;
2153         case 37:
2154           v = VariantShogi;
2155           break;
2156         case 38:
2157           v = VariantXiangqi;
2158           break;
2159         case 39:
2160           v = VariantCourier;
2161           break;
2162         case 40:
2163           v = VariantGothic;
2164           break;
2165         case 41:
2166           v = VariantCapablanca;
2167           break;
2168         case 42:
2169           v = VariantKnightmate;
2170           break;
2171         case 43:
2172           v = VariantFairy;
2173           break;
2174         case 44:
2175           v = VariantCylinder;
2176           break;
2177         case 45:
2178           v = VariantFalcon;
2179           break;
2180         case 46:
2181           v = VariantCapaRandom;
2182           break;
2183         case 47:
2184           v = VariantBerolina;
2185           break;
2186         case 48:
2187           v = VariantJanus;
2188           break;
2189         case 49:
2190           v = VariantSuper;
2191           break;
2192         case 50:
2193           v = VariantGreat;
2194           break;
2195         case -1:
2196           /* Found "wild" or "w" in the string but no number;
2197              must assume it's normal chess. */
2198           v = VariantNormal;
2199           break;
2200         default:
2201           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2202           if( (len >= MSG_SIZ) && appData.debugMode )
2203             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2204
2205           DisplayError(buf, 0);
2206           v = VariantUnknown;
2207           break;
2208         }
2209       }
2210     }
2211     if (appData.debugMode) {
2212       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2213               e, wnum, VariantName(v));
2214     }
2215     return v;
2216 }
2217
2218 static int leftover_start = 0, leftover_len = 0;
2219 char star_match[STAR_MATCH_N][MSG_SIZ];
2220
2221 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2222    advance *index beyond it, and set leftover_start to the new value of
2223    *index; else return FALSE.  If pattern contains the character '*', it
2224    matches any sequence of characters not containing '\r', '\n', or the
2225    character following the '*' (if any), and the matched sequence(s) are
2226    copied into star_match.
2227    */
2228 int
2229 looking_at ( char *buf, int *index, char *pattern)
2230 {
2231     char *bufp = &buf[*index], *patternp = pattern;
2232     int star_count = 0;
2233     char *matchp = star_match[0];
2234
2235     for (;;) {
2236         if (*patternp == NULLCHAR) {
2237             *index = leftover_start = bufp - buf;
2238             *matchp = NULLCHAR;
2239             return TRUE;
2240         }
2241         if (*bufp == NULLCHAR) return FALSE;
2242         if (*patternp == '*') {
2243             if (*bufp == *(patternp + 1)) {
2244                 *matchp = NULLCHAR;
2245                 matchp = star_match[++star_count];
2246                 patternp += 2;
2247                 bufp++;
2248                 continue;
2249             } else if (*bufp == '\n' || *bufp == '\r') {
2250                 patternp++;
2251                 if (*patternp == NULLCHAR)
2252                   continue;
2253                 else
2254                   return FALSE;
2255             } else {
2256                 *matchp++ = *bufp++;
2257                 continue;
2258             }
2259         }
2260         if (*patternp != *bufp) return FALSE;
2261         patternp++;
2262         bufp++;
2263     }
2264 }
2265
2266 void
2267 SendToPlayer (char *data, int length)
2268 {
2269     int error, outCount;
2270     outCount = OutputToProcess(NoProc, data, length, &error);
2271     if (outCount < length) {
2272         DisplayFatalError(_("Error writing to display"), error, 1);
2273     }
2274 }
2275
2276 void
2277 PackHolding (char packed[], char *holding)
2278 {
2279     char *p = holding;
2280     char *q = packed;
2281     int runlength = 0;
2282     int curr = 9999;
2283     do {
2284         if (*p == curr) {
2285             runlength++;
2286         } else {
2287             switch (runlength) {
2288               case 0:
2289                 break;
2290               case 1:
2291                 *q++ = curr;
2292                 break;
2293               case 2:
2294                 *q++ = curr;
2295                 *q++ = curr;
2296                 break;
2297               default:
2298                 sprintf(q, "%d", runlength);
2299                 while (*q) q++;
2300                 *q++ = curr;
2301                 break;
2302             }
2303             runlength = 1;
2304             curr = *p;
2305         }
2306     } while (*p++);
2307     *q = NULLCHAR;
2308 }
2309
2310 /* Telnet protocol requests from the front end */
2311 void
2312 TelnetRequest (unsigned char ddww, unsigned char option)
2313 {
2314     unsigned char msg[3];
2315     int outCount, outError;
2316
2317     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2318
2319     if (appData.debugMode) {
2320         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2321         switch (ddww) {
2322           case TN_DO:
2323             ddwwStr = "DO";
2324             break;
2325           case TN_DONT:
2326             ddwwStr = "DONT";
2327             break;
2328           case TN_WILL:
2329             ddwwStr = "WILL";
2330             break;
2331           case TN_WONT:
2332             ddwwStr = "WONT";
2333             break;
2334           default:
2335             ddwwStr = buf1;
2336             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2337             break;
2338         }
2339         switch (option) {
2340           case TN_ECHO:
2341             optionStr = "ECHO";
2342             break;
2343           default:
2344             optionStr = buf2;
2345             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2346             break;
2347         }
2348         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2349     }
2350     msg[0] = TN_IAC;
2351     msg[1] = ddww;
2352     msg[2] = option;
2353     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2354     if (outCount < 3) {
2355         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2356     }
2357 }
2358
2359 void
2360 DoEcho ()
2361 {
2362     if (!appData.icsActive) return;
2363     TelnetRequest(TN_DO, TN_ECHO);
2364 }
2365
2366 void
2367 DontEcho ()
2368 {
2369     if (!appData.icsActive) return;
2370     TelnetRequest(TN_DONT, TN_ECHO);
2371 }
2372
2373 void
2374 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2375 {
2376     /* put the holdings sent to us by the server on the board holdings area */
2377     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2378     char p;
2379     ChessSquare piece;
2380
2381     if(gameInfo.holdingsWidth < 2)  return;
2382     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2383         return; // prevent overwriting by pre-board holdings
2384
2385     if( (int)lowestPiece >= BlackPawn ) {
2386         holdingsColumn = 0;
2387         countsColumn = 1;
2388         holdingsStartRow = BOARD_HEIGHT-1;
2389         direction = -1;
2390     } else {
2391         holdingsColumn = BOARD_WIDTH-1;
2392         countsColumn = BOARD_WIDTH-2;
2393         holdingsStartRow = 0;
2394         direction = 1;
2395     }
2396
2397     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2398         board[i][holdingsColumn] = EmptySquare;
2399         board[i][countsColumn]   = (ChessSquare) 0;
2400     }
2401     while( (p=*holdings++) != NULLCHAR ) {
2402         piece = CharToPiece( ToUpper(p) );
2403         if(piece == EmptySquare) continue;
2404         /*j = (int) piece - (int) WhitePawn;*/
2405         j = PieceToNumber(piece);
2406         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2407         if(j < 0) continue;               /* should not happen */
2408         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2409         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2410         board[holdingsStartRow+j*direction][countsColumn]++;
2411     }
2412 }
2413
2414
2415 void
2416 VariantSwitch (Board board, VariantClass newVariant)
2417 {
2418    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2419    static Board oldBoard;
2420
2421    startedFromPositionFile = FALSE;
2422    if(gameInfo.variant == newVariant) return;
2423
2424    /* [HGM] This routine is called each time an assignment is made to
2425     * gameInfo.variant during a game, to make sure the board sizes
2426     * are set to match the new variant. If that means adding or deleting
2427     * holdings, we shift the playing board accordingly
2428     * This kludge is needed because in ICS observe mode, we get boards
2429     * of an ongoing game without knowing the variant, and learn about the
2430     * latter only later. This can be because of the move list we requested,
2431     * in which case the game history is refilled from the beginning anyway,
2432     * but also when receiving holdings of a crazyhouse game. In the latter
2433     * case we want to add those holdings to the already received position.
2434     */
2435
2436
2437    if (appData.debugMode) {
2438      fprintf(debugFP, "Switch board from %s to %s\n",
2439              VariantName(gameInfo.variant), VariantName(newVariant));
2440      setbuf(debugFP, NULL);
2441    }
2442    shuffleOpenings = 0;       /* [HGM] shuffle */
2443    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2444    switch(newVariant)
2445      {
2446      case VariantShogi:
2447        newWidth = 9;  newHeight = 9;
2448        gameInfo.holdingsSize = 7;
2449      case VariantBughouse:
2450      case VariantCrazyhouse:
2451        newHoldingsWidth = 2; break;
2452      case VariantGreat:
2453        newWidth = 10;
2454      case VariantSuper:
2455        newHoldingsWidth = 2;
2456        gameInfo.holdingsSize = 8;
2457        break;
2458      case VariantGothic:
2459      case VariantCapablanca:
2460      case VariantCapaRandom:
2461        newWidth = 10;
2462      default:
2463        newHoldingsWidth = gameInfo.holdingsSize = 0;
2464      };
2465
2466    if(newWidth  != gameInfo.boardWidth  ||
2467       newHeight != gameInfo.boardHeight ||
2468       newHoldingsWidth != gameInfo.holdingsWidth ) {
2469
2470      /* shift position to new playing area, if needed */
2471      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2472        for(i=0; i<BOARD_HEIGHT; i++)
2473          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2474            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2475              board[i][j];
2476        for(i=0; i<newHeight; i++) {
2477          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2478          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2479        }
2480      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2481        for(i=0; i<BOARD_HEIGHT; i++)
2482          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2483            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2484              board[i][j];
2485      }
2486      board[HOLDINGS_SET] = 0;
2487      gameInfo.boardWidth  = newWidth;
2488      gameInfo.boardHeight = newHeight;
2489      gameInfo.holdingsWidth = newHoldingsWidth;
2490      gameInfo.variant = newVariant;
2491      InitDrawingSizes(-2, 0);
2492    } else gameInfo.variant = newVariant;
2493    CopyBoard(oldBoard, board);   // remember correctly formatted board
2494      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2495    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2496 }
2497
2498 static int loggedOn = FALSE;
2499
2500 /*-- Game start info cache: --*/
2501 int gs_gamenum;
2502 char gs_kind[MSG_SIZ];
2503 static char player1Name[128] = "";
2504 static char player2Name[128] = "";
2505 static char cont_seq[] = "\n\\   ";
2506 static int player1Rating = -1;
2507 static int player2Rating = -1;
2508 /*----------------------------*/
2509
2510 ColorClass curColor = ColorNormal;
2511 int suppressKibitz = 0;
2512
2513 // [HGM] seekgraph
2514 Boolean soughtPending = FALSE;
2515 Boolean seekGraphUp;
2516 #define MAX_SEEK_ADS 200
2517 #define SQUARE 0x80
2518 char *seekAdList[MAX_SEEK_ADS];
2519 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2520 float tcList[MAX_SEEK_ADS];
2521 char colorList[MAX_SEEK_ADS];
2522 int nrOfSeekAds = 0;
2523 int minRating = 1010, maxRating = 2800;
2524 int hMargin = 10, vMargin = 20, h, w;
2525 extern int squareSize, lineGap;
2526
2527 void
2528 PlotSeekAd (int i)
2529 {
2530         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2531         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2532         if(r < minRating+100 && r >=0 ) r = minRating+100;
2533         if(r > maxRating) r = maxRating;
2534         if(tc < 1.f) tc = 1.f;
2535         if(tc > 95.f) tc = 95.f;
2536         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2537         y = ((double)r - minRating)/(maxRating - minRating)
2538             * (h-vMargin-squareSize/8-1) + vMargin;
2539         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2540         if(strstr(seekAdList[i], " u ")) color = 1;
2541         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2542            !strstr(seekAdList[i], "bullet") &&
2543            !strstr(seekAdList[i], "blitz") &&
2544            !strstr(seekAdList[i], "standard") ) color = 2;
2545         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2546         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2547 }
2548
2549 void
2550 PlotSingleSeekAd (int i)
2551 {
2552         PlotSeekAd(i);
2553 }
2554
2555 void
2556 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2557 {
2558         char buf[MSG_SIZ], *ext = "";
2559         VariantClass v = StringToVariant(type);
2560         if(strstr(type, "wild")) {
2561             ext = type + 4; // append wild number
2562             if(v == VariantFischeRandom) type = "chess960"; else
2563             if(v == VariantLoadable) type = "setup"; else
2564             type = VariantName(v);
2565         }
2566         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2567         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2568             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2569             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2570             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2571             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2572             seekNrList[nrOfSeekAds] = nr;
2573             zList[nrOfSeekAds] = 0;
2574             seekAdList[nrOfSeekAds++] = StrSave(buf);
2575             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2576         }
2577 }
2578
2579 void
2580 EraseSeekDot (int i)
2581 {
2582     int x = xList[i], y = yList[i], d=squareSize/4, k;
2583     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2584     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2585     // now replot every dot that overlapped
2586     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2587         int xx = xList[k], yy = yList[k];
2588         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2589             DrawSeekDot(xx, yy, colorList[k]);
2590     }
2591 }
2592
2593 void
2594 RemoveSeekAd (int nr)
2595 {
2596         int i;
2597         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2598             EraseSeekDot(i);
2599             if(seekAdList[i]) free(seekAdList[i]);
2600             seekAdList[i] = seekAdList[--nrOfSeekAds];
2601             seekNrList[i] = seekNrList[nrOfSeekAds];
2602             ratingList[i] = ratingList[nrOfSeekAds];
2603             colorList[i]  = colorList[nrOfSeekAds];
2604             tcList[i] = tcList[nrOfSeekAds];
2605             xList[i]  = xList[nrOfSeekAds];
2606             yList[i]  = yList[nrOfSeekAds];
2607             zList[i]  = zList[nrOfSeekAds];
2608             seekAdList[nrOfSeekAds] = NULL;
2609             break;
2610         }
2611 }
2612
2613 Boolean
2614 MatchSoughtLine (char *line)
2615 {
2616     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2617     int nr, base, inc, u=0; char dummy;
2618
2619     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2620        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2621        (u=1) &&
2622        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2623         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2624         // match: compact and save the line
2625         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2626         return TRUE;
2627     }
2628     return FALSE;
2629 }
2630
2631 int
2632 DrawSeekGraph ()
2633 {
2634     int i;
2635     if(!seekGraphUp) return FALSE;
2636     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2637     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2638
2639     DrawSeekBackground(0, 0, w, h);
2640     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2641     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2642     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2643         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2644         yy = h-1-yy;
2645         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2646         if(i%500 == 0) {
2647             char buf[MSG_SIZ];
2648             snprintf(buf, MSG_SIZ, "%d", i);
2649             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2650         }
2651     }
2652     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2653     for(i=1; i<100; i+=(i<10?1:5)) {
2654         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2655         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2656         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2657             char buf[MSG_SIZ];
2658             snprintf(buf, MSG_SIZ, "%d", i);
2659             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2660         }
2661     }
2662     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2663     return TRUE;
2664 }
2665
2666 int
2667 SeekGraphClick (ClickType click, int x, int y, int moving)
2668 {
2669     static int lastDown = 0, displayed = 0, lastSecond;
2670     if(y < 0) return FALSE;
2671     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2672         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2673         if(!seekGraphUp) return FALSE;
2674         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2675         DrawPosition(TRUE, NULL);
2676         return TRUE;
2677     }
2678     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2679         if(click == Release || moving) return FALSE;
2680         nrOfSeekAds = 0;
2681         soughtPending = TRUE;
2682         SendToICS(ics_prefix);
2683         SendToICS("sought\n"); // should this be "sought all"?
2684     } else { // issue challenge based on clicked ad
2685         int dist = 10000; int i, closest = 0, second = 0;
2686         for(i=0; i<nrOfSeekAds; i++) {
2687             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2688             if(d < dist) { dist = d; closest = i; }
2689             second += (d - zList[i] < 120); // count in-range ads
2690             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2691         }
2692         if(dist < 120) {
2693             char buf[MSG_SIZ];
2694             second = (second > 1);
2695             if(displayed != closest || second != lastSecond) {
2696                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2697                 lastSecond = second; displayed = closest;
2698             }
2699             if(click == Press) {
2700                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2701                 lastDown = closest;
2702                 return TRUE;
2703             } // on press 'hit', only show info
2704             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2705             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2706             SendToICS(ics_prefix);
2707             SendToICS(buf);
2708             return TRUE; // let incoming board of started game pop down the graph
2709         } else if(click == Release) { // release 'miss' is ignored
2710             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2711             if(moving == 2) { // right up-click
2712                 nrOfSeekAds = 0; // refresh graph
2713                 soughtPending = TRUE;
2714                 SendToICS(ics_prefix);
2715                 SendToICS("sought\n"); // should this be "sought all"?
2716             }
2717             return TRUE;
2718         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2719         // press miss or release hit 'pop down' seek graph
2720         seekGraphUp = FALSE;
2721         DrawPosition(TRUE, NULL);
2722     }
2723     return TRUE;
2724 }
2725
2726 void
2727 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2728 {
2729 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2730 #define STARTED_NONE 0
2731 #define STARTED_MOVES 1
2732 #define STARTED_BOARD 2
2733 #define STARTED_OBSERVE 3
2734 #define STARTED_HOLDINGS 4
2735 #define STARTED_CHATTER 5
2736 #define STARTED_COMMENT 6
2737 #define STARTED_MOVES_NOHIDE 7
2738
2739     static int started = STARTED_NONE;
2740     static char parse[20000];
2741     static int parse_pos = 0;
2742     static char buf[BUF_SIZE + 1];
2743     static int firstTime = TRUE, intfSet = FALSE;
2744     static ColorClass prevColor = ColorNormal;
2745     static int savingComment = FALSE;
2746     static int cmatch = 0; // continuation sequence match
2747     char *bp;
2748     char str[MSG_SIZ];
2749     int i, oldi;
2750     int buf_len;
2751     int next_out;
2752     int tkind;
2753     int backup;    /* [DM] For zippy color lines */
2754     char *p;
2755     char talker[MSG_SIZ]; // [HGM] chat
2756     int channel;
2757
2758     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2759
2760     if (appData.debugMode) {
2761       if (!error) {
2762         fprintf(debugFP, "<ICS: ");
2763         show_bytes(debugFP, data, count);
2764         fprintf(debugFP, "\n");
2765       }
2766     }
2767
2768     if (appData.debugMode) { int f = forwardMostMove;
2769         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2770                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2771                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2772     }
2773     if (count > 0) {
2774         /* If last read ended with a partial line that we couldn't parse,
2775            prepend it to the new read and try again. */
2776         if (leftover_len > 0) {
2777             for (i=0; i<leftover_len; i++)
2778               buf[i] = buf[leftover_start + i];
2779         }
2780
2781     /* copy new characters into the buffer */
2782     bp = buf + leftover_len;
2783     buf_len=leftover_len;
2784     for (i=0; i<count; i++)
2785     {
2786         // ignore these
2787         if (data[i] == '\r')
2788             continue;
2789
2790         // join lines split by ICS?
2791         if (!appData.noJoin)
2792         {
2793             /*
2794                 Joining just consists of finding matches against the
2795                 continuation sequence, and discarding that sequence
2796                 if found instead of copying it.  So, until a match
2797                 fails, there's nothing to do since it might be the
2798                 complete sequence, and thus, something we don't want
2799                 copied.
2800             */
2801             if (data[i] == cont_seq[cmatch])
2802             {
2803                 cmatch++;
2804                 if (cmatch == strlen(cont_seq))
2805                 {
2806                     cmatch = 0; // complete match.  just reset the counter
2807
2808                     /*
2809                         it's possible for the ICS to not include the space
2810                         at the end of the last word, making our [correct]
2811                         join operation fuse two separate words.  the server
2812                         does this when the space occurs at the width setting.
2813                     */
2814                     if (!buf_len || buf[buf_len-1] != ' ')
2815                     {
2816                         *bp++ = ' ';
2817                         buf_len++;
2818                     }
2819                 }
2820                 continue;
2821             }
2822             else if (cmatch)
2823             {
2824                 /*
2825                     match failed, so we have to copy what matched before
2826                     falling through and copying this character.  In reality,
2827                     this will only ever be just the newline character, but
2828                     it doesn't hurt to be precise.
2829                 */
2830                 strncpy(bp, cont_seq, cmatch);
2831                 bp += cmatch;
2832                 buf_len += cmatch;
2833                 cmatch = 0;
2834             }
2835         }
2836
2837         // copy this char
2838         *bp++ = data[i];
2839         buf_len++;
2840     }
2841
2842         buf[buf_len] = NULLCHAR;
2843 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2844         next_out = 0;
2845         leftover_start = 0;
2846
2847         i = 0;
2848         while (i < buf_len) {
2849             /* Deal with part of the TELNET option negotiation
2850                protocol.  We refuse to do anything beyond the
2851                defaults, except that we allow the WILL ECHO option,
2852                which ICS uses to turn off password echoing when we are
2853                directly connected to it.  We reject this option
2854                if localLineEditing mode is on (always on in xboard)
2855                and we are talking to port 23, which might be a real
2856                telnet server that will try to keep WILL ECHO on permanently.
2857              */
2858             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2859                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2860                 unsigned char option;
2861                 oldi = i;
2862                 switch ((unsigned char) buf[++i]) {
2863                   case TN_WILL:
2864                     if (appData.debugMode)
2865                       fprintf(debugFP, "\n<WILL ");
2866                     switch (option = (unsigned char) buf[++i]) {
2867                       case TN_ECHO:
2868                         if (appData.debugMode)
2869                           fprintf(debugFP, "ECHO ");
2870                         /* Reply only if this is a change, according
2871                            to the protocol rules. */
2872                         if (remoteEchoOption) break;
2873                         if (appData.localLineEditing &&
2874                             atoi(appData.icsPort) == TN_PORT) {
2875                             TelnetRequest(TN_DONT, TN_ECHO);
2876                         } else {
2877                             EchoOff();
2878                             TelnetRequest(TN_DO, TN_ECHO);
2879                             remoteEchoOption = TRUE;
2880                         }
2881                         break;
2882                       default:
2883                         if (appData.debugMode)
2884                           fprintf(debugFP, "%d ", option);
2885                         /* Whatever this is, we don't want it. */
2886                         TelnetRequest(TN_DONT, option);
2887                         break;
2888                     }
2889                     break;
2890                   case TN_WONT:
2891                     if (appData.debugMode)
2892                       fprintf(debugFP, "\n<WONT ");
2893                     switch (option = (unsigned char) buf[++i]) {
2894                       case TN_ECHO:
2895                         if (appData.debugMode)
2896                           fprintf(debugFP, "ECHO ");
2897                         /* Reply only if this is a change, according
2898                            to the protocol rules. */
2899                         if (!remoteEchoOption) break;
2900                         EchoOn();
2901                         TelnetRequest(TN_DONT, TN_ECHO);
2902                         remoteEchoOption = FALSE;
2903                         break;
2904                       default:
2905                         if (appData.debugMode)
2906                           fprintf(debugFP, "%d ", (unsigned char) option);
2907                         /* Whatever this is, it must already be turned
2908                            off, because we never agree to turn on
2909                            anything non-default, so according to the
2910                            protocol rules, we don't reply. */
2911                         break;
2912                     }
2913                     break;
2914                   case TN_DO:
2915                     if (appData.debugMode)
2916                       fprintf(debugFP, "\n<DO ");
2917                     switch (option = (unsigned char) buf[++i]) {
2918                       default:
2919                         /* Whatever this is, we refuse to do it. */
2920                         if (appData.debugMode)
2921                           fprintf(debugFP, "%d ", option);
2922                         TelnetRequest(TN_WONT, option);
2923                         break;
2924                     }
2925                     break;
2926                   case TN_DONT:
2927                     if (appData.debugMode)
2928                       fprintf(debugFP, "\n<DONT ");
2929                     switch (option = (unsigned char) buf[++i]) {
2930                       default:
2931                         if (appData.debugMode)
2932                           fprintf(debugFP, "%d ", option);
2933                         /* Whatever this is, we are already not doing
2934                            it, because we never agree to do anything
2935                            non-default, so according to the protocol
2936                            rules, we don't reply. */
2937                         break;
2938                     }
2939                     break;
2940                   case TN_IAC:
2941                     if (appData.debugMode)
2942                       fprintf(debugFP, "\n<IAC ");
2943                     /* Doubled IAC; pass it through */
2944                     i--;
2945                     break;
2946                   default:
2947                     if (appData.debugMode)
2948                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2949                     /* Drop all other telnet commands on the floor */
2950                     break;
2951                 }
2952                 if (oldi > next_out)
2953                   SendToPlayer(&buf[next_out], oldi - next_out);
2954                 if (++i > next_out)
2955                   next_out = i;
2956                 continue;
2957             }
2958
2959             /* OK, this at least will *usually* work */
2960             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2961                 loggedOn = TRUE;
2962             }
2963
2964             if (loggedOn && !intfSet) {
2965                 if (ics_type == ICS_ICC) {
2966                   snprintf(str, MSG_SIZ,
2967                           "/set-quietly interface %s\n/set-quietly style 12\n",
2968                           programVersion);
2969                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2970                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2971                 } else if (ics_type == ICS_CHESSNET) {
2972                   snprintf(str, MSG_SIZ, "/style 12\n");
2973                 } else {
2974                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2975                   strcat(str, programVersion);
2976                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2977                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2978                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2979 #ifdef WIN32
2980                   strcat(str, "$iset nohighlight 1\n");
2981 #endif
2982                   strcat(str, "$iset lock 1\n$style 12\n");
2983                 }
2984                 SendToICS(str);
2985                 NotifyFrontendLogin();
2986                 intfSet = TRUE;
2987             }
2988
2989             if (started == STARTED_COMMENT) {
2990                 /* Accumulate characters in comment */
2991                 parse[parse_pos++] = buf[i];
2992                 if (buf[i] == '\n') {
2993                     parse[parse_pos] = NULLCHAR;
2994                     if(chattingPartner>=0) {
2995                         char mess[MSG_SIZ];
2996                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2997                         OutputChatMessage(chattingPartner, mess);
2998                         chattingPartner = -1;
2999                         next_out = i+1; // [HGM] suppress printing in ICS window
3000                     } else
3001                     if(!suppressKibitz) // [HGM] kibitz
3002                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3003                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3004                         int nrDigit = 0, nrAlph = 0, j;
3005                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3006                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3007                         parse[parse_pos] = NULLCHAR;
3008                         // try to be smart: if it does not look like search info, it should go to
3009                         // ICS interaction window after all, not to engine-output window.
3010                         for(j=0; j<parse_pos; j++) { // count letters and digits
3011                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3012                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3013                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3014                         }
3015                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3016                             int depth=0; float score;
3017                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3018                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3019                                 pvInfoList[forwardMostMove-1].depth = depth;
3020                                 pvInfoList[forwardMostMove-1].score = 100*score;
3021                             }
3022                             OutputKibitz(suppressKibitz, parse);
3023                         } else {
3024                             char tmp[MSG_SIZ];
3025                             if(gameMode == IcsObserving) // restore original ICS messages
3026                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3027                             else
3028                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3029                             SendToPlayer(tmp, strlen(tmp));
3030                         }
3031                         next_out = i+1; // [HGM] suppress printing in ICS window
3032                     }
3033                     started = STARTED_NONE;
3034                 } else {
3035                     /* Don't match patterns against characters in comment */
3036                     i++;
3037                     continue;
3038                 }
3039             }
3040             if (started == STARTED_CHATTER) {
3041                 if (buf[i] != '\n') {
3042                     /* Don't match patterns against characters in chatter */
3043                     i++;
3044                     continue;
3045                 }
3046                 started = STARTED_NONE;
3047                 if(suppressKibitz) next_out = i+1;
3048             }
3049
3050             /* Kludge to deal with rcmd protocol */
3051             if (firstTime && looking_at(buf, &i, "\001*")) {
3052                 DisplayFatalError(&buf[1], 0, 1);
3053                 continue;
3054             } else {
3055                 firstTime = FALSE;
3056             }
3057
3058             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3059                 ics_type = ICS_ICC;
3060                 ics_prefix = "/";
3061                 if (appData.debugMode)
3062                   fprintf(debugFP, "ics_type %d\n", ics_type);
3063                 continue;
3064             }
3065             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3066                 ics_type = ICS_FICS;
3067                 ics_prefix = "$";
3068                 if (appData.debugMode)
3069                   fprintf(debugFP, "ics_type %d\n", ics_type);
3070                 continue;
3071             }
3072             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3073                 ics_type = ICS_CHESSNET;
3074                 ics_prefix = "/";
3075                 if (appData.debugMode)
3076                   fprintf(debugFP, "ics_type %d\n", ics_type);
3077                 continue;
3078             }
3079
3080             if (!loggedOn &&
3081                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3082                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3083                  looking_at(buf, &i, "will be \"*\""))) {
3084               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3085               continue;
3086             }
3087
3088             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3089               char buf[MSG_SIZ];
3090               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3091               DisplayIcsInteractionTitle(buf);
3092               have_set_title = TRUE;
3093             }
3094
3095             /* skip finger notes */
3096             if (started == STARTED_NONE &&
3097                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3098                  (buf[i] == '1' && buf[i+1] == '0')) &&
3099                 buf[i+2] == ':' && buf[i+3] == ' ') {
3100               started = STARTED_CHATTER;
3101               i += 3;
3102               continue;
3103             }
3104
3105             oldi = i;
3106             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3107             if(appData.seekGraph) {
3108                 if(soughtPending && MatchSoughtLine(buf+i)) {
3109                     i = strstr(buf+i, "rated") - buf;
3110                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3111                     next_out = leftover_start = i;
3112                     started = STARTED_CHATTER;
3113                     suppressKibitz = TRUE;
3114                     continue;
3115                 }
3116                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3117                         && looking_at(buf, &i, "* ads displayed")) {
3118                     soughtPending = FALSE;
3119                     seekGraphUp = TRUE;
3120                     DrawSeekGraph();
3121                     continue;
3122                 }
3123                 if(appData.autoRefresh) {
3124                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3125                         int s = (ics_type == ICS_ICC); // ICC format differs
3126                         if(seekGraphUp)
3127                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3128                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3129                         looking_at(buf, &i, "*% "); // eat prompt
3130                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3131                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3132                         next_out = i; // suppress
3133                         continue;
3134                     }
3135                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3136                         char *p = star_match[0];
3137                         while(*p) {
3138                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3139                             while(*p && *p++ != ' '); // next
3140                         }
3141                         looking_at(buf, &i, "*% "); // eat prompt
3142                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3143                         next_out = i;
3144                         continue;
3145                     }
3146                 }
3147             }
3148
3149             /* skip formula vars */
3150             if (started == STARTED_NONE &&
3151                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3152               started = STARTED_CHATTER;
3153               i += 3;
3154               continue;
3155             }
3156
3157             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3158             if (appData.autoKibitz && started == STARTED_NONE &&
3159                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3160                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3161                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3162                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3163                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3164                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3165                         suppressKibitz = TRUE;
3166                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3167                         next_out = i;
3168                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3169                                 && (gameMode == IcsPlayingWhite)) ||
3170                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3171                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3172                             started = STARTED_CHATTER; // own kibitz we simply discard
3173                         else {
3174                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3175                             parse_pos = 0; parse[0] = NULLCHAR;
3176                             savingComment = TRUE;
3177                             suppressKibitz = gameMode != IcsObserving ? 2 :
3178                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3179                         }
3180                         continue;
3181                 } else
3182                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3183                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3184                          && atoi(star_match[0])) {
3185                     // suppress the acknowledgements of our own autoKibitz
3186                     char *p;
3187                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3188                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3189                     SendToPlayer(star_match[0], strlen(star_match[0]));
3190                     if(looking_at(buf, &i, "*% ")) // eat prompt
3191                         suppressKibitz = FALSE;
3192                     next_out = i;
3193                     continue;
3194                 }
3195             } // [HGM] kibitz: end of patch
3196
3197             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3198
3199             // [HGM] chat: intercept tells by users for which we have an open chat window
3200             channel = -1;
3201             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3202                                            looking_at(buf, &i, "* whispers:") ||
3203                                            looking_at(buf, &i, "* kibitzes:") ||
3204                                            looking_at(buf, &i, "* shouts:") ||
3205                                            looking_at(buf, &i, "* c-shouts:") ||
3206                                            looking_at(buf, &i, "--> * ") ||
3207                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3208                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3209                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3210                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3211                 int p;
3212                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3213                 chattingPartner = -1;
3214
3215                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3216                 for(p=0; p<MAX_CHAT; p++) {
3217                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3218                     talker[0] = '['; strcat(talker, "] ");
3219                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3220                     chattingPartner = p; break;
3221                     }
3222                 } else
3223                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3224                 for(p=0; p<MAX_CHAT; p++) {
3225                     if(!strcmp("kibitzes", chatPartner[p])) {
3226                         talker[0] = '['; strcat(talker, "] ");
3227                         chattingPartner = p; break;
3228                     }
3229                 } else
3230                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3231                 for(p=0; p<MAX_CHAT; p++) {
3232                     if(!strcmp("whispers", chatPartner[p])) {
3233                         talker[0] = '['; strcat(talker, "] ");
3234                         chattingPartner = p; break;
3235                     }
3236                 } else
3237                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3238                   if(buf[i-8] == '-' && buf[i-3] == 't')
3239                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3240                     if(!strcmp("c-shouts", chatPartner[p])) {
3241                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3242                         chattingPartner = p; break;
3243                     }
3244                   }
3245                   if(chattingPartner < 0)
3246                   for(p=0; p<MAX_CHAT; p++) {
3247                     if(!strcmp("shouts", chatPartner[p])) {
3248                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3249                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3250                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3251                         chattingPartner = p; break;
3252                     }
3253                   }
3254                 }
3255                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3256                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3257                     talker[0] = 0; Colorize(ColorTell, FALSE);
3258                     chattingPartner = p; break;
3259                 }
3260                 if(chattingPartner<0) i = oldi; else {
3261                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3262                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3263                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3264                     started = STARTED_COMMENT;
3265                     parse_pos = 0; parse[0] = NULLCHAR;
3266                     savingComment = 3 + chattingPartner; // counts as TRUE
3267                     suppressKibitz = TRUE;
3268                     continue;
3269                 }
3270             } // [HGM] chat: end of patch
3271
3272           backup = i;
3273             if (appData.zippyTalk || appData.zippyPlay) {
3274                 /* [DM] Backup address for color zippy lines */
3275 #if ZIPPY
3276                if (loggedOn == TRUE)
3277                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3278                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3279 #endif
3280             } // [DM] 'else { ' deleted
3281                 if (
3282                     /* Regular tells and says */
3283                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3284                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3285                     looking_at(buf, &i, "* says: ") ||
3286                     /* Don't color "message" or "messages" output */
3287                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3288                     looking_at(buf, &i, "*. * at *:*: ") ||
3289                     looking_at(buf, &i, "--* (*:*): ") ||
3290                     /* Message notifications (same color as tells) */
3291                     looking_at(buf, &i, "* has left a message ") ||
3292                     looking_at(buf, &i, "* just sent you a message:\n") ||
3293                     /* Whispers and kibitzes */
3294                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3295                     looking_at(buf, &i, "* kibitzes: ") ||
3296                     /* Channel tells */
3297                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3298
3299                   if (tkind == 1 && strchr(star_match[0], ':')) {
3300                       /* Avoid "tells you:" spoofs in channels */
3301                      tkind = 3;
3302                   }
3303                   if (star_match[0][0] == NULLCHAR ||
3304                       strchr(star_match[0], ' ') ||
3305                       (tkind == 3 && strchr(star_match[1], ' '))) {
3306                     /* Reject bogus matches */
3307                     i = oldi;
3308                   } else {
3309                     if (appData.colorize) {
3310                       if (oldi > next_out) {
3311                         SendToPlayer(&buf[next_out], oldi - next_out);
3312                         next_out = oldi;
3313                       }
3314                       switch (tkind) {
3315                       case 1:
3316                         Colorize(ColorTell, FALSE);
3317                         curColor = ColorTell;
3318                         break;
3319                       case 2:
3320                         Colorize(ColorKibitz, FALSE);
3321                         curColor = ColorKibitz;
3322                         break;
3323                       case 3:
3324                         p = strrchr(star_match[1], '(');
3325                         if (p == NULL) {
3326                           p = star_match[1];
3327                         } else {
3328                           p++;
3329                         }
3330                         if (atoi(p) == 1) {
3331                           Colorize(ColorChannel1, FALSE);
3332                           curColor = ColorChannel1;
3333                         } else {
3334                           Colorize(ColorChannel, FALSE);
3335                           curColor = ColorChannel;
3336                         }
3337                         break;
3338                       case 5:
3339                         curColor = ColorNormal;
3340                         break;
3341                       }
3342                     }
3343                     if (started == STARTED_NONE && appData.autoComment &&
3344                         (gameMode == IcsObserving ||
3345                          gameMode == IcsPlayingWhite ||
3346                          gameMode == IcsPlayingBlack)) {
3347                       parse_pos = i - oldi;
3348                       memcpy(parse, &buf[oldi], parse_pos);
3349                       parse[parse_pos] = NULLCHAR;
3350                       started = STARTED_COMMENT;
3351                       savingComment = TRUE;
3352                     } else {
3353                       started = STARTED_CHATTER;
3354                       savingComment = FALSE;
3355                     }
3356                     loggedOn = TRUE;
3357                     continue;
3358                   }
3359                 }
3360
3361                 if (looking_at(buf, &i, "* s-shouts: ") ||
3362                     looking_at(buf, &i, "* c-shouts: ")) {
3363                     if (appData.colorize) {
3364                         if (oldi > next_out) {
3365                             SendToPlayer(&buf[next_out], oldi - next_out);
3366                             next_out = oldi;
3367                         }
3368                         Colorize(ColorSShout, FALSE);
3369                         curColor = ColorSShout;
3370                     }
3371                     loggedOn = TRUE;
3372                     started = STARTED_CHATTER;
3373                     continue;
3374                 }
3375
3376                 if (looking_at(buf, &i, "--->")) {
3377                     loggedOn = TRUE;
3378                     continue;
3379                 }
3380
3381                 if (looking_at(buf, &i, "* shouts: ") ||
3382                     looking_at(buf, &i, "--> ")) {
3383                     if (appData.colorize) {
3384                         if (oldi > next_out) {
3385                             SendToPlayer(&buf[next_out], oldi - next_out);
3386                             next_out = oldi;
3387                         }
3388                         Colorize(ColorShout, FALSE);
3389                         curColor = ColorShout;
3390                     }
3391                     loggedOn = TRUE;
3392                     started = STARTED_CHATTER;
3393                     continue;
3394                 }
3395
3396                 if (looking_at( buf, &i, "Challenge:")) {
3397                     if (appData.colorize) {
3398                         if (oldi > next_out) {
3399                             SendToPlayer(&buf[next_out], oldi - next_out);
3400                             next_out = oldi;
3401                         }
3402                         Colorize(ColorChallenge, FALSE);
3403                         curColor = ColorChallenge;
3404                     }
3405                     loggedOn = TRUE;
3406                     continue;
3407                 }
3408
3409                 if (looking_at(buf, &i, "* offers you") ||
3410                     looking_at(buf, &i, "* offers to be") ||
3411                     looking_at(buf, &i, "* would like to") ||
3412                     looking_at(buf, &i, "* requests to") ||
3413                     looking_at(buf, &i, "Your opponent offers") ||
3414                     looking_at(buf, &i, "Your opponent requests")) {
3415
3416                     if (appData.colorize) {
3417                         if (oldi > next_out) {
3418                             SendToPlayer(&buf[next_out], oldi - next_out);
3419                             next_out = oldi;
3420                         }
3421                         Colorize(ColorRequest, FALSE);
3422                         curColor = ColorRequest;
3423                     }
3424                     continue;
3425                 }
3426
3427                 if (looking_at(buf, &i, "* (*) seeking")) {
3428                     if (appData.colorize) {
3429                         if (oldi > next_out) {
3430                             SendToPlayer(&buf[next_out], oldi - next_out);
3431                             next_out = oldi;
3432                         }
3433                         Colorize(ColorSeek, FALSE);
3434                         curColor = ColorSeek;
3435                     }
3436                     continue;
3437             }
3438
3439           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3440
3441             if (looking_at(buf, &i, "\\   ")) {
3442                 if (prevColor != ColorNormal) {
3443                     if (oldi > next_out) {
3444                         SendToPlayer(&buf[next_out], oldi - next_out);
3445                         next_out = oldi;
3446                     }
3447                     Colorize(prevColor, TRUE);
3448                     curColor = prevColor;
3449                 }
3450                 if (savingComment) {
3451                     parse_pos = i - oldi;
3452                     memcpy(parse, &buf[oldi], parse_pos);
3453                     parse[parse_pos] = NULLCHAR;
3454                     started = STARTED_COMMENT;
3455                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3456                         chattingPartner = savingComment - 3; // kludge to remember the box
3457                 } else {
3458                     started = STARTED_CHATTER;
3459                 }
3460                 continue;
3461             }
3462
3463             if (looking_at(buf, &i, "Black Strength :") ||
3464                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3465                 looking_at(buf, &i, "<10>") ||
3466                 looking_at(buf, &i, "#@#")) {
3467                 /* Wrong board style */
3468                 loggedOn = TRUE;
3469                 SendToICS(ics_prefix);
3470                 SendToICS("set style 12\n");
3471                 SendToICS(ics_prefix);
3472                 SendToICS("refresh\n");
3473                 continue;
3474             }
3475
3476             if (looking_at(buf, &i, "login:")) {
3477               if (!have_sent_ICS_logon) {
3478                 if(ICSInitScript())
3479                   have_sent_ICS_logon = 1;
3480                 else // no init script was found
3481                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3482               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3483                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3484               }
3485                 continue;
3486             }
3487
3488             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3489                 (looking_at(buf, &i, "\n<12> ") ||
3490                  looking_at(buf, &i, "<12> "))) {
3491                 loggedOn = TRUE;
3492                 if (oldi > next_out) {
3493                     SendToPlayer(&buf[next_out], oldi - next_out);
3494                 }
3495                 next_out = i;
3496                 started = STARTED_BOARD;
3497                 parse_pos = 0;
3498                 continue;
3499             }
3500
3501             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3502                 looking_at(buf, &i, "<b1> ")) {
3503                 if (oldi > next_out) {
3504                     SendToPlayer(&buf[next_out], oldi - next_out);
3505                 }
3506                 next_out = i;
3507                 started = STARTED_HOLDINGS;
3508                 parse_pos = 0;
3509                 continue;
3510             }
3511
3512             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3513                 loggedOn = TRUE;
3514                 /* Header for a move list -- first line */
3515
3516                 switch (ics_getting_history) {
3517                   case H_FALSE:
3518                     switch (gameMode) {
3519                       case IcsIdle:
3520                       case BeginningOfGame:
3521                         /* User typed "moves" or "oldmoves" while we
3522                            were idle.  Pretend we asked for these
3523                            moves and soak them up so user can step
3524                            through them and/or save them.
3525                            */
3526                         Reset(FALSE, TRUE);
3527                         gameMode = IcsObserving;
3528                         ModeHighlight();
3529                         ics_gamenum = -1;
3530                         ics_getting_history = H_GOT_UNREQ_HEADER;
3531                         break;
3532                       case EditGame: /*?*/
3533                       case EditPosition: /*?*/
3534                         /* Should above feature work in these modes too? */
3535                         /* For now it doesn't */
3536                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3537                         break;
3538                       default:
3539                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3540                         break;
3541                     }
3542                     break;
3543                   case H_REQUESTED:
3544                     /* Is this the right one? */
3545                     if (gameInfo.white && gameInfo.black &&
3546                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3547                         strcmp(gameInfo.black, star_match[2]) == 0) {
3548                         /* All is well */
3549                         ics_getting_history = H_GOT_REQ_HEADER;
3550                     }
3551                     break;
3552                   case H_GOT_REQ_HEADER:
3553                   case H_GOT_UNREQ_HEADER:
3554                   case H_GOT_UNWANTED_HEADER:
3555                   case H_GETTING_MOVES:
3556                     /* Should not happen */
3557                     DisplayError(_("Error gathering move list: two headers"), 0);
3558                     ics_getting_history = H_FALSE;
3559                     break;
3560                 }
3561
3562                 /* Save player ratings into gameInfo if needed */
3563                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3564                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3565                     (gameInfo.whiteRating == -1 ||
3566                      gameInfo.blackRating == -1)) {
3567
3568                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3569                     gameInfo.blackRating = string_to_rating(star_match[3]);
3570                     if (appData.debugMode)
3571                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3572                               gameInfo.whiteRating, gameInfo.blackRating);
3573                 }
3574                 continue;
3575             }
3576
3577             if (looking_at(buf, &i,
3578               "* * match, initial time: * minute*, increment: * second")) {
3579                 /* Header for a move list -- second line */
3580                 /* Initial board will follow if this is a wild game */
3581                 if (gameInfo.event != NULL) free(gameInfo.event);
3582                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3583                 gameInfo.event = StrSave(str);
3584                 /* [HGM] we switched variant. Translate boards if needed. */
3585                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3586                 continue;
3587             }
3588
3589             if (looking_at(buf, &i, "Move  ")) {
3590                 /* Beginning of a move list */
3591                 switch (ics_getting_history) {
3592                   case H_FALSE:
3593                     /* Normally should not happen */
3594                     /* Maybe user hit reset while we were parsing */
3595                     break;
3596                   case H_REQUESTED:
3597                     /* Happens if we are ignoring a move list that is not
3598                      * the one we just requested.  Common if the user
3599                      * tries to observe two games without turning off
3600                      * getMoveList */
3601                     break;
3602                   case H_GETTING_MOVES:
3603                     /* Should not happen */
3604                     DisplayError(_("Error gathering move list: nested"), 0);
3605                     ics_getting_history = H_FALSE;
3606                     break;
3607                   case H_GOT_REQ_HEADER:
3608                     ics_getting_history = H_GETTING_MOVES;
3609                     started = STARTED_MOVES;
3610                     parse_pos = 0;
3611                     if (oldi > next_out) {
3612                         SendToPlayer(&buf[next_out], oldi - next_out);
3613                     }
3614                     break;
3615                   case H_GOT_UNREQ_HEADER:
3616                     ics_getting_history = H_GETTING_MOVES;
3617                     started = STARTED_MOVES_NOHIDE;
3618                     parse_pos = 0;
3619                     break;
3620                   case H_GOT_UNWANTED_HEADER:
3621                     ics_getting_history = H_FALSE;
3622                     break;
3623                 }
3624                 continue;
3625             }
3626
3627             if (looking_at(buf, &i, "% ") ||
3628                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3629                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3630                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3631                     soughtPending = FALSE;
3632                     seekGraphUp = TRUE;
3633                     DrawSeekGraph();
3634                 }
3635                 if(suppressKibitz) next_out = i;
3636                 savingComment = FALSE;
3637                 suppressKibitz = 0;
3638                 switch (started) {
3639                   case STARTED_MOVES:
3640                   case STARTED_MOVES_NOHIDE:
3641                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3642                     parse[parse_pos + i - oldi] = NULLCHAR;
3643                     ParseGameHistory(parse);
3644 #if ZIPPY
3645                     if (appData.zippyPlay && first.initDone) {
3646                         FeedMovesToProgram(&first, forwardMostMove);
3647                         if (gameMode == IcsPlayingWhite) {
3648                             if (WhiteOnMove(forwardMostMove)) {
3649                                 if (first.sendTime) {
3650                                   if (first.useColors) {
3651                                     SendToProgram("black\n", &first);
3652                                   }
3653                                   SendTimeRemaining(&first, TRUE);
3654                                 }
3655                                 if (first.useColors) {
3656                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3657                                 }
3658                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3659                                 first.maybeThinking = TRUE;
3660                             } else {
3661                                 if (first.usePlayother) {
3662                                   if (first.sendTime) {
3663                                     SendTimeRemaining(&first, TRUE);
3664                                   }
3665                                   SendToProgram("playother\n", &first);
3666                                   firstMove = FALSE;
3667                                 } else {
3668                                   firstMove = TRUE;
3669                                 }
3670                             }
3671                         } else if (gameMode == IcsPlayingBlack) {
3672                             if (!WhiteOnMove(forwardMostMove)) {
3673                                 if (first.sendTime) {
3674                                   if (first.useColors) {
3675                                     SendToProgram("white\n", &first);
3676                                   }
3677                                   SendTimeRemaining(&first, FALSE);
3678                                 }
3679                                 if (first.useColors) {
3680                                   SendToProgram("black\n", &first);
3681                                 }
3682                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3683                                 first.maybeThinking = TRUE;
3684                             } else {
3685                                 if (first.usePlayother) {
3686                                   if (first.sendTime) {
3687                                     SendTimeRemaining(&first, FALSE);
3688                                   }
3689                                   SendToProgram("playother\n", &first);
3690                                   firstMove = FALSE;
3691                                 } else {
3692                                   firstMove = TRUE;
3693                                 }
3694                             }
3695                         }
3696                     }
3697 #endif
3698                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3699                         /* Moves came from oldmoves or moves command
3700                            while we weren't doing anything else.
3701                            */
3702                         currentMove = forwardMostMove;
3703                         ClearHighlights();/*!!could figure this out*/
3704                         flipView = appData.flipView;
3705                         DrawPosition(TRUE, boards[currentMove]);
3706                         DisplayBothClocks();
3707                         snprintf(str, MSG_SIZ, "%s %s %s",
3708                                 gameInfo.white, _("vs."),  gameInfo.black);
3709                         DisplayTitle(str);
3710                         gameMode = IcsIdle;
3711                     } else {
3712                         /* Moves were history of an active game */
3713                         if (gameInfo.resultDetails != NULL) {
3714                             free(gameInfo.resultDetails);
3715                             gameInfo.resultDetails = NULL;
3716                         }
3717                     }
3718                     HistorySet(parseList, backwardMostMove,
3719                                forwardMostMove, currentMove-1);
3720                     DisplayMove(currentMove - 1);
3721                     if (started == STARTED_MOVES) next_out = i;
3722                     started = STARTED_NONE;
3723                     ics_getting_history = H_FALSE;
3724                     break;
3725
3726                   case STARTED_OBSERVE:
3727                     started = STARTED_NONE;
3728                     SendToICS(ics_prefix);
3729                     SendToICS("refresh\n");
3730                     break;
3731
3732                   default:
3733                     break;
3734                 }
3735                 if(bookHit) { // [HGM] book: simulate book reply
3736                     static char bookMove[MSG_SIZ]; // a bit generous?
3737
3738                     programStats.nodes = programStats.depth = programStats.time =
3739                     programStats.score = programStats.got_only_move = 0;
3740                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3741
3742                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3743                     strcat(bookMove, bookHit);
3744                     HandleMachineMove(bookMove, &first);
3745                 }
3746                 continue;
3747             }
3748
3749             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3750                  started == STARTED_HOLDINGS ||
3751                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3752                 /* Accumulate characters in move list or board */
3753                 parse[parse_pos++] = buf[i];
3754             }
3755
3756             /* Start of game messages.  Mostly we detect start of game
3757                when the first board image arrives.  On some versions
3758                of the ICS, though, we need to do a "refresh" after starting
3759                to observe in order to get the current board right away. */
3760             if (looking_at(buf, &i, "Adding game * to observation list")) {
3761                 started = STARTED_OBSERVE;
3762                 continue;
3763             }
3764
3765             /* Handle auto-observe */
3766             if (appData.autoObserve &&
3767                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3768                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3769                 char *player;
3770                 /* Choose the player that was highlighted, if any. */
3771                 if (star_match[0][0] == '\033' ||
3772                     star_match[1][0] != '\033') {
3773                     player = star_match[0];
3774                 } else {
3775                     player = star_match[2];
3776                 }
3777                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3778                         ics_prefix, StripHighlightAndTitle(player));
3779                 SendToICS(str);
3780
3781                 /* Save ratings from notify string */
3782                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3783                 player1Rating = string_to_rating(star_match[1]);
3784                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3785                 player2Rating = string_to_rating(star_match[3]);
3786
3787                 if (appData.debugMode)
3788                   fprintf(debugFP,
3789                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3790                           player1Name, player1Rating,
3791                           player2Name, player2Rating);
3792
3793                 continue;
3794             }
3795
3796             /* Deal with automatic examine mode after a game,
3797                and with IcsObserving -> IcsExamining transition */
3798             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3799                 looking_at(buf, &i, "has made you an examiner of game *")) {
3800
3801                 int gamenum = atoi(star_match[0]);
3802                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3803                     gamenum == ics_gamenum) {
3804                     /* We were already playing or observing this game;
3805                        no need to refetch history */
3806                     gameMode = IcsExamining;
3807                     if (pausing) {
3808                         pauseExamForwardMostMove = forwardMostMove;
3809                     } else if (currentMove < forwardMostMove) {
3810                         ForwardInner(forwardMostMove);
3811                     }
3812                 } else {
3813                     /* I don't think this case really can happen */
3814                     SendToICS(ics_prefix);
3815                     SendToICS("refresh\n");
3816                 }
3817                 continue;
3818             }
3819
3820             /* Error messages */
3821 //          if (ics_user_moved) {
3822             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3823                 if (looking_at(buf, &i, "Illegal move") ||
3824                     looking_at(buf, &i, "Not a legal move") ||
3825                     looking_at(buf, &i, "Your king is in check") ||
3826                     looking_at(buf, &i, "It isn't your turn") ||
3827                     looking_at(buf, &i, "It is not your move")) {
3828                     /* Illegal move */
3829                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3830                         currentMove = forwardMostMove-1;
3831                         DisplayMove(currentMove - 1); /* before DMError */
3832                         DrawPosition(FALSE, boards[currentMove]);
3833                         SwitchClocks(forwardMostMove-1); // [HGM] race
3834                         DisplayBothClocks();
3835                     }
3836                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3837                     ics_user_moved = 0;
3838                     continue;
3839                 }
3840             }
3841
3842             if (looking_at(buf, &i, "still have time") ||
3843                 looking_at(buf, &i, "not out of time") ||
3844                 looking_at(buf, &i, "either player is out of time") ||
3845                 looking_at(buf, &i, "has timeseal; checking")) {
3846                 /* We must have called his flag a little too soon */
3847                 whiteFlag = blackFlag = FALSE;
3848                 continue;
3849             }
3850
3851             if (looking_at(buf, &i, "added * seconds to") ||
3852                 looking_at(buf, &i, "seconds were added to")) {
3853                 /* Update the clocks */
3854                 SendToICS(ics_prefix);
3855                 SendToICS("refresh\n");
3856                 continue;
3857             }
3858
3859             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3860                 ics_clock_paused = TRUE;
3861                 StopClocks();
3862                 continue;
3863             }
3864
3865             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3866                 ics_clock_paused = FALSE;
3867                 StartClocks();
3868                 continue;
3869             }
3870
3871             /* Grab player ratings from the Creating: message.
3872                Note we have to check for the special case when
3873                the ICS inserts things like [white] or [black]. */
3874             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3875                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3876                 /* star_matches:
3877                    0    player 1 name (not necessarily white)
3878                    1    player 1 rating
3879                    2    empty, white, or black (IGNORED)
3880                    3    player 2 name (not necessarily black)
3881                    4    player 2 rating
3882
3883                    The names/ratings are sorted out when the game
3884                    actually starts (below).
3885                 */
3886                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3887                 player1Rating = string_to_rating(star_match[1]);
3888                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3889                 player2Rating = string_to_rating(star_match[4]);
3890
3891                 if (appData.debugMode)
3892                   fprintf(debugFP,
3893                           "Ratings from 'Creating:' %s %d, %s %d\n",
3894                           player1Name, player1Rating,
3895                           player2Name, player2Rating);
3896
3897                 continue;
3898             }
3899
3900             /* Improved generic start/end-of-game messages */
3901             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3902                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3903                 /* If tkind == 0: */
3904                 /* star_match[0] is the game number */
3905                 /*           [1] is the white player's name */
3906                 /*           [2] is the black player's name */
3907                 /* For end-of-game: */
3908                 /*           [3] is the reason for the game end */
3909                 /*           [4] is a PGN end game-token, preceded by " " */
3910                 /* For start-of-game: */
3911                 /*           [3] begins with "Creating" or "Continuing" */
3912                 /*           [4] is " *" or empty (don't care). */
3913                 int gamenum = atoi(star_match[0]);
3914                 char *whitename, *blackname, *why, *endtoken;
3915                 ChessMove endtype = EndOfFile;
3916
3917                 if (tkind == 0) {
3918                   whitename = star_match[1];
3919                   blackname = star_match[2];
3920                   why = star_match[3];
3921                   endtoken = star_match[4];
3922                 } else {
3923                   whitename = star_match[1];
3924                   blackname = star_match[3];
3925                   why = star_match[5];
3926                   endtoken = star_match[6];
3927                 }
3928
3929                 /* Game start messages */
3930                 if (strncmp(why, "Creating ", 9) == 0 ||
3931                     strncmp(why, "Continuing ", 11) == 0) {
3932                     gs_gamenum = gamenum;
3933                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3934                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3935                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3936 #if ZIPPY
3937                     if (appData.zippyPlay) {
3938                         ZippyGameStart(whitename, blackname);
3939                     }
3940 #endif /*ZIPPY*/
3941                     partnerBoardValid = FALSE; // [HGM] bughouse
3942                     continue;
3943                 }
3944
3945                 /* Game end messages */
3946                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3947                     ics_gamenum != gamenum) {
3948                     continue;
3949                 }
3950                 while (endtoken[0] == ' ') endtoken++;
3951                 switch (endtoken[0]) {
3952                   case '*':
3953                   default:
3954                     endtype = GameUnfinished;
3955                     break;
3956                   case '0':
3957                     endtype = BlackWins;
3958                     break;
3959                   case '1':
3960                     if (endtoken[1] == '/')
3961                       endtype = GameIsDrawn;
3962                     else
3963                       endtype = WhiteWins;
3964                     break;
3965                 }
3966                 GameEnds(endtype, why, GE_ICS);
3967 #if ZIPPY
3968                 if (appData.zippyPlay && first.initDone) {
3969                     ZippyGameEnd(endtype, why);
3970                     if (first.pr == NoProc) {
3971                       /* Start the next process early so that we'll
3972                          be ready for the next challenge */
3973                       StartChessProgram(&first);
3974                     }
3975                     /* Send "new" early, in case this command takes
3976                        a long time to finish, so that we'll be ready
3977                        for the next challenge. */
3978                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3979                     Reset(TRUE, TRUE);
3980                 }
3981 #endif /*ZIPPY*/
3982                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3983                 continue;
3984             }
3985
3986             if (looking_at(buf, &i, "Removing game * from observation") ||
3987                 looking_at(buf, &i, "no longer observing game *") ||
3988                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3989                 if (gameMode == IcsObserving &&
3990                     atoi(star_match[0]) == ics_gamenum)
3991                   {
3992                       /* icsEngineAnalyze */
3993                       if (appData.icsEngineAnalyze) {
3994                             ExitAnalyzeMode();
3995                             ModeHighlight();
3996                       }
3997                       StopClocks();
3998                       gameMode = IcsIdle;
3999                       ics_gamenum = -1;
4000                       ics_user_moved = FALSE;
4001                   }
4002                 continue;
4003             }
4004
4005             if (looking_at(buf, &i, "no longer examining game *")) {
4006                 if (gameMode == IcsExamining &&
4007                     atoi(star_match[0]) == ics_gamenum)
4008                   {
4009                       gameMode = IcsIdle;
4010                       ics_gamenum = -1;
4011                       ics_user_moved = FALSE;
4012                   }
4013                 continue;
4014             }
4015
4016             /* Advance leftover_start past any newlines we find,
4017                so only partial lines can get reparsed */
4018             if (looking_at(buf, &i, "\n")) {
4019                 prevColor = curColor;
4020                 if (curColor != ColorNormal) {
4021                     if (oldi > next_out) {
4022                         SendToPlayer(&buf[next_out], oldi - next_out);
4023                         next_out = oldi;
4024                     }
4025                     Colorize(ColorNormal, FALSE);
4026                     curColor = ColorNormal;
4027                 }
4028                 if (started == STARTED_BOARD) {
4029                     started = STARTED_NONE;
4030                     parse[parse_pos] = NULLCHAR;
4031                     ParseBoard12(parse);
4032                     ics_user_moved = 0;
4033
4034                     /* Send premove here */
4035                     if (appData.premove) {
4036                       char str[MSG_SIZ];
4037                       if (currentMove == 0 &&
4038                           gameMode == IcsPlayingWhite &&
4039                           appData.premoveWhite) {
4040                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4041                         if (appData.debugMode)
4042                           fprintf(debugFP, "Sending premove:\n");
4043                         SendToICS(str);
4044                       } else if (currentMove == 1 &&
4045                                  gameMode == IcsPlayingBlack &&
4046                                  appData.premoveBlack) {
4047                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4048                         if (appData.debugMode)
4049                           fprintf(debugFP, "Sending premove:\n");
4050                         SendToICS(str);
4051                       } else if (gotPremove) {
4052                         gotPremove = 0;
4053                         ClearPremoveHighlights();
4054                         if (appData.debugMode)
4055                           fprintf(debugFP, "Sending premove:\n");
4056                           UserMoveEvent(premoveFromX, premoveFromY,
4057                                         premoveToX, premoveToY,
4058                                         premovePromoChar);
4059                       }
4060                     }
4061
4062                     /* Usually suppress following prompt */
4063                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4064                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4065                         if (looking_at(buf, &i, "*% ")) {
4066                             savingComment = FALSE;
4067                             suppressKibitz = 0;
4068                         }
4069                     }
4070                     next_out = i;
4071                 } else if (started == STARTED_HOLDINGS) {
4072                     int gamenum;
4073                     char new_piece[MSG_SIZ];
4074                     started = STARTED_NONE;
4075                     parse[parse_pos] = NULLCHAR;
4076                     if (appData.debugMode)
4077                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4078                                                         parse, currentMove);
4079                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4080                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4081                         if (gameInfo.variant == VariantNormal) {
4082                           /* [HGM] We seem to switch variant during a game!
4083                            * Presumably no holdings were displayed, so we have
4084                            * to move the position two files to the right to
4085                            * create room for them!
4086                            */
4087                           VariantClass newVariant;
4088                           switch(gameInfo.boardWidth) { // base guess on board width
4089                                 case 9:  newVariant = VariantShogi; break;
4090                                 case 10: newVariant = VariantGreat; break;
4091                                 default: newVariant = VariantCrazyhouse; break;
4092                           }
4093                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4094                           /* Get a move list just to see the header, which
4095                              will tell us whether this is really bug or zh */
4096                           if (ics_getting_history == H_FALSE) {
4097                             ics_getting_history = H_REQUESTED;
4098                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4099                             SendToICS(str);
4100                           }
4101                         }
4102                         new_piece[0] = NULLCHAR;
4103                         sscanf(parse, "game %d white [%s black [%s <- %s",
4104                                &gamenum, white_holding, black_holding,
4105                                new_piece);
4106                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4107                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4108                         /* [HGM] copy holdings to board holdings area */
4109                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4110                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4111                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4112 #if ZIPPY
4113                         if (appData.zippyPlay && first.initDone) {
4114                             ZippyHoldings(white_holding, black_holding,
4115                                           new_piece);
4116                         }
4117 #endif /*ZIPPY*/
4118                         if (tinyLayout || smallLayout) {
4119                             char wh[16], bh[16];
4120                             PackHolding(wh, white_holding);
4121                             PackHolding(bh, black_holding);
4122                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4123                                     gameInfo.white, gameInfo.black);
4124                         } else {
4125                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4126                                     gameInfo.white, white_holding, _("vs."),
4127                                     gameInfo.black, black_holding);
4128                         }
4129                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4130                         DrawPosition(FALSE, boards[currentMove]);
4131                         DisplayTitle(str);
4132                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4133                         sscanf(parse, "game %d white [%s black [%s <- %s",
4134                                &gamenum, white_holding, black_holding,
4135                                new_piece);
4136                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4137                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4138                         /* [HGM] copy holdings to partner-board holdings area */
4139                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4140                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4141                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4142                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4143                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4144                       }
4145                     }
4146                     /* Suppress following prompt */
4147                     if (looking_at(buf, &i, "*% ")) {
4148                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4149                         savingComment = FALSE;
4150                         suppressKibitz = 0;
4151                     }
4152                     next_out = i;
4153                 }
4154                 continue;
4155             }
4156
4157             i++;                /* skip unparsed character and loop back */
4158         }
4159
4160         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4161 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4162 //          SendToPlayer(&buf[next_out], i - next_out);
4163             started != STARTED_HOLDINGS && leftover_start > next_out) {
4164             SendToPlayer(&buf[next_out], leftover_start - next_out);
4165             next_out = i;
4166         }
4167
4168         leftover_len = buf_len - leftover_start;
4169         /* if buffer ends with something we couldn't parse,
4170            reparse it after appending the next read */
4171
4172     } else if (count == 0) {
4173         RemoveInputSource(isr);
4174         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4175     } else {
4176         DisplayFatalError(_("Error reading from ICS"), error, 1);
4177     }
4178 }
4179
4180
4181 /* Board style 12 looks like this:
4182
4183    <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
4184
4185  * The "<12> " is stripped before it gets to this routine.  The two
4186  * trailing 0's (flip state and clock ticking) are later addition, and
4187  * some chess servers may not have them, or may have only the first.
4188  * Additional trailing fields may be added in the future.
4189  */
4190
4191 #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"
4192
4193 #define RELATION_OBSERVING_PLAYED    0
4194 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4195 #define RELATION_PLAYING_MYMOVE      1
4196 #define RELATION_PLAYING_NOTMYMOVE  -1
4197 #define RELATION_EXAMINING           2
4198 #define RELATION_ISOLATED_BOARD     -3
4199 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4200
4201 void
4202 ParseBoard12 (char *string)
4203 {
4204 #if ZIPPY
4205     int i, takeback;
4206     char *bookHit = NULL; // [HGM] book
4207 #endif
4208     GameMode newGameMode;
4209     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4210     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4211     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4212     char to_play, board_chars[200];
4213     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4214     char black[32], white[32];
4215     Board board;
4216     int prevMove = currentMove;
4217     int ticking = 2;
4218     ChessMove moveType;
4219     int fromX, fromY, toX, toY;
4220     char promoChar;
4221     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4222     Boolean weird = FALSE, reqFlag = FALSE;
4223
4224     fromX = fromY = toX = toY = -1;
4225
4226     newGame = FALSE;
4227
4228     if (appData.debugMode)
4229       fprintf(debugFP, "Parsing board: %s\n", string);
4230
4231     move_str[0] = NULLCHAR;
4232     elapsed_time[0] = NULLCHAR;
4233     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4234         int  i = 0, j;
4235         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4236             if(string[i] == ' ') { ranks++; files = 0; }
4237             else files++;
4238             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4239             i++;
4240         }
4241         for(j = 0; j <i; j++) board_chars[j] = string[j];
4242         board_chars[i] = '\0';
4243         string += i + 1;
4244     }
4245     n = sscanf(string, PATTERN, &to_play, &double_push,
4246                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4247                &gamenum, white, black, &relation, &basetime, &increment,
4248                &white_stren, &black_stren, &white_time, &black_time,
4249                &moveNum, str, elapsed_time, move_str, &ics_flip,
4250                &ticking);
4251
4252     if (n < 21) {
4253         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4254         DisplayError(str, 0);
4255         return;
4256     }
4257
4258     /* Convert the move number to internal form */
4259     moveNum = (moveNum - 1) * 2;
4260     if (to_play == 'B') moveNum++;
4261     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4262       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4263                         0, 1);
4264       return;
4265     }
4266
4267     switch (relation) {
4268       case RELATION_OBSERVING_PLAYED:
4269       case RELATION_OBSERVING_STATIC:
4270         if (gamenum == -1) {
4271             /* Old ICC buglet */
4272             relation = RELATION_OBSERVING_STATIC;
4273         }
4274         newGameMode = IcsObserving;
4275         break;
4276       case RELATION_PLAYING_MYMOVE:
4277       case RELATION_PLAYING_NOTMYMOVE:
4278         newGameMode =
4279           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4280             IcsPlayingWhite : IcsPlayingBlack;
4281         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4282         break;
4283       case RELATION_EXAMINING:
4284         newGameMode = IcsExamining;
4285         break;
4286       case RELATION_ISOLATED_BOARD:
4287       default:
4288         /* Just display this board.  If user was doing something else,
4289            we will forget about it until the next board comes. */
4290         newGameMode = IcsIdle;
4291         break;
4292       case RELATION_STARTING_POSITION:
4293         newGameMode = gameMode;
4294         break;
4295     }
4296
4297     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4298         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4299          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4300       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4301       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4302       static int lastBgGame = -1;
4303       char *toSqr;
4304       for (k = 0; k < ranks; k++) {
4305         for (j = 0; j < files; j++)
4306           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4307         if(gameInfo.holdingsWidth > 1) {
4308              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4309              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4310         }
4311       }
4312       CopyBoard(partnerBoard, board);
4313       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4314         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4315         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4316       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4317       if(toSqr = strchr(str, '-')) {
4318         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4319         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4320       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4321       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4322       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4323       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4324       if(twoBoards) {
4325           DisplayWhiteClock(white_time*fac, to_play == 'W');
4326           DisplayBlackClock(black_time*fac, to_play != 'W');
4327           activePartner = to_play;
4328           if(gamenum != lastBgGame) {
4329               char buf[MSG_SIZ];
4330               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4331               DisplayTitle(buf);
4332           }
4333           lastBgGame = gamenum;
4334           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4335                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4336       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4337                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4338       if(!twoBoards) DisplayMessage(partnerStatus, "");
4339         partnerBoardValid = TRUE;
4340       return;
4341     }
4342
4343     if(appData.dualBoard && appData.bgObserve) {
4344         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4345             SendToICS(ics_prefix), SendToICS("pobserve\n");
4346         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4347             char buf[MSG_SIZ];
4348             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4349             SendToICS(buf);
4350         }
4351     }
4352
4353     /* Modify behavior for initial board display on move listing
4354        of wild games.
4355        */
4356     switch (ics_getting_history) {
4357       case H_FALSE:
4358       case H_REQUESTED:
4359         break;
4360       case H_GOT_REQ_HEADER:
4361       case H_GOT_UNREQ_HEADER:
4362         /* This is the initial position of the current game */
4363         gamenum = ics_gamenum;
4364         moveNum = 0;            /* old ICS bug workaround */
4365         if (to_play == 'B') {
4366           startedFromSetupPosition = TRUE;
4367           blackPlaysFirst = TRUE;
4368           moveNum = 1;
4369           if (forwardMostMove == 0) forwardMostMove = 1;
4370           if (backwardMostMove == 0) backwardMostMove = 1;
4371           if (currentMove == 0) currentMove = 1;
4372         }
4373         newGameMode = gameMode;
4374         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4375         break;
4376       case H_GOT_UNWANTED_HEADER:
4377         /* This is an initial board that we don't want */
4378         return;
4379       case H_GETTING_MOVES:
4380         /* Should not happen */
4381         DisplayError(_("Error gathering move list: extra board"), 0);
4382         ics_getting_history = H_FALSE;
4383         return;
4384     }
4385
4386    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4387                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4388                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4389      /* [HGM] We seem to have switched variant unexpectedly
4390       * Try to guess new variant from board size
4391       */
4392           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4393           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4394           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4395           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4396           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4397           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4398           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4399           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4400           /* Get a move list just to see the header, which
4401              will tell us whether this is really bug or zh */
4402           if (ics_getting_history == H_FALSE) {
4403             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4404             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4405             SendToICS(str);
4406           }
4407     }
4408
4409     /* Take action if this is the first board of a new game, or of a
4410        different game than is currently being displayed.  */
4411     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4412         relation == RELATION_ISOLATED_BOARD) {
4413
4414         /* Forget the old game and get the history (if any) of the new one */
4415         if (gameMode != BeginningOfGame) {
4416           Reset(TRUE, TRUE);
4417         }
4418         newGame = TRUE;
4419         if (appData.autoRaiseBoard) BoardToTop();
4420         prevMove = -3;
4421         if (gamenum == -1) {
4422             newGameMode = IcsIdle;
4423         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4424                    appData.getMoveList && !reqFlag) {
4425             /* Need to get game history */
4426             ics_getting_history = H_REQUESTED;
4427             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4428             SendToICS(str);
4429         }
4430
4431         /* Initially flip the board to have black on the bottom if playing
4432            black or if the ICS flip flag is set, but let the user change
4433            it with the Flip View button. */
4434         flipView = appData.autoFlipView ?
4435           (newGameMode == IcsPlayingBlack) || ics_flip :
4436           appData.flipView;
4437
4438         /* Done with values from previous mode; copy in new ones */
4439         gameMode = newGameMode;
4440         ModeHighlight();
4441         ics_gamenum = gamenum;
4442         if (gamenum == gs_gamenum) {
4443             int klen = strlen(gs_kind);
4444             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4445             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4446             gameInfo.event = StrSave(str);
4447         } else {
4448             gameInfo.event = StrSave("ICS game");
4449         }
4450         gameInfo.site = StrSave(appData.icsHost);
4451         gameInfo.date = PGNDate();
4452         gameInfo.round = StrSave("-");
4453         gameInfo.white = StrSave(white);
4454         gameInfo.black = StrSave(black);
4455         timeControl = basetime * 60 * 1000;
4456         timeControl_2 = 0;
4457         timeIncrement = increment * 1000;
4458         movesPerSession = 0;
4459         gameInfo.timeControl = TimeControlTagValue();
4460         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4461   if (appData.debugMode) {
4462     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4463     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4464     setbuf(debugFP, NULL);
4465   }
4466
4467         gameInfo.outOfBook = NULL;
4468
4469         /* Do we have the ratings? */
4470         if (strcmp(player1Name, white) == 0 &&
4471             strcmp(player2Name, black) == 0) {
4472             if (appData.debugMode)
4473               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4474                       player1Rating, player2Rating);
4475             gameInfo.whiteRating = player1Rating;
4476             gameInfo.blackRating = player2Rating;
4477         } else if (strcmp(player2Name, white) == 0 &&
4478                    strcmp(player1Name, black) == 0) {
4479             if (appData.debugMode)
4480               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4481                       player2Rating, player1Rating);
4482             gameInfo.whiteRating = player2Rating;
4483             gameInfo.blackRating = player1Rating;
4484         }
4485         player1Name[0] = player2Name[0] = NULLCHAR;
4486
4487         /* Silence shouts if requested */
4488         if (appData.quietPlay &&
4489             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4490             SendToICS(ics_prefix);
4491             SendToICS("set shout 0\n");
4492         }
4493     }
4494
4495     /* Deal with midgame name changes */
4496     if (!newGame) {
4497         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4498             if (gameInfo.white) free(gameInfo.white);
4499             gameInfo.white = StrSave(white);
4500         }
4501         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4502             if (gameInfo.black) free(gameInfo.black);
4503             gameInfo.black = StrSave(black);
4504         }
4505     }
4506
4507     /* Throw away game result if anything actually changes in examine mode */
4508     if (gameMode == IcsExamining && !newGame) {
4509         gameInfo.result = GameUnfinished;
4510         if (gameInfo.resultDetails != NULL) {
4511             free(gameInfo.resultDetails);
4512             gameInfo.resultDetails = NULL;
4513         }
4514     }
4515
4516     /* In pausing && IcsExamining mode, we ignore boards coming
4517        in if they are in a different variation than we are. */
4518     if (pauseExamInvalid) return;
4519     if (pausing && gameMode == IcsExamining) {
4520         if (moveNum <= pauseExamForwardMostMove) {
4521             pauseExamInvalid = TRUE;
4522             forwardMostMove = pauseExamForwardMostMove;
4523             return;
4524         }
4525     }
4526
4527   if (appData.debugMode) {
4528     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4529   }
4530     /* Parse the board */
4531     for (k = 0; k < ranks; k++) {
4532       for (j = 0; j < files; j++)
4533         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4534       if(gameInfo.holdingsWidth > 1) {
4535            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4536            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4537       }
4538     }
4539     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4540       board[5][BOARD_RGHT+1] = WhiteAngel;
4541       board[6][BOARD_RGHT+1] = WhiteMarshall;
4542       board[1][0] = BlackMarshall;
4543       board[2][0] = BlackAngel;
4544       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4545     }
4546     CopyBoard(boards[moveNum], board);
4547     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4548     if (moveNum == 0) {
4549         startedFromSetupPosition =
4550           !CompareBoards(board, initialPosition);
4551         if(startedFromSetupPosition)
4552             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4553     }
4554
4555     /* [HGM] Set castling rights. Take the outermost Rooks,
4556        to make it also work for FRC opening positions. Note that board12
4557        is really defective for later FRC positions, as it has no way to
4558        indicate which Rook can castle if they are on the same side of King.
4559        For the initial position we grant rights to the outermost Rooks,
4560        and remember thos rights, and we then copy them on positions
4561        later in an FRC game. This means WB might not recognize castlings with
4562        Rooks that have moved back to their original position as illegal,
4563        but in ICS mode that is not its job anyway.
4564     */
4565     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4566     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4567
4568         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4569             if(board[0][i] == WhiteRook) j = i;
4570         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4571         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4572             if(board[0][i] == WhiteRook) j = i;
4573         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4574         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4575             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4576         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4577         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4578             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4579         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4580
4581         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4582         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4583         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4584             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4585         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4586             if(board[BOARD_HEIGHT-1][k] == bKing)
4587                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4588         if(gameInfo.variant == VariantTwoKings) {
4589             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4590             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4591             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4592         }
4593     } else { int r;
4594         r = boards[moveNum][CASTLING][0] = initialRights[0];
4595         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4596         r = boards[moveNum][CASTLING][1] = initialRights[1];
4597         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4598         r = boards[moveNum][CASTLING][3] = initialRights[3];
4599         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4600         r = boards[moveNum][CASTLING][4] = initialRights[4];
4601         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4602         /* wildcastle kludge: always assume King has rights */
4603         r = boards[moveNum][CASTLING][2] = initialRights[2];
4604         r = boards[moveNum][CASTLING][5] = initialRights[5];
4605     }
4606     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4607     boards[moveNum][EP_STATUS] = EP_NONE;
4608     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4609     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4610     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4611
4612
4613     if (ics_getting_history == H_GOT_REQ_HEADER ||
4614         ics_getting_history == H_GOT_UNREQ_HEADER) {
4615         /* This was an initial position from a move list, not
4616            the current position */
4617         return;
4618     }
4619
4620     /* Update currentMove and known move number limits */
4621     newMove = newGame || moveNum > forwardMostMove;
4622
4623     if (newGame) {
4624         forwardMostMove = backwardMostMove = currentMove = moveNum;
4625         if (gameMode == IcsExamining && moveNum == 0) {
4626           /* Workaround for ICS limitation: we are not told the wild
4627              type when starting to examine a game.  But if we ask for
4628              the move list, the move list header will tell us */
4629             ics_getting_history = H_REQUESTED;
4630             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4631             SendToICS(str);
4632         }
4633     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4634                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4635 #if ZIPPY
4636         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4637         /* [HGM] applied this also to an engine that is silently watching        */
4638         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4639             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4640             gameInfo.variant == currentlyInitializedVariant) {
4641           takeback = forwardMostMove - moveNum;
4642           for (i = 0; i < takeback; i++) {
4643             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4644             SendToProgram("undo\n", &first);
4645           }
4646         }
4647 #endif
4648
4649         forwardMostMove = moveNum;
4650         if (!pausing || currentMove > forwardMostMove)
4651           currentMove = forwardMostMove;
4652     } else {
4653         /* New part of history that is not contiguous with old part */
4654         if (pausing && gameMode == IcsExamining) {
4655             pauseExamInvalid = TRUE;
4656             forwardMostMove = pauseExamForwardMostMove;
4657             return;
4658         }
4659         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4660 #if ZIPPY
4661             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4662                 // [HGM] when we will receive the move list we now request, it will be
4663                 // fed to the engine from the first move on. So if the engine is not
4664                 // in the initial position now, bring it there.
4665                 InitChessProgram(&first, 0);
4666             }
4667 #endif
4668             ics_getting_history = H_REQUESTED;
4669             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4670             SendToICS(str);
4671         }
4672         forwardMostMove = backwardMostMove = currentMove = moveNum;
4673     }
4674
4675     /* Update the clocks */
4676     if (strchr(elapsed_time, '.')) {
4677       /* Time is in ms */
4678       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4679       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4680     } else {
4681       /* Time is in seconds */
4682       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4683       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4684     }
4685
4686
4687 #if ZIPPY
4688     if (appData.zippyPlay && newGame &&
4689         gameMode != IcsObserving && gameMode != IcsIdle &&
4690         gameMode != IcsExamining)
4691       ZippyFirstBoard(moveNum, basetime, increment);
4692 #endif
4693
4694     /* Put the move on the move list, first converting
4695        to canonical algebraic form. */
4696     if (moveNum > 0) {
4697   if (appData.debugMode) {
4698     int f = forwardMostMove;
4699     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4700             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4701             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4702     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4703     fprintf(debugFP, "moveNum = %d\n", moveNum);
4704     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4705     setbuf(debugFP, NULL);
4706   }
4707         if (moveNum <= backwardMostMove) {
4708             /* We don't know what the board looked like before
4709                this move.  Punt. */
4710           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4711             strcat(parseList[moveNum - 1], " ");
4712             strcat(parseList[moveNum - 1], elapsed_time);
4713             moveList[moveNum - 1][0] = NULLCHAR;
4714         } else if (strcmp(move_str, "none") == 0) {
4715             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4716             /* Again, we don't know what the board looked like;
4717                this is really the start of the game. */
4718             parseList[moveNum - 1][0] = NULLCHAR;
4719             moveList[moveNum - 1][0] = NULLCHAR;
4720             backwardMostMove = moveNum;
4721             startedFromSetupPosition = TRUE;
4722             fromX = fromY = toX = toY = -1;
4723         } else {
4724           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4725           //                 So we parse the long-algebraic move string in stead of the SAN move
4726           int valid; char buf[MSG_SIZ], *prom;
4727
4728           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4729                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4730           // str looks something like "Q/a1-a2"; kill the slash
4731           if(str[1] == '/')
4732             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4733           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4734           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4735                 strcat(buf, prom); // long move lacks promo specification!
4736           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4737                 if(appData.debugMode)
4738                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4739                 safeStrCpy(move_str, buf, MSG_SIZ);
4740           }
4741           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4742                                 &fromX, &fromY, &toX, &toY, &promoChar)
4743                || ParseOneMove(buf, moveNum - 1, &moveType,
4744                                 &fromX, &fromY, &toX, &toY, &promoChar);
4745           // end of long SAN patch
4746           if (valid) {
4747             (void) CoordsToAlgebraic(boards[moveNum - 1],
4748                                      PosFlags(moveNum - 1),
4749                                      fromY, fromX, toY, toX, promoChar,
4750                                      parseList[moveNum-1]);
4751             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4752               case MT_NONE:
4753               case MT_STALEMATE:
4754               default:
4755                 break;
4756               case MT_CHECK:
4757                 if(gameInfo.variant != VariantShogi)
4758                     strcat(parseList[moveNum - 1], "+");
4759                 break;
4760               case MT_CHECKMATE:
4761               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4762                 strcat(parseList[moveNum - 1], "#");
4763                 break;
4764             }
4765             strcat(parseList[moveNum - 1], " ");
4766             strcat(parseList[moveNum - 1], elapsed_time);
4767             /* currentMoveString is set as a side-effect of ParseOneMove */
4768             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4769             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4770             strcat(moveList[moveNum - 1], "\n");
4771
4772             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4773                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4774               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4775                 ChessSquare old, new = boards[moveNum][k][j];
4776                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4777                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4778                   if(old == new) continue;
4779                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4780                   else if(new == WhiteWazir || new == BlackWazir) {
4781                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4782                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4783                       else boards[moveNum][k][j] = old; // preserve type of Gold
4784                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4785                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4786               }
4787           } else {
4788             /* Move from ICS was illegal!?  Punt. */
4789             if (appData.debugMode) {
4790               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4791               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4792             }
4793             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4794             strcat(parseList[moveNum - 1], " ");
4795             strcat(parseList[moveNum - 1], elapsed_time);
4796             moveList[moveNum - 1][0] = NULLCHAR;
4797             fromX = fromY = toX = toY = -1;
4798           }
4799         }
4800   if (appData.debugMode) {
4801     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4802     setbuf(debugFP, NULL);
4803   }
4804
4805 #if ZIPPY
4806         /* Send move to chess program (BEFORE animating it). */
4807         if (appData.zippyPlay && !newGame && newMove &&
4808            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4809
4810             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4811                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4812                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4813                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4814                             move_str);
4815                     DisplayError(str, 0);
4816                 } else {
4817                     if (first.sendTime) {
4818                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4819                     }
4820                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4821                     if (firstMove && !bookHit) {
4822                         firstMove = FALSE;
4823                         if (first.useColors) {
4824                           SendToProgram(gameMode == IcsPlayingWhite ?
4825                                         "white\ngo\n" :
4826                                         "black\ngo\n", &first);
4827                         } else {
4828                           SendToProgram("go\n", &first);
4829                         }
4830                         first.maybeThinking = TRUE;
4831                     }
4832                 }
4833             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4834               if (moveList[moveNum - 1][0] == NULLCHAR) {
4835                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4836                 DisplayError(str, 0);
4837               } else {
4838                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4839                 SendMoveToProgram(moveNum - 1, &first);
4840               }
4841             }
4842         }
4843 #endif
4844     }
4845
4846     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4847         /* If move comes from a remote source, animate it.  If it
4848            isn't remote, it will have already been animated. */
4849         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4850             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4851         }
4852         if (!pausing && appData.highlightLastMove) {
4853             SetHighlights(fromX, fromY, toX, toY);
4854         }
4855     }
4856
4857     /* Start the clocks */
4858     whiteFlag = blackFlag = FALSE;
4859     appData.clockMode = !(basetime == 0 && increment == 0);
4860     if (ticking == 0) {
4861       ics_clock_paused = TRUE;
4862       StopClocks();
4863     } else if (ticking == 1) {
4864       ics_clock_paused = FALSE;
4865     }
4866     if (gameMode == IcsIdle ||
4867         relation == RELATION_OBSERVING_STATIC ||
4868         relation == RELATION_EXAMINING ||
4869         ics_clock_paused)
4870       DisplayBothClocks();
4871     else
4872       StartClocks();
4873
4874     /* Display opponents and material strengths */
4875     if (gameInfo.variant != VariantBughouse &&
4876         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4877         if (tinyLayout || smallLayout) {
4878             if(gameInfo.variant == VariantNormal)
4879               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4880                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4881                     basetime, increment);
4882             else
4883               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4884                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4885                     basetime, increment, (int) gameInfo.variant);
4886         } else {
4887             if(gameInfo.variant == VariantNormal)
4888               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4889                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4890                     basetime, increment);
4891             else
4892               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4893                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4894                     basetime, increment, VariantName(gameInfo.variant));
4895         }
4896         DisplayTitle(str);
4897   if (appData.debugMode) {
4898     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4899   }
4900     }
4901
4902
4903     /* Display the board */
4904     if (!pausing && !appData.noGUI) {
4905
4906       if (appData.premove)
4907           if (!gotPremove ||
4908              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4909              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4910               ClearPremoveHighlights();
4911
4912       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4913         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4914       DrawPosition(j, boards[currentMove]);
4915
4916       DisplayMove(moveNum - 1);
4917       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4918             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4919               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4920         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4921       }
4922     }
4923
4924     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4925 #if ZIPPY
4926     if(bookHit) { // [HGM] book: simulate book reply
4927         static char bookMove[MSG_SIZ]; // a bit generous?
4928
4929         programStats.nodes = programStats.depth = programStats.time =
4930         programStats.score = programStats.got_only_move = 0;
4931         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4932
4933         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4934         strcat(bookMove, bookHit);
4935         HandleMachineMove(bookMove, &first);
4936     }
4937 #endif
4938 }
4939
4940 void
4941 GetMoveListEvent ()
4942 {
4943     char buf[MSG_SIZ];
4944     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4945         ics_getting_history = H_REQUESTED;
4946         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4947         SendToICS(buf);
4948     }
4949 }
4950
4951 void
4952 SendToBoth (char *msg)
4953 {   // to make it easy to keep two engines in step in dual analysis
4954     SendToProgram(msg, &first);
4955     if(second.analyzing) SendToProgram(msg, &second);
4956 }
4957
4958 void
4959 AnalysisPeriodicEvent (int force)
4960 {
4961     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4962          && !force) || !appData.periodicUpdates)
4963       return;
4964
4965     /* Send . command to Crafty to collect stats */
4966     SendToBoth(".\n");
4967
4968     /* Don't send another until we get a response (this makes
4969        us stop sending to old Crafty's which don't understand
4970        the "." command (sending illegal cmds resets node count & time,
4971        which looks bad)) */
4972     programStats.ok_to_send = 0;
4973 }
4974
4975 void
4976 ics_update_width (int new_width)
4977 {
4978         ics_printf("set width %d\n", new_width);
4979 }
4980
4981 void
4982 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4983 {
4984     char buf[MSG_SIZ];
4985
4986     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4987         // null move in variant where engine does not understand it (for analysis purposes)
4988         SendBoard(cps, moveNum + 1); // send position after move in stead.
4989         return;
4990     }
4991     if (cps->useUsermove) {
4992       SendToProgram("usermove ", cps);
4993     }
4994     if (cps->useSAN) {
4995       char *space;
4996       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4997         int len = space - parseList[moveNum];
4998         memcpy(buf, parseList[moveNum], len);
4999         buf[len++] = '\n';
5000         buf[len] = NULLCHAR;
5001       } else {
5002         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5003       }
5004       SendToProgram(buf, cps);
5005     } else {
5006       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5007         AlphaRank(moveList[moveNum], 4);
5008         SendToProgram(moveList[moveNum], cps);
5009         AlphaRank(moveList[moveNum], 4); // and back
5010       } else
5011       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5012        * the engine. It would be nice to have a better way to identify castle
5013        * moves here. */
5014       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5015                                                                          && cps->useOOCastle) {
5016         int fromX = moveList[moveNum][0] - AAA;
5017         int fromY = moveList[moveNum][1] - ONE;
5018         int toX = moveList[moveNum][2] - AAA;
5019         int toY = moveList[moveNum][3] - ONE;
5020         if((boards[moveNum][fromY][fromX] == WhiteKing
5021             && boards[moveNum][toY][toX] == WhiteRook)
5022            || (boards[moveNum][fromY][fromX] == BlackKing
5023                && boards[moveNum][toY][toX] == BlackRook)) {
5024           if(toX > fromX) SendToProgram("O-O\n", cps);
5025           else SendToProgram("O-O-O\n", cps);
5026         }
5027         else SendToProgram(moveList[moveNum], cps);
5028       } else
5029       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5030         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5031           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5032           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5033                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5034         } else
5035           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5036                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5037         SendToProgram(buf, cps);
5038       }
5039       else SendToProgram(moveList[moveNum], cps);
5040       /* End of additions by Tord */
5041     }
5042
5043     /* [HGM] setting up the opening has brought engine in force mode! */
5044     /*       Send 'go' if we are in a mode where machine should play. */
5045     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5046         (gameMode == TwoMachinesPlay   ||
5047 #if ZIPPY
5048          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5049 #endif
5050          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5051         SendToProgram("go\n", cps);
5052   if (appData.debugMode) {
5053     fprintf(debugFP, "(extra)\n");
5054   }
5055     }
5056     setboardSpoiledMachineBlack = 0;
5057 }
5058
5059 void
5060 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5061 {
5062     char user_move[MSG_SIZ];
5063     char suffix[4];
5064
5065     if(gameInfo.variant == VariantSChess && promoChar) {
5066         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5067         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5068     } else suffix[0] = NULLCHAR;
5069
5070     switch (moveType) {
5071       default:
5072         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5073                 (int)moveType, fromX, fromY, toX, toY);
5074         DisplayError(user_move + strlen("say "), 0);
5075         break;
5076       case WhiteKingSideCastle:
5077       case BlackKingSideCastle:
5078       case WhiteQueenSideCastleWild:
5079       case BlackQueenSideCastleWild:
5080       /* PUSH Fabien */
5081       case WhiteHSideCastleFR:
5082       case BlackHSideCastleFR:
5083       /* POP Fabien */
5084         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5085         break;
5086       case WhiteQueenSideCastle:
5087       case BlackQueenSideCastle:
5088       case WhiteKingSideCastleWild:
5089       case BlackKingSideCastleWild:
5090       /* PUSH Fabien */
5091       case WhiteASideCastleFR:
5092       case BlackASideCastleFR:
5093       /* POP Fabien */
5094         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5095         break;
5096       case WhiteNonPromotion:
5097       case BlackNonPromotion:
5098         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5099         break;
5100       case WhitePromotion:
5101       case BlackPromotion:
5102         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5103            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5104           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5105                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5106                 PieceToChar(WhiteFerz));
5107         else if(gameInfo.variant == VariantGreat)
5108           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5109                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5110                 PieceToChar(WhiteMan));
5111         else
5112           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5113                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5114                 promoChar);
5115         break;
5116       case WhiteDrop:
5117       case BlackDrop:
5118       drop:
5119         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5120                  ToUpper(PieceToChar((ChessSquare) fromX)),
5121                  AAA + toX, ONE + toY);
5122         break;
5123       case IllegalMove:  /* could be a variant we don't quite understand */
5124         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5125       case NormalMove:
5126       case WhiteCapturesEnPassant:
5127       case BlackCapturesEnPassant:
5128         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5129                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5130         break;
5131     }
5132     SendToICS(user_move);
5133     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5134         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5135 }
5136
5137 void
5138 UploadGameEvent ()
5139 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5140     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5141     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5142     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5143       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5144       return;
5145     }
5146     if(gameMode != IcsExamining) { // is this ever not the case?
5147         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5148
5149         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5150           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5151         } else { // on FICS we must first go to general examine mode
5152           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5153         }
5154         if(gameInfo.variant != VariantNormal) {
5155             // try figure out wild number, as xboard names are not always valid on ICS
5156             for(i=1; i<=36; i++) {
5157               snprintf(buf, MSG_SIZ, "wild/%d", i);
5158                 if(StringToVariant(buf) == gameInfo.variant) break;
5159             }
5160             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5161             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5162             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5163         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5164         SendToICS(ics_prefix);
5165         SendToICS(buf);
5166         if(startedFromSetupPosition || backwardMostMove != 0) {
5167           fen = PositionToFEN(backwardMostMove, NULL, 1);
5168           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5169             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5170             SendToICS(buf);
5171           } else { // FICS: everything has to set by separate bsetup commands
5172             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5173             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5174             SendToICS(buf);
5175             if(!WhiteOnMove(backwardMostMove)) {
5176                 SendToICS("bsetup tomove black\n");
5177             }
5178             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5179             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5180             SendToICS(buf);
5181             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5182             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5183             SendToICS(buf);
5184             i = boards[backwardMostMove][EP_STATUS];
5185             if(i >= 0) { // set e.p.
5186               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5187                 SendToICS(buf);
5188             }
5189             bsetup++;
5190           }
5191         }
5192       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5193             SendToICS("bsetup done\n"); // switch to normal examining.
5194     }
5195     for(i = backwardMostMove; i<last; i++) {
5196         char buf[20];
5197         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5198         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5199             int len = strlen(moveList[i]);
5200             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5201             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5202         }
5203         SendToICS(buf);
5204     }
5205     SendToICS(ics_prefix);
5206     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5207 }
5208
5209 void
5210 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5211 {
5212     if (rf == DROP_RANK) {
5213       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5214       sprintf(move, "%c@%c%c\n",
5215                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5216     } else {
5217         if (promoChar == 'x' || promoChar == NULLCHAR) {
5218           sprintf(move, "%c%c%c%c\n",
5219                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5220         } else {
5221             sprintf(move, "%c%c%c%c%c\n",
5222                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5223         }
5224     }
5225 }
5226
5227 void
5228 ProcessICSInitScript (FILE *f)
5229 {
5230     char buf[MSG_SIZ];
5231
5232     while (fgets(buf, MSG_SIZ, f)) {
5233         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5234     }
5235
5236     fclose(f);
5237 }
5238
5239
5240 static int lastX, lastY, selectFlag, dragging;
5241
5242 void
5243 Sweep (int step)
5244 {
5245     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5246     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5247     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5248     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5249     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5250     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5251     do {
5252         promoSweep -= step;
5253         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5254         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5255         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5256         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5257         if(!step) step = -1;
5258     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5259             appData.testLegality && (promoSweep == king ||
5260             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5261     if(toX >= 0) {
5262         int victim = boards[currentMove][toY][toX];
5263         boards[currentMove][toY][toX] = promoSweep;
5264         DrawPosition(FALSE, boards[currentMove]);
5265         boards[currentMove][toY][toX] = victim;
5266     } else
5267     ChangeDragPiece(promoSweep);
5268 }
5269
5270 int
5271 PromoScroll (int x, int y)
5272 {
5273   int step = 0;
5274
5275   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5276   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5277   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5278   if(!step) return FALSE;
5279   lastX = x; lastY = y;
5280   if((promoSweep < BlackPawn) == flipView) step = -step;
5281   if(step > 0) selectFlag = 1;
5282   if(!selectFlag) Sweep(step);
5283   return FALSE;
5284 }
5285
5286 void
5287 NextPiece (int step)
5288 {
5289     ChessSquare piece = boards[currentMove][toY][toX];
5290     do {
5291         pieceSweep -= step;
5292         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5293         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5294         if(!step) step = -1;
5295     } while(PieceToChar(pieceSweep) == '.');
5296     boards[currentMove][toY][toX] = pieceSweep;
5297     DrawPosition(FALSE, boards[currentMove]);
5298     boards[currentMove][toY][toX] = piece;
5299 }
5300 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5301 void
5302 AlphaRank (char *move, int n)
5303 {
5304 //    char *p = move, c; int x, y;
5305
5306     if (appData.debugMode) {
5307         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5308     }
5309
5310     if(move[1]=='*' &&
5311        move[2]>='0' && move[2]<='9' &&
5312        move[3]>='a' && move[3]<='x'    ) {
5313         move[1] = '@';
5314         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5315         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5316     } else
5317     if(move[0]>='0' && move[0]<='9' &&
5318        move[1]>='a' && move[1]<='x' &&
5319        move[2]>='0' && move[2]<='9' &&
5320        move[3]>='a' && move[3]<='x'    ) {
5321         /* input move, Shogi -> normal */
5322         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5323         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5324         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5325         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5326     } else
5327     if(move[1]=='@' &&
5328        move[3]>='0' && move[3]<='9' &&
5329        move[2]>='a' && move[2]<='x'    ) {
5330         move[1] = '*';
5331         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5332         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5333     } else
5334     if(
5335        move[0]>='a' && move[0]<='x' &&
5336        move[3]>='0' && move[3]<='9' &&
5337        move[2]>='a' && move[2]<='x'    ) {
5338          /* output move, normal -> Shogi */
5339         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5340         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5341         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5342         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5343         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5344     }
5345     if (appData.debugMode) {
5346         fprintf(debugFP, "   out = '%s'\n", move);
5347     }
5348 }
5349
5350 char yy_textstr[8000];
5351
5352 /* Parser for moves from gnuchess, ICS, or user typein box */
5353 Boolean
5354 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5355 {
5356     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5357
5358     switch (*moveType) {
5359       case WhitePromotion:
5360       case BlackPromotion:
5361       case WhiteNonPromotion:
5362       case BlackNonPromotion:
5363       case NormalMove:
5364       case WhiteCapturesEnPassant:
5365       case BlackCapturesEnPassant:
5366       case WhiteKingSideCastle:
5367       case WhiteQueenSideCastle:
5368       case BlackKingSideCastle:
5369       case BlackQueenSideCastle:
5370       case WhiteKingSideCastleWild:
5371       case WhiteQueenSideCastleWild:
5372       case BlackKingSideCastleWild:
5373       case BlackQueenSideCastleWild:
5374       /* Code added by Tord: */
5375       case WhiteHSideCastleFR:
5376       case WhiteASideCastleFR:
5377       case BlackHSideCastleFR:
5378       case BlackASideCastleFR:
5379       /* End of code added by Tord */
5380       case IllegalMove:         /* bug or odd chess variant */
5381         *fromX = currentMoveString[0] - AAA;
5382         *fromY = currentMoveString[1] - ONE;
5383         *toX = currentMoveString[2] - AAA;
5384         *toY = currentMoveString[3] - ONE;
5385         *promoChar = currentMoveString[4];
5386         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5387             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5388     if (appData.debugMode) {
5389         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5390     }
5391             *fromX = *fromY = *toX = *toY = 0;
5392             return FALSE;
5393         }
5394         if (appData.testLegality) {
5395           return (*moveType != IllegalMove);
5396         } else {
5397           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5398                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5399         }
5400
5401       case WhiteDrop:
5402       case BlackDrop:
5403         *fromX = *moveType == WhiteDrop ?
5404           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5405           (int) CharToPiece(ToLower(currentMoveString[0]));
5406         *fromY = DROP_RANK;
5407         *toX = currentMoveString[2] - AAA;
5408         *toY = currentMoveString[3] - ONE;
5409         *promoChar = NULLCHAR;
5410         return TRUE;
5411
5412       case AmbiguousMove:
5413       case ImpossibleMove:
5414       case EndOfFile:
5415       case ElapsedTime:
5416       case Comment:
5417       case PGNTag:
5418       case NAG:
5419       case WhiteWins:
5420       case BlackWins:
5421       case GameIsDrawn:
5422       default:
5423     if (appData.debugMode) {
5424         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5425     }
5426         /* bug? */
5427         *fromX = *fromY = *toX = *toY = 0;
5428         *promoChar = NULLCHAR;
5429         return FALSE;
5430     }
5431 }
5432
5433 Boolean pushed = FALSE;
5434 char *lastParseAttempt;
5435
5436 void
5437 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5438 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5439   int fromX, fromY, toX, toY; char promoChar;
5440   ChessMove moveType;
5441   Boolean valid;
5442   int nr = 0;
5443
5444   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5445   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5446     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5447     pushed = TRUE;
5448   }
5449   endPV = forwardMostMove;
5450   do {
5451     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5452     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5453     lastParseAttempt = pv;
5454     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5455     if(!valid && nr == 0 &&
5456        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5457         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5458         // Hande case where played move is different from leading PV move
5459         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5460         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5461         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5462         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5463           endPV += 2; // if position different, keep this
5464           moveList[endPV-1][0] = fromX + AAA;
5465           moveList[endPV-1][1] = fromY + ONE;
5466           moveList[endPV-1][2] = toX + AAA;
5467           moveList[endPV-1][3] = toY + ONE;
5468           parseList[endPV-1][0] = NULLCHAR;
5469           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5470         }
5471       }
5472     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5473     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5474     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5475     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5476         valid++; // allow comments in PV
5477         continue;
5478     }
5479     nr++;
5480     if(endPV+1 > framePtr) break; // no space, truncate
5481     if(!valid) break;
5482     endPV++;
5483     CopyBoard(boards[endPV], boards[endPV-1]);
5484     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5485     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5486     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5487     CoordsToAlgebraic(boards[endPV - 1],
5488                              PosFlags(endPV - 1),
5489                              fromY, fromX, toY, toX, promoChar,
5490                              parseList[endPV - 1]);
5491   } while(valid);
5492   if(atEnd == 2) return; // used hidden, for PV conversion
5493   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5494   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5495   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5496                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5497   DrawPosition(TRUE, boards[currentMove]);
5498 }
5499
5500 int
5501 MultiPV (ChessProgramState *cps)
5502 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5503         int i;
5504         for(i=0; i<cps->nrOptions; i++)
5505             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5506                 return i;
5507         return -1;
5508 }
5509
5510 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5511
5512 Boolean
5513 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5514 {
5515         int startPV, multi, lineStart, origIndex = index;
5516         char *p, buf2[MSG_SIZ];
5517         ChessProgramState *cps = (pane ? &second : &first);
5518
5519         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5520         lastX = x; lastY = y;
5521         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5522         lineStart = startPV = index;
5523         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5524         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5525         index = startPV;
5526         do{ while(buf[index] && buf[index] != '\n') index++;
5527         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5528         buf[index] = 0;
5529         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5530                 int n = cps->option[multi].value;
5531                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5532                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5533                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5534                 cps->option[multi].value = n;
5535                 *start = *end = 0;
5536                 return FALSE;
5537         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5538                 ExcludeClick(origIndex - lineStart);
5539                 return FALSE;
5540         }
5541         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5542         *start = startPV; *end = index-1;
5543         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5544         return TRUE;
5545 }
5546
5547 char *
5548 PvToSAN (char *pv)
5549 {
5550         static char buf[10*MSG_SIZ];
5551         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5552         *buf = NULLCHAR;
5553         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5554         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5555         for(i = forwardMostMove; i<endPV; i++){
5556             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5557             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5558             k += strlen(buf+k);
5559         }
5560         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5561         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5562         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5563         endPV = savedEnd;
5564         return buf;
5565 }
5566
5567 Boolean
5568 LoadPV (int x, int y)
5569 { // called on right mouse click to load PV
5570   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5571   lastX = x; lastY = y;
5572   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5573   extendGame = FALSE;
5574   return TRUE;
5575 }
5576
5577 void
5578 UnLoadPV ()
5579 {
5580   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5581   if(endPV < 0) return;
5582   if(appData.autoCopyPV) CopyFENToClipboard();
5583   endPV = -1;
5584   if(extendGame && currentMove > forwardMostMove) {
5585         Boolean saveAnimate = appData.animate;
5586         if(pushed) {
5587             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5588                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5589             } else storedGames--; // abandon shelved tail of original game
5590         }
5591         pushed = FALSE;
5592         forwardMostMove = currentMove;
5593         currentMove = oldFMM;
5594         appData.animate = FALSE;
5595         ToNrEvent(forwardMostMove);
5596         appData.animate = saveAnimate;
5597   }
5598   currentMove = forwardMostMove;
5599   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5600   ClearPremoveHighlights();
5601   DrawPosition(TRUE, boards[currentMove]);
5602 }
5603
5604 void
5605 MovePV (int x, int y, int h)
5606 { // step through PV based on mouse coordinates (called on mouse move)
5607   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5608
5609   // we must somehow check if right button is still down (might be released off board!)
5610   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5611   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5612   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5613   if(!step) return;
5614   lastX = x; lastY = y;
5615
5616   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5617   if(endPV < 0) return;
5618   if(y < margin) step = 1; else
5619   if(y > h - margin) step = -1;
5620   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5621   currentMove += step;
5622   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5623   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5624                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5625   DrawPosition(FALSE, boards[currentMove]);
5626 }
5627
5628
5629 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5630 // All positions will have equal probability, but the current method will not provide a unique
5631 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5632 #define DARK 1
5633 #define LITE 2
5634 #define ANY 3
5635
5636 int squaresLeft[4];
5637 int piecesLeft[(int)BlackPawn];
5638 int seed, nrOfShuffles;
5639
5640 void
5641 GetPositionNumber ()
5642 {       // sets global variable seed
5643         int i;
5644
5645         seed = appData.defaultFrcPosition;
5646         if(seed < 0) { // randomize based on time for negative FRC position numbers
5647                 for(i=0; i<50; i++) seed += random();
5648                 seed = random() ^ random() >> 8 ^ random() << 8;
5649                 if(seed<0) seed = -seed;
5650         }
5651 }
5652
5653 int
5654 put (Board board, int pieceType, int rank, int n, int shade)
5655 // put the piece on the (n-1)-th empty squares of the given shade
5656 {
5657         int i;
5658
5659         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5660                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5661                         board[rank][i] = (ChessSquare) pieceType;
5662                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5663                         squaresLeft[ANY]--;
5664                         piecesLeft[pieceType]--;
5665                         return i;
5666                 }
5667         }
5668         return -1;
5669 }
5670
5671
5672 void
5673 AddOnePiece (Board board, int pieceType, int rank, int shade)
5674 // calculate where the next piece goes, (any empty square), and put it there
5675 {
5676         int i;
5677
5678         i = seed % squaresLeft[shade];
5679         nrOfShuffles *= squaresLeft[shade];
5680         seed /= squaresLeft[shade];
5681         put(board, pieceType, rank, i, shade);
5682 }
5683
5684 void
5685 AddTwoPieces (Board board, int pieceType, int rank)
5686 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5687 {
5688         int i, n=squaresLeft[ANY], j=n-1, k;
5689
5690         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5691         i = seed % k;  // pick one
5692         nrOfShuffles *= k;
5693         seed /= k;
5694         while(i >= j) i -= j--;
5695         j = n - 1 - j; i += j;
5696         put(board, pieceType, rank, j, ANY);
5697         put(board, pieceType, rank, i, ANY);
5698 }
5699
5700 void
5701 SetUpShuffle (Board board, int number)
5702 {
5703         int i, p, first=1;
5704
5705         GetPositionNumber(); nrOfShuffles = 1;
5706
5707         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5708         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5709         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5710
5711         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5712
5713         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5714             p = (int) board[0][i];
5715             if(p < (int) BlackPawn) piecesLeft[p] ++;
5716             board[0][i] = EmptySquare;
5717         }
5718
5719         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5720             // shuffles restricted to allow normal castling put KRR first
5721             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5722                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5723             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5724                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5725             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5726                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5727             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5728                 put(board, WhiteRook, 0, 0, ANY);
5729             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5730         }
5731
5732         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5733             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5734             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5735                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5736                 while(piecesLeft[p] >= 2) {
5737                     AddOnePiece(board, p, 0, LITE);
5738                     AddOnePiece(board, p, 0, DARK);
5739                 }
5740                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5741             }
5742
5743         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5744             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5745             // but we leave King and Rooks for last, to possibly obey FRC restriction
5746             if(p == (int)WhiteRook) continue;
5747             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5748             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5749         }
5750
5751         // now everything is placed, except perhaps King (Unicorn) and Rooks
5752
5753         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5754             // Last King gets castling rights
5755             while(piecesLeft[(int)WhiteUnicorn]) {
5756                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5757                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5758             }
5759
5760             while(piecesLeft[(int)WhiteKing]) {
5761                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5762                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5763             }
5764
5765
5766         } else {
5767             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5768             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5769         }
5770
5771         // Only Rooks can be left; simply place them all
5772         while(piecesLeft[(int)WhiteRook]) {
5773                 i = put(board, WhiteRook, 0, 0, ANY);
5774                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5775                         if(first) {
5776                                 first=0;
5777                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5778                         }
5779                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5780                 }
5781         }
5782         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5783             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5784         }
5785
5786         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5787 }
5788
5789 int
5790 SetCharTable (char *table, const char * map)
5791 /* [HGM] moved here from winboard.c because of its general usefulness */
5792 /*       Basically a safe strcpy that uses the last character as King */
5793 {
5794     int result = FALSE; int NrPieces;
5795
5796     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5797                     && NrPieces >= 12 && !(NrPieces&1)) {
5798         int i; /* [HGM] Accept even length from 12 to 34 */
5799
5800         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5801         for( i=0; i<NrPieces/2-1; i++ ) {
5802             table[i] = map[i];
5803             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5804         }
5805         table[(int) WhiteKing]  = map[NrPieces/2-1];
5806         table[(int) BlackKing]  = map[NrPieces-1];
5807
5808         result = TRUE;
5809     }
5810
5811     return result;
5812 }
5813
5814 void
5815 Prelude (Board board)
5816 {       // [HGM] superchess: random selection of exo-pieces
5817         int i, j, k; ChessSquare p;
5818         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5819
5820         GetPositionNumber(); // use FRC position number
5821
5822         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5823             SetCharTable(pieceToChar, appData.pieceToCharTable);
5824             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5825                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5826         }
5827
5828         j = seed%4;                 seed /= 4;
5829         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5830         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5831         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5832         j = seed%3 + (seed%3 >= j); seed /= 3;
5833         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5834         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5835         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5836         j = seed%3;                 seed /= 3;
5837         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5838         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5839         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5840         j = seed%2 + (seed%2 >= j); seed /= 2;
5841         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5842         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5843         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5844         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5845         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5846         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5847         put(board, exoPieces[0],    0, 0, ANY);
5848         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5849 }
5850
5851 void
5852 InitPosition (int redraw)
5853 {
5854     ChessSquare (* pieces)[BOARD_FILES];
5855     int i, j, pawnRow, overrule,
5856     oldx = gameInfo.boardWidth,
5857     oldy = gameInfo.boardHeight,
5858     oldh = gameInfo.holdingsWidth;
5859     static int oldv;
5860
5861     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5862
5863     /* [AS] Initialize pv info list [HGM] and game status */
5864     {
5865         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5866             pvInfoList[i].depth = 0;
5867             boards[i][EP_STATUS] = EP_NONE;
5868             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5869         }
5870
5871         initialRulePlies = 0; /* 50-move counter start */
5872
5873         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5874         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5875     }
5876
5877
5878     /* [HGM] logic here is completely changed. In stead of full positions */
5879     /* the initialized data only consist of the two backranks. The switch */
5880     /* selects which one we will use, which is than copied to the Board   */
5881     /* initialPosition, which for the rest is initialized by Pawns and    */
5882     /* empty squares. This initial position is then copied to boards[0],  */
5883     /* possibly after shuffling, so that it remains available.            */
5884
5885     gameInfo.holdingsWidth = 0; /* default board sizes */
5886     gameInfo.boardWidth    = 8;
5887     gameInfo.boardHeight   = 8;
5888     gameInfo.holdingsSize  = 0;
5889     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5890     for(i=0; i<BOARD_FILES-2; i++)
5891       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5892     initialPosition[EP_STATUS] = EP_NONE;
5893     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5894     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5895          SetCharTable(pieceNickName, appData.pieceNickNames);
5896     else SetCharTable(pieceNickName, "............");
5897     pieces = FIDEArray;
5898
5899     switch (gameInfo.variant) {
5900     case VariantFischeRandom:
5901       shuffleOpenings = TRUE;
5902     default:
5903       break;
5904     case VariantShatranj:
5905       pieces = ShatranjArray;
5906       nrCastlingRights = 0;
5907       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5908       break;
5909     case VariantMakruk:
5910       pieces = makrukArray;
5911       nrCastlingRights = 0;
5912       startedFromSetupPosition = TRUE;
5913       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5914       break;
5915     case VariantASEAN:
5916       pieces = aseanArray;
5917       nrCastlingRights = 0;
5918       startedFromSetupPosition = TRUE;
5919       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5920       break;
5921     case VariantTwoKings:
5922       pieces = twoKingsArray;
5923       break;
5924     case VariantGrand:
5925       pieces = GrandArray;
5926       nrCastlingRights = 0;
5927       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5928       gameInfo.boardWidth = 10;
5929       gameInfo.boardHeight = 10;
5930       gameInfo.holdingsSize = 7;
5931       break;
5932     case VariantCapaRandom:
5933       shuffleOpenings = TRUE;
5934     case VariantCapablanca:
5935       pieces = CapablancaArray;
5936       gameInfo.boardWidth = 10;
5937       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5938       break;
5939     case VariantGothic:
5940       pieces = GothicArray;
5941       gameInfo.boardWidth = 10;
5942       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5943       break;
5944     case VariantSChess:
5945       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5946       gameInfo.holdingsSize = 7;
5947       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5948       break;
5949     case VariantJanus:
5950       pieces = JanusArray;
5951       gameInfo.boardWidth = 10;
5952       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5953       nrCastlingRights = 6;
5954         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5955         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5956         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5957         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5958         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5959         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5960       break;
5961     case VariantFalcon:
5962       pieces = FalconArray;
5963       gameInfo.boardWidth = 10;
5964       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5965       break;
5966     case VariantXiangqi:
5967       pieces = XiangqiArray;
5968       gameInfo.boardWidth  = 9;
5969       gameInfo.boardHeight = 10;
5970       nrCastlingRights = 0;
5971       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5972       break;
5973     case VariantShogi:
5974       pieces = ShogiArray;
5975       gameInfo.boardWidth  = 9;
5976       gameInfo.boardHeight = 9;
5977       gameInfo.holdingsSize = 7;
5978       nrCastlingRights = 0;
5979       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5980       break;
5981     case VariantCourier:
5982       pieces = CourierArray;
5983       gameInfo.boardWidth  = 12;
5984       nrCastlingRights = 0;
5985       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5986       break;
5987     case VariantKnightmate:
5988       pieces = KnightmateArray;
5989       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5990       break;
5991     case VariantSpartan:
5992       pieces = SpartanArray;
5993       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5994       break;
5995     case VariantFairy:
5996       pieces = fairyArray;
5997       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5998       break;
5999     case VariantGreat:
6000       pieces = GreatArray;
6001       gameInfo.boardWidth = 10;
6002       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6003       gameInfo.holdingsSize = 8;
6004       break;
6005     case VariantSuper:
6006       pieces = FIDEArray;
6007       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6008       gameInfo.holdingsSize = 8;
6009       startedFromSetupPosition = TRUE;
6010       break;
6011     case VariantCrazyhouse:
6012     case VariantBughouse:
6013       pieces = FIDEArray;
6014       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6015       gameInfo.holdingsSize = 5;
6016       break;
6017     case VariantWildCastle:
6018       pieces = FIDEArray;
6019       /* !!?shuffle with kings guaranteed to be on d or e file */
6020       shuffleOpenings = 1;
6021       break;
6022     case VariantNoCastle:
6023       pieces = FIDEArray;
6024       nrCastlingRights = 0;
6025       /* !!?unconstrained back-rank shuffle */
6026       shuffleOpenings = 1;
6027       break;
6028     }
6029
6030     overrule = 0;
6031     if(appData.NrFiles >= 0) {
6032         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6033         gameInfo.boardWidth = appData.NrFiles;
6034     }
6035     if(appData.NrRanks >= 0) {
6036         gameInfo.boardHeight = appData.NrRanks;
6037     }
6038     if(appData.holdingsSize >= 0) {
6039         i = appData.holdingsSize;
6040         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6041         gameInfo.holdingsSize = i;
6042     }
6043     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6044     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6045         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6046
6047     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6048     if(pawnRow < 1) pawnRow = 1;
6049     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6050
6051     /* User pieceToChar list overrules defaults */
6052     if(appData.pieceToCharTable != NULL)
6053         SetCharTable(pieceToChar, appData.pieceToCharTable);
6054
6055     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6056
6057         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6058             s = (ChessSquare) 0; /* account holding counts in guard band */
6059         for( i=0; i<BOARD_HEIGHT; i++ )
6060             initialPosition[i][j] = s;
6061
6062         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6063         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6064         initialPosition[pawnRow][j] = WhitePawn;
6065         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6066         if(gameInfo.variant == VariantXiangqi) {
6067             if(j&1) {
6068                 initialPosition[pawnRow][j] =
6069                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6070                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6071                    initialPosition[2][j] = WhiteCannon;
6072                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6073                 }
6074             }
6075         }
6076         if(gameInfo.variant == VariantGrand) {
6077             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6078                initialPosition[0][j] = WhiteRook;
6079                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6080             }
6081         }
6082         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6083     }
6084     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6085
6086             j=BOARD_LEFT+1;
6087             initialPosition[1][j] = WhiteBishop;
6088             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6089             j=BOARD_RGHT-2;
6090             initialPosition[1][j] = WhiteRook;
6091             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6092     }
6093
6094     if( nrCastlingRights == -1) {
6095         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6096         /*       This sets default castling rights from none to normal corners   */
6097         /* Variants with other castling rights must set them themselves above    */
6098         nrCastlingRights = 6;
6099
6100         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6101         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6102         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6103         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6104         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6105         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6106      }
6107
6108      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6109      if(gameInfo.variant == VariantGreat) { // promotion commoners
6110         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6111         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6112         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6113         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6114      }
6115      if( gameInfo.variant == VariantSChess ) {
6116       initialPosition[1][0] = BlackMarshall;
6117       initialPosition[2][0] = BlackAngel;
6118       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6119       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6120       initialPosition[1][1] = initialPosition[2][1] =
6121       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6122      }
6123   if (appData.debugMode) {
6124     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6125   }
6126     if(shuffleOpenings) {
6127         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6128         startedFromSetupPosition = TRUE;
6129     }
6130     if(startedFromPositionFile) {
6131       /* [HGM] loadPos: use PositionFile for every new game */
6132       CopyBoard(initialPosition, filePosition);
6133       for(i=0; i<nrCastlingRights; i++)
6134           initialRights[i] = filePosition[CASTLING][i];
6135       startedFromSetupPosition = TRUE;
6136     }
6137
6138     CopyBoard(boards[0], initialPosition);
6139
6140     if(oldx != gameInfo.boardWidth ||
6141        oldy != gameInfo.boardHeight ||
6142        oldv != gameInfo.variant ||
6143        oldh != gameInfo.holdingsWidth
6144                                          )
6145             InitDrawingSizes(-2 ,0);
6146
6147     oldv = gameInfo.variant;
6148     if (redraw)
6149       DrawPosition(TRUE, boards[currentMove]);
6150 }
6151
6152 void
6153 SendBoard (ChessProgramState *cps, int moveNum)
6154 {
6155     char message[MSG_SIZ];
6156
6157     if (cps->useSetboard) {
6158       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6159       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6160       SendToProgram(message, cps);
6161       free(fen);
6162
6163     } else {
6164       ChessSquare *bp;
6165       int i, j, left=0, right=BOARD_WIDTH;
6166       /* Kludge to set black to move, avoiding the troublesome and now
6167        * deprecated "black" command.
6168        */
6169       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6170         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6171
6172       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6173
6174       SendToProgram("edit\n", cps);
6175       SendToProgram("#\n", cps);
6176       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6177         bp = &boards[moveNum][i][left];
6178         for (j = left; j < right; j++, bp++) {
6179           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6180           if ((int) *bp < (int) BlackPawn) {
6181             if(j == BOARD_RGHT+1)
6182                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6183             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6184             if(message[0] == '+' || message[0] == '~') {
6185               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6186                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6187                         AAA + j, ONE + i);
6188             }
6189             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6190                 message[1] = BOARD_RGHT   - 1 - j + '1';
6191                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6192             }
6193             SendToProgram(message, cps);
6194           }
6195         }
6196       }
6197
6198       SendToProgram("c\n", cps);
6199       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6200         bp = &boards[moveNum][i][left];
6201         for (j = left; j < right; j++, bp++) {
6202           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6203           if (((int) *bp != (int) EmptySquare)
6204               && ((int) *bp >= (int) BlackPawn)) {
6205             if(j == BOARD_LEFT-2)
6206                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6207             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6208                     AAA + j, ONE + i);
6209             if(message[0] == '+' || message[0] == '~') {
6210               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6211                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6212                         AAA + j, ONE + i);
6213             }
6214             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6215                 message[1] = BOARD_RGHT   - 1 - j + '1';
6216                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6217             }
6218             SendToProgram(message, cps);
6219           }
6220         }
6221       }
6222
6223       SendToProgram(".\n", cps);
6224     }
6225     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6226 }
6227
6228 char exclusionHeader[MSG_SIZ];
6229 int exCnt, excludePtr;
6230 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6231 static Exclusion excluTab[200];
6232 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6233
6234 static void
6235 WriteMap (int s)
6236 {
6237     int j;
6238     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6239     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6240 }
6241
6242 static void
6243 ClearMap ()
6244 {
6245     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6246     excludePtr = 24; exCnt = 0;
6247     WriteMap(0);
6248 }
6249
6250 static void
6251 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6252 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6253     char buf[2*MOVE_LEN], *p;
6254     Exclusion *e = excluTab;
6255     int i;
6256     for(i=0; i<exCnt; i++)
6257         if(e[i].ff == fromX && e[i].fr == fromY &&
6258            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6259     if(i == exCnt) { // was not in exclude list; add it
6260         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6261         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6262             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6263             return; // abort
6264         }
6265         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6266         excludePtr++; e[i].mark = excludePtr++;
6267         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6268         exCnt++;
6269     }
6270     exclusionHeader[e[i].mark] = state;
6271 }
6272
6273 static int
6274 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6275 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6276     char buf[MSG_SIZ];
6277     int j, k;
6278     ChessMove moveType;
6279     if((signed char)promoChar == -1) { // kludge to indicate best move
6280         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6281             return 1; // if unparsable, abort
6282     }
6283     // update exclusion map (resolving toggle by consulting existing state)
6284     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6285     j = k%8; k >>= 3;
6286     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6287     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6288          excludeMap[k] |=   1<<j;
6289     else excludeMap[k] &= ~(1<<j);
6290     // update header
6291     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6292     // inform engine
6293     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6294     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6295     SendToBoth(buf);
6296     return (state == '+');
6297 }
6298
6299 static void
6300 ExcludeClick (int index)
6301 {
6302     int i, j;
6303     Exclusion *e = excluTab;
6304     if(index < 25) { // none, best or tail clicked
6305         if(index < 13) { // none: include all
6306             WriteMap(0); // clear map
6307             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6308             SendToBoth("include all\n"); // and inform engine
6309         } else if(index > 18) { // tail
6310             if(exclusionHeader[19] == '-') { // tail was excluded
6311                 SendToBoth("include all\n");
6312                 WriteMap(0); // clear map completely
6313                 // now re-exclude selected moves
6314                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6315                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6316             } else { // tail was included or in mixed state
6317                 SendToBoth("exclude all\n");
6318                 WriteMap(0xFF); // fill map completely
6319                 // now re-include selected moves
6320                 j = 0; // count them
6321                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6322                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6323                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6324             }
6325         } else { // best
6326             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6327         }
6328     } else {
6329         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6330             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6331             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6332             break;
6333         }
6334     }
6335 }
6336
6337 ChessSquare
6338 DefaultPromoChoice (int white)
6339 {
6340     ChessSquare result;
6341     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6342        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6343         result = WhiteFerz; // no choice
6344     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6345         result= WhiteKing; // in Suicide Q is the last thing we want
6346     else if(gameInfo.variant == VariantSpartan)
6347         result = white ? WhiteQueen : WhiteAngel;
6348     else result = WhiteQueen;
6349     if(!white) result = WHITE_TO_BLACK result;
6350     return result;
6351 }
6352
6353 static int autoQueen; // [HGM] oneclick
6354
6355 int
6356 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6357 {
6358     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6359     /* [HGM] add Shogi promotions */
6360     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6361     ChessSquare piece;
6362     ChessMove moveType;
6363     Boolean premove;
6364
6365     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6366     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6367
6368     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6369       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6370         return FALSE;
6371
6372     piece = boards[currentMove][fromY][fromX];
6373     if(gameInfo.variant == VariantShogi) {
6374         promotionZoneSize = BOARD_HEIGHT/3;
6375         highestPromotingPiece = (int)WhiteFerz;
6376     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6377         promotionZoneSize = 3;
6378     }
6379
6380     // Treat Lance as Pawn when it is not representing Amazon
6381     if(gameInfo.variant != VariantSuper) {
6382         if(piece == WhiteLance) piece = WhitePawn; else
6383         if(piece == BlackLance) piece = BlackPawn;
6384     }
6385
6386     // next weed out all moves that do not touch the promotion zone at all
6387     if((int)piece >= BlackPawn) {
6388         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6389              return FALSE;
6390         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6391     } else {
6392         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6393            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6394     }
6395
6396     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6397
6398     // weed out mandatory Shogi promotions
6399     if(gameInfo.variant == VariantShogi) {
6400         if(piece >= BlackPawn) {
6401             if(toY == 0 && piece == BlackPawn ||
6402                toY == 0 && piece == BlackQueen ||
6403                toY <= 1 && piece == BlackKnight) {
6404                 *promoChoice = '+';
6405                 return FALSE;
6406             }
6407         } else {
6408             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6409                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6410                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6411                 *promoChoice = '+';
6412                 return FALSE;
6413             }
6414         }
6415     }
6416
6417     // weed out obviously illegal Pawn moves
6418     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6419         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6420         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6421         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6422         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6423         // note we are not allowed to test for valid (non-)capture, due to premove
6424     }
6425
6426     // we either have a choice what to promote to, or (in Shogi) whether to promote
6427     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6428        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6429         *promoChoice = PieceToChar(BlackFerz);  // no choice
6430         return FALSE;
6431     }
6432     // no sense asking what we must promote to if it is going to explode...
6433     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6434         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6435         return FALSE;
6436     }
6437     // give caller the default choice even if we will not make it
6438     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6439     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6440     if(        sweepSelect && gameInfo.variant != VariantGreat
6441                            && gameInfo.variant != VariantGrand
6442                            && gameInfo.variant != VariantSuper) return FALSE;
6443     if(autoQueen) return FALSE; // predetermined
6444
6445     // suppress promotion popup on illegal moves that are not premoves
6446     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6447               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6448     if(appData.testLegality && !premove) {
6449         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6450                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6451         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6452             return FALSE;
6453     }
6454
6455     return TRUE;
6456 }
6457
6458 int
6459 InPalace (int row, int column)
6460 {   /* [HGM] for Xiangqi */
6461     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6462          column < (BOARD_WIDTH + 4)/2 &&
6463          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6464     return FALSE;
6465 }
6466
6467 int
6468 PieceForSquare (int x, int y)
6469 {
6470   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6471      return -1;
6472   else
6473      return boards[currentMove][y][x];
6474 }
6475
6476 int
6477 OKToStartUserMove (int x, int y)
6478 {
6479     ChessSquare from_piece;
6480     int white_piece;
6481
6482     if (matchMode) return FALSE;
6483     if (gameMode == EditPosition) return TRUE;
6484
6485     if (x >= 0 && y >= 0)
6486       from_piece = boards[currentMove][y][x];
6487     else
6488       from_piece = EmptySquare;
6489
6490     if (from_piece == EmptySquare) return FALSE;
6491
6492     white_piece = (int)from_piece >= (int)WhitePawn &&
6493       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6494
6495     switch (gameMode) {
6496       case AnalyzeFile:
6497       case TwoMachinesPlay:
6498       case EndOfGame:
6499         return FALSE;
6500
6501       case IcsObserving:
6502       case IcsIdle:
6503         return FALSE;
6504
6505       case MachinePlaysWhite:
6506       case IcsPlayingBlack:
6507         if (appData.zippyPlay) return FALSE;
6508         if (white_piece) {
6509             DisplayMoveError(_("You are playing Black"));
6510             return FALSE;
6511         }
6512         break;
6513
6514       case MachinePlaysBlack:
6515       case IcsPlayingWhite:
6516         if (appData.zippyPlay) return FALSE;
6517         if (!white_piece) {
6518             DisplayMoveError(_("You are playing White"));
6519             return FALSE;
6520         }
6521         break;
6522
6523       case PlayFromGameFile:
6524             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6525       case EditGame:
6526         if (!white_piece && WhiteOnMove(currentMove)) {
6527             DisplayMoveError(_("It is White's turn"));
6528             return FALSE;
6529         }
6530         if (white_piece && !WhiteOnMove(currentMove)) {
6531             DisplayMoveError(_("It is Black's turn"));
6532             return FALSE;
6533         }
6534         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6535             /* Editing correspondence game history */
6536             /* Could disallow this or prompt for confirmation */
6537             cmailOldMove = -1;
6538         }
6539         break;
6540
6541       case BeginningOfGame:
6542         if (appData.icsActive) return FALSE;
6543         if (!appData.noChessProgram) {
6544             if (!white_piece) {
6545                 DisplayMoveError(_("You are playing White"));
6546                 return FALSE;
6547             }
6548         }
6549         break;
6550
6551       case Training:
6552         if (!white_piece && WhiteOnMove(currentMove)) {
6553             DisplayMoveError(_("It is White's turn"));
6554             return FALSE;
6555         }
6556         if (white_piece && !WhiteOnMove(currentMove)) {
6557             DisplayMoveError(_("It is Black's turn"));
6558             return FALSE;
6559         }
6560         break;
6561
6562       default:
6563       case IcsExamining:
6564         break;
6565     }
6566     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6567         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6568         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6569         && gameMode != AnalyzeFile && gameMode != Training) {
6570         DisplayMoveError(_("Displayed position is not current"));
6571         return FALSE;
6572     }
6573     return TRUE;
6574 }
6575
6576 Boolean
6577 OnlyMove (int *x, int *y, Boolean captures)
6578 {
6579     DisambiguateClosure cl;
6580     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6581     switch(gameMode) {
6582       case MachinePlaysBlack:
6583       case IcsPlayingWhite:
6584       case BeginningOfGame:
6585         if(!WhiteOnMove(currentMove)) return FALSE;
6586         break;
6587       case MachinePlaysWhite:
6588       case IcsPlayingBlack:
6589         if(WhiteOnMove(currentMove)) return FALSE;
6590         break;
6591       case EditGame:
6592         break;
6593       default:
6594         return FALSE;
6595     }
6596     cl.pieceIn = EmptySquare;
6597     cl.rfIn = *y;
6598     cl.ffIn = *x;
6599     cl.rtIn = -1;
6600     cl.ftIn = -1;
6601     cl.promoCharIn = NULLCHAR;
6602     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6603     if( cl.kind == NormalMove ||
6604         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6605         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6606         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6607       fromX = cl.ff;
6608       fromY = cl.rf;
6609       *x = cl.ft;
6610       *y = cl.rt;
6611       return TRUE;
6612     }
6613     if(cl.kind != ImpossibleMove) return FALSE;
6614     cl.pieceIn = EmptySquare;
6615     cl.rfIn = -1;
6616     cl.ffIn = -1;
6617     cl.rtIn = *y;
6618     cl.ftIn = *x;
6619     cl.promoCharIn = NULLCHAR;
6620     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6621     if( cl.kind == NormalMove ||
6622         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6623         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6624         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6625       fromX = cl.ff;
6626       fromY = cl.rf;
6627       *x = cl.ft;
6628       *y = cl.rt;
6629       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6630       return TRUE;
6631     }
6632     return FALSE;
6633 }
6634
6635 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6636 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6637 int lastLoadGameUseList = FALSE;
6638 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6639 ChessMove lastLoadGameStart = EndOfFile;
6640 int doubleClick;
6641
6642 void
6643 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6644 {
6645     ChessMove moveType;
6646     ChessSquare pup;
6647     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6648
6649     /* Check if the user is playing in turn.  This is complicated because we
6650        let the user "pick up" a piece before it is his turn.  So the piece he
6651        tried to pick up may have been captured by the time he puts it down!
6652        Therefore we use the color the user is supposed to be playing in this
6653        test, not the color of the piece that is currently on the starting
6654        square---except in EditGame mode, where the user is playing both
6655        sides; fortunately there the capture race can't happen.  (It can
6656        now happen in IcsExamining mode, but that's just too bad.  The user
6657        will get a somewhat confusing message in that case.)
6658        */
6659
6660     switch (gameMode) {
6661       case AnalyzeFile:
6662       case TwoMachinesPlay:
6663       case EndOfGame:
6664       case IcsObserving:
6665       case IcsIdle:
6666         /* We switched into a game mode where moves are not accepted,
6667            perhaps while the mouse button was down. */
6668         return;
6669
6670       case MachinePlaysWhite:
6671         /* User is moving for Black */
6672         if (WhiteOnMove(currentMove)) {
6673             DisplayMoveError(_("It is White's turn"));
6674             return;
6675         }
6676         break;
6677
6678       case MachinePlaysBlack:
6679         /* User is moving for White */
6680         if (!WhiteOnMove(currentMove)) {
6681             DisplayMoveError(_("It is Black's turn"));
6682             return;
6683         }
6684         break;
6685
6686       case PlayFromGameFile:
6687             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6688       case EditGame:
6689       case IcsExamining:
6690       case BeginningOfGame:
6691       case AnalyzeMode:
6692       case Training:
6693         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6694         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6695             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6696             /* User is moving for Black */
6697             if (WhiteOnMove(currentMove)) {
6698                 DisplayMoveError(_("It is White's turn"));
6699                 return;
6700             }
6701         } else {
6702             /* User is moving for White */
6703             if (!WhiteOnMove(currentMove)) {
6704                 DisplayMoveError(_("It is Black's turn"));
6705                 return;
6706             }
6707         }
6708         break;
6709
6710       case IcsPlayingBlack:
6711         /* User is moving for Black */
6712         if (WhiteOnMove(currentMove)) {
6713             if (!appData.premove) {
6714                 DisplayMoveError(_("It is White's turn"));
6715             } else if (toX >= 0 && toY >= 0) {
6716                 premoveToX = toX;
6717                 premoveToY = toY;
6718                 premoveFromX = fromX;
6719                 premoveFromY = fromY;
6720                 premovePromoChar = promoChar;
6721                 gotPremove = 1;
6722                 if (appData.debugMode)
6723                     fprintf(debugFP, "Got premove: fromX %d,"
6724                             "fromY %d, toX %d, toY %d\n",
6725                             fromX, fromY, toX, toY);
6726             }
6727             return;
6728         }
6729         break;
6730
6731       case IcsPlayingWhite:
6732         /* User is moving for White */
6733         if (!WhiteOnMove(currentMove)) {
6734             if (!appData.premove) {
6735                 DisplayMoveError(_("It is Black's turn"));
6736             } else if (toX >= 0 && toY >= 0) {
6737                 premoveToX = toX;
6738                 premoveToY = toY;
6739                 premoveFromX = fromX;
6740                 premoveFromY = fromY;
6741                 premovePromoChar = promoChar;
6742                 gotPremove = 1;
6743                 if (appData.debugMode)
6744                     fprintf(debugFP, "Got premove: fromX %d,"
6745                             "fromY %d, toX %d, toY %d\n",
6746                             fromX, fromY, toX, toY);
6747             }
6748             return;
6749         }
6750         break;
6751
6752       default:
6753         break;
6754
6755       case EditPosition:
6756         /* EditPosition, empty square, or different color piece;
6757            click-click move is possible */
6758         if (toX == -2 || toY == -2) {
6759             boards[0][fromY][fromX] = EmptySquare;
6760             DrawPosition(FALSE, boards[currentMove]);
6761             return;
6762         } else if (toX >= 0 && toY >= 0) {
6763             boards[0][toY][toX] = boards[0][fromY][fromX];
6764             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6765                 if(boards[0][fromY][0] != EmptySquare) {
6766                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6767                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6768                 }
6769             } else
6770             if(fromX == BOARD_RGHT+1) {
6771                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6772                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6773                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6774                 }
6775             } else
6776             boards[0][fromY][fromX] = gatingPiece;
6777             DrawPosition(FALSE, boards[currentMove]);
6778             return;
6779         }
6780         return;
6781     }
6782
6783     if(toX < 0 || toY < 0) return;
6784     pup = boards[currentMove][toY][toX];
6785
6786     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6787     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6788          if( pup != EmptySquare ) return;
6789          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6790            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6791                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6792            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6793            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6794            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6795            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6796          fromY = DROP_RANK;
6797     }
6798
6799     /* [HGM] always test for legality, to get promotion info */
6800     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6801                                          fromY, fromX, toY, toX, promoChar);
6802
6803     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6804
6805     /* [HGM] but possibly ignore an IllegalMove result */
6806     if (appData.testLegality) {
6807         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6808             DisplayMoveError(_("Illegal move"));
6809             return;
6810         }
6811     }
6812
6813     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6814         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6815              ClearPremoveHighlights(); // was included
6816         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6817         return;
6818     }
6819
6820     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6821 }
6822
6823 /* Common tail of UserMoveEvent and DropMenuEvent */
6824 int
6825 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6826 {
6827     char *bookHit = 0;
6828
6829     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6830         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6831         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6832         if(WhiteOnMove(currentMove)) {
6833             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6834         } else {
6835             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6836         }
6837     }
6838
6839     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6840        move type in caller when we know the move is a legal promotion */
6841     if(moveType == NormalMove && promoChar)
6842         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6843
6844     /* [HGM] <popupFix> The following if has been moved here from
6845        UserMoveEvent(). Because it seemed to belong here (why not allow
6846        piece drops in training games?), and because it can only be
6847        performed after it is known to what we promote. */
6848     if (gameMode == Training) {
6849       /* compare the move played on the board to the next move in the
6850        * game. If they match, display the move and the opponent's response.
6851        * If they don't match, display an error message.
6852        */
6853       int saveAnimate;
6854       Board testBoard;
6855       CopyBoard(testBoard, boards[currentMove]);
6856       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6857
6858       if (CompareBoards(testBoard, boards[currentMove+1])) {
6859         ForwardInner(currentMove+1);
6860
6861         /* Autoplay the opponent's response.
6862          * if appData.animate was TRUE when Training mode was entered,
6863          * the response will be animated.
6864          */
6865         saveAnimate = appData.animate;
6866         appData.animate = animateTraining;
6867         ForwardInner(currentMove+1);
6868         appData.animate = saveAnimate;
6869
6870         /* check for the end of the game */
6871         if (currentMove >= forwardMostMove) {
6872           gameMode = PlayFromGameFile;
6873           ModeHighlight();
6874           SetTrainingModeOff();
6875           DisplayInformation(_("End of game"));
6876         }
6877       } else {
6878         DisplayError(_("Incorrect move"), 0);
6879       }
6880       return 1;
6881     }
6882
6883   /* Ok, now we know that the move is good, so we can kill
6884      the previous line in Analysis Mode */
6885   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6886                                 && currentMove < forwardMostMove) {
6887     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6888     else forwardMostMove = currentMove;
6889   }
6890
6891   ClearMap();
6892
6893   /* If we need the chess program but it's dead, restart it */
6894   ResurrectChessProgram();
6895
6896   /* A user move restarts a paused game*/
6897   if (pausing)
6898     PauseEvent();
6899
6900   thinkOutput[0] = NULLCHAR;
6901
6902   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6903
6904   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6905     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6906     return 1;
6907   }
6908
6909   if (gameMode == BeginningOfGame) {
6910     if (appData.noChessProgram) {
6911       gameMode = EditGame;
6912       SetGameInfo();
6913     } else {
6914       char buf[MSG_SIZ];
6915       gameMode = MachinePlaysBlack;
6916       StartClocks();
6917       SetGameInfo();
6918       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6919       DisplayTitle(buf);
6920       if (first.sendName) {
6921         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6922         SendToProgram(buf, &first);
6923       }
6924       StartClocks();
6925     }
6926     ModeHighlight();
6927   }
6928
6929   /* Relay move to ICS or chess engine */
6930   if (appData.icsActive) {
6931     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6932         gameMode == IcsExamining) {
6933       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6934         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6935         SendToICS("draw ");
6936         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6937       }
6938       // also send plain move, in case ICS does not understand atomic claims
6939       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6940       ics_user_moved = 1;
6941     }
6942   } else {
6943     if (first.sendTime && (gameMode == BeginningOfGame ||
6944                            gameMode == MachinePlaysWhite ||
6945                            gameMode == MachinePlaysBlack)) {
6946       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6947     }
6948     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6949          // [HGM] book: if program might be playing, let it use book
6950         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6951         first.maybeThinking = TRUE;
6952     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6953         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6954         SendBoard(&first, currentMove+1);
6955         if(second.analyzing) {
6956             if(!second.useSetboard) SendToProgram("undo\n", &second);
6957             SendBoard(&second, currentMove+1);
6958         }
6959     } else {
6960         SendMoveToProgram(forwardMostMove-1, &first);
6961         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6962     }
6963     if (currentMove == cmailOldMove + 1) {
6964       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6965     }
6966   }
6967
6968   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6969
6970   switch (gameMode) {
6971   case EditGame:
6972     if(appData.testLegality)
6973     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6974     case MT_NONE:
6975     case MT_CHECK:
6976       break;
6977     case MT_CHECKMATE:
6978     case MT_STAINMATE:
6979       if (WhiteOnMove(currentMove)) {
6980         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6981       } else {
6982         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6983       }
6984       break;
6985     case MT_STALEMATE:
6986       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6987       break;
6988     }
6989     break;
6990
6991   case MachinePlaysBlack:
6992   case MachinePlaysWhite:
6993     /* disable certain menu options while machine is thinking */
6994     SetMachineThinkingEnables();
6995     break;
6996
6997   default:
6998     break;
6999   }
7000
7001   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7002   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7003
7004   if(bookHit) { // [HGM] book: simulate book reply
7005         static char bookMove[MSG_SIZ]; // a bit generous?
7006
7007         programStats.nodes = programStats.depth = programStats.time =
7008         programStats.score = programStats.got_only_move = 0;
7009         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7010
7011         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7012         strcat(bookMove, bookHit);
7013         HandleMachineMove(bookMove, &first);
7014   }
7015   return 1;
7016 }
7017
7018 void
7019 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7020 {
7021     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7022     Markers *m = (Markers *) closure;
7023     if(rf == fromY && ff == fromX)
7024         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7025                          || kind == WhiteCapturesEnPassant
7026                          || kind == BlackCapturesEnPassant);
7027     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7028 }
7029
7030 void
7031 MarkTargetSquares (int clear)
7032 {
7033   int x, y;
7034   if(clear) // no reason to ever suppress clearing
7035     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
7036   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7037      !appData.testLegality || gameMode == EditPosition) return;
7038   if(!clear) {
7039     int capt = 0;
7040     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7041     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7042       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7043       if(capt)
7044       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7045     }
7046   }
7047   DrawPosition(FALSE, NULL);
7048 }
7049
7050 int
7051 Explode (Board board, int fromX, int fromY, int toX, int toY)
7052 {
7053     if(gameInfo.variant == VariantAtomic &&
7054        (board[toY][toX] != EmptySquare ||                     // capture?
7055         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7056                          board[fromY][fromX] == BlackPawn   )
7057       )) {
7058         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7059         return TRUE;
7060     }
7061     return FALSE;
7062 }
7063
7064 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7065
7066 int
7067 CanPromote (ChessSquare piece, int y)
7068 {
7069         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7070         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7071         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7072            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7073            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7074          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7075         return (piece == BlackPawn && y == 1 ||
7076                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7077                 piece == BlackLance && y == 1 ||
7078                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7079 }
7080
7081 void
7082 LeftClick (ClickType clickType, int xPix, int yPix)
7083 {
7084     int x, y;
7085     Boolean saveAnimate;
7086     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7087     char promoChoice = NULLCHAR;
7088     ChessSquare piece;
7089     static TimeMark lastClickTime, prevClickTime;
7090
7091     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7092
7093     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7094
7095     if (clickType == Press) ErrorPopDown();
7096
7097     x = EventToSquare(xPix, BOARD_WIDTH);
7098     y = EventToSquare(yPix, BOARD_HEIGHT);
7099     if (!flipView && y >= 0) {
7100         y = BOARD_HEIGHT - 1 - y;
7101     }
7102     if (flipView && x >= 0) {
7103         x = BOARD_WIDTH - 1 - x;
7104     }
7105
7106     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7107         defaultPromoChoice = promoSweep;
7108         promoSweep = EmptySquare;   // terminate sweep
7109         promoDefaultAltered = TRUE;
7110         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7111     }
7112
7113     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7114         if(clickType == Release) return; // ignore upclick of click-click destination
7115         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7116         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7117         if(gameInfo.holdingsWidth &&
7118                 (WhiteOnMove(currentMove)
7119                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7120                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7121             // click in right holdings, for determining promotion piece
7122             ChessSquare p = boards[currentMove][y][x];
7123             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7124             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7125             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7126                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7127                 fromX = fromY = -1;
7128                 return;
7129             }
7130         }
7131         DrawPosition(FALSE, boards[currentMove]);
7132         return;
7133     }
7134
7135     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7136     if(clickType == Press
7137             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7138               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7139               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7140         return;
7141
7142     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7143         // could be static click on premove from-square: abort premove
7144         gotPremove = 0;
7145         ClearPremoveHighlights();
7146     }
7147
7148     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7149         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7150
7151     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7152         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7153                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7154         defaultPromoChoice = DefaultPromoChoice(side);
7155     }
7156
7157     autoQueen = appData.alwaysPromoteToQueen;
7158
7159     if (fromX == -1) {
7160       int originalY = y;
7161       gatingPiece = EmptySquare;
7162       if (clickType != Press) {
7163         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7164             DragPieceEnd(xPix, yPix); dragging = 0;
7165             DrawPosition(FALSE, NULL);
7166         }
7167         return;
7168       }
7169       doubleClick = FALSE;
7170       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7171         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7172       }
7173       fromX = x; fromY = y; toX = toY = -1;
7174       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7175          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7176          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7177             /* First square */
7178             if (OKToStartUserMove(fromX, fromY)) {
7179                 second = 0;
7180                 MarkTargetSquares(0);
7181                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7182                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7183                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7184                     promoSweep = defaultPromoChoice;
7185                     selectFlag = 0; lastX = xPix; lastY = yPix;
7186                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7187                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7188                 }
7189                 if (appData.highlightDragging) {
7190                     SetHighlights(fromX, fromY, -1, -1);
7191                 } else {
7192                     ClearHighlights();
7193                 }
7194             } else fromX = fromY = -1;
7195             return;
7196         }
7197     }
7198
7199     /* fromX != -1 */
7200     if (clickType == Press && gameMode != EditPosition) {
7201         ChessSquare fromP;
7202         ChessSquare toP;
7203         int frc;
7204
7205         // ignore off-board to clicks
7206         if(y < 0 || x < 0) return;
7207
7208         /* Check if clicking again on the same color piece */
7209         fromP = boards[currentMove][fromY][fromX];
7210         toP = boards[currentMove][y][x];
7211         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7212         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7213              WhitePawn <= toP && toP <= WhiteKing &&
7214              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7215              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7216             (BlackPawn <= fromP && fromP <= BlackKing &&
7217              BlackPawn <= toP && toP <= BlackKing &&
7218              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7219              !(fromP == BlackKing && toP == BlackRook && frc))) {
7220             /* Clicked again on same color piece -- changed his mind */
7221             second = (x == fromX && y == fromY);
7222             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7223                 second = FALSE; // first double-click rather than scond click
7224                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7225             }
7226             promoDefaultAltered = FALSE;
7227             MarkTargetSquares(1);
7228            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7229             if (appData.highlightDragging) {
7230                 SetHighlights(x, y, -1, -1);
7231             } else {
7232                 ClearHighlights();
7233             }
7234             if (OKToStartUserMove(x, y)) {
7235                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7236                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7237                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7238                  gatingPiece = boards[currentMove][fromY][fromX];
7239                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7240                 fromX = x;
7241                 fromY = y; dragging = 1;
7242                 MarkTargetSquares(0);
7243                 DragPieceBegin(xPix, yPix, FALSE);
7244                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7245                     promoSweep = defaultPromoChoice;
7246                     selectFlag = 0; lastX = xPix; lastY = yPix;
7247                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7248                 }
7249             }
7250            }
7251            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7252            second = FALSE;
7253         }
7254         // ignore clicks on holdings
7255         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7256     }
7257
7258     if (clickType == Release && x == fromX && y == fromY) {
7259         DragPieceEnd(xPix, yPix); dragging = 0;
7260         if(clearFlag) {
7261             // a deferred attempt to click-click move an empty square on top of a piece
7262             boards[currentMove][y][x] = EmptySquare;
7263             ClearHighlights();
7264             DrawPosition(FALSE, boards[currentMove]);
7265             fromX = fromY = -1; clearFlag = 0;
7266             return;
7267         }
7268         if (appData.animateDragging) {
7269             /* Undo animation damage if any */
7270             DrawPosition(FALSE, NULL);
7271         }
7272         if (second || sweepSelecting) {
7273             /* Second up/down in same square; just abort move */
7274             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7275             second = sweepSelecting = 0;
7276             fromX = fromY = -1;
7277             gatingPiece = EmptySquare;
7278             ClearHighlights();
7279             gotPremove = 0;
7280             ClearPremoveHighlights();
7281         } else {
7282             /* First upclick in same square; start click-click mode */
7283             SetHighlights(x, y, -1, -1);
7284         }
7285         return;
7286     }
7287
7288     clearFlag = 0;
7289
7290     /* we now have a different from- and (possibly off-board) to-square */
7291     /* Completed move */
7292     if(!sweepSelecting) {
7293         toX = x;
7294         toY = y;
7295     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7296
7297     saveAnimate = appData.animate;
7298     if (clickType == Press) {
7299         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7300             // must be Edit Position mode with empty-square selected
7301             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7302             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7303             return;
7304         }
7305         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7306           if(appData.sweepSelect) {
7307             ChessSquare piece = boards[currentMove][fromY][fromX];
7308             promoSweep = defaultPromoChoice;
7309             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7310             selectFlag = 0; lastX = xPix; lastY = yPix;
7311             Sweep(0); // Pawn that is going to promote: preview promotion piece
7312             sweepSelecting = 1;
7313             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7314             MarkTargetSquares(1);
7315           }
7316           return; // promo popup appears on up-click
7317         }
7318         /* Finish clickclick move */
7319         if (appData.animate || appData.highlightLastMove) {
7320             SetHighlights(fromX, fromY, toX, toY);
7321         } else {
7322             ClearHighlights();
7323         }
7324     } else {
7325 #if 0
7326 // [HGM] this must be done after the move is made, as with arrow it could lead to a board redraw with piece still on from square
7327         /* Finish drag move */
7328         if (appData.highlightLastMove) {
7329             SetHighlights(fromX, fromY, toX, toY);
7330         } else {
7331             ClearHighlights();
7332         }
7333 #endif
7334         DragPieceEnd(xPix, yPix); dragging = 0;
7335         /* Don't animate move and drag both */
7336         appData.animate = FALSE;
7337     }
7338
7339     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7340     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7341         ChessSquare piece = boards[currentMove][fromY][fromX];
7342         if(gameMode == EditPosition && piece != EmptySquare &&
7343            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7344             int n;
7345
7346             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7347                 n = PieceToNumber(piece - (int)BlackPawn);
7348                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7349                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7350                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7351             } else
7352             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7353                 n = PieceToNumber(piece);
7354                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7355                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7356                 boards[currentMove][n][BOARD_WIDTH-2]++;
7357             }
7358             boards[currentMove][fromY][fromX] = EmptySquare;
7359         }
7360         ClearHighlights();
7361         fromX = fromY = -1;
7362         MarkTargetSquares(1);
7363         DrawPosition(TRUE, boards[currentMove]);
7364         return;
7365     }
7366
7367     // off-board moves should not be highlighted
7368     if(x < 0 || y < 0) ClearHighlights();
7369
7370     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7371
7372     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7373         SetHighlights(fromX, fromY, toX, toY);
7374         MarkTargetSquares(1);
7375         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7376             // [HGM] super: promotion to captured piece selected from holdings
7377             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7378             promotionChoice = TRUE;
7379             // kludge follows to temporarily execute move on display, without promoting yet
7380             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7381             boards[currentMove][toY][toX] = p;
7382             DrawPosition(FALSE, boards[currentMove]);
7383             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7384             boards[currentMove][toY][toX] = q;
7385             DisplayMessage("Click in holdings to choose piece", "");
7386             return;
7387         }
7388         PromotionPopUp();
7389     } else {
7390         int oldMove = currentMove;
7391         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7392         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7393         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7394         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7395            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7396             DrawPosition(TRUE, boards[currentMove]);
7397         MarkTargetSquares(1);
7398         fromX = fromY = -1;
7399     }
7400     appData.animate = saveAnimate;
7401     if (appData.animate || appData.animateDragging) {
7402         /* Undo animation damage if needed */
7403         DrawPosition(FALSE, NULL);
7404     }
7405 }
7406
7407 int
7408 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7409 {   // front-end-free part taken out of PieceMenuPopup
7410     int whichMenu; int xSqr, ySqr;
7411
7412     if(seekGraphUp) { // [HGM] seekgraph
7413         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7414         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7415         return -2;
7416     }
7417
7418     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7419          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7420         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7421         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7422         if(action == Press)   {
7423             originalFlip = flipView;
7424             flipView = !flipView; // temporarily flip board to see game from partners perspective
7425             DrawPosition(TRUE, partnerBoard);
7426             DisplayMessage(partnerStatus, "");
7427             partnerUp = TRUE;
7428         } else if(action == Release) {
7429             flipView = originalFlip;
7430             DrawPosition(TRUE, boards[currentMove]);
7431             partnerUp = FALSE;
7432         }
7433         return -2;
7434     }
7435
7436     xSqr = EventToSquare(x, BOARD_WIDTH);
7437     ySqr = EventToSquare(y, BOARD_HEIGHT);
7438     if (action == Release) {
7439         if(pieceSweep != EmptySquare) {
7440             EditPositionMenuEvent(pieceSweep, toX, toY);
7441             pieceSweep = EmptySquare;
7442         } else UnLoadPV(); // [HGM] pv
7443     }
7444     if (action != Press) return -2; // return code to be ignored
7445     switch (gameMode) {
7446       case IcsExamining:
7447         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7448       case EditPosition:
7449         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7450         if (xSqr < 0 || ySqr < 0) return -1;
7451         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7452         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7453         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7454         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7455         NextPiece(0);
7456         return 2; // grab
7457       case IcsObserving:
7458         if(!appData.icsEngineAnalyze) return -1;
7459       case IcsPlayingWhite:
7460       case IcsPlayingBlack:
7461         if(!appData.zippyPlay) goto noZip;
7462       case AnalyzeMode:
7463       case AnalyzeFile:
7464       case MachinePlaysWhite:
7465       case MachinePlaysBlack:
7466       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7467         if (!appData.dropMenu) {
7468           LoadPV(x, y);
7469           return 2; // flag front-end to grab mouse events
7470         }
7471         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7472            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7473       case EditGame:
7474       noZip:
7475         if (xSqr < 0 || ySqr < 0) return -1;
7476         if (!appData.dropMenu || appData.testLegality &&
7477             gameInfo.variant != VariantBughouse &&
7478             gameInfo.variant != VariantCrazyhouse) return -1;
7479         whichMenu = 1; // drop menu
7480         break;
7481       default:
7482         return -1;
7483     }
7484
7485     if (((*fromX = xSqr) < 0) ||
7486         ((*fromY = ySqr) < 0)) {
7487         *fromX = *fromY = -1;
7488         return -1;
7489     }
7490     if (flipView)
7491       *fromX = BOARD_WIDTH - 1 - *fromX;
7492     else
7493       *fromY = BOARD_HEIGHT - 1 - *fromY;
7494
7495     return whichMenu;
7496 }
7497
7498 void
7499 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7500 {
7501 //    char * hint = lastHint;
7502     FrontEndProgramStats stats;
7503
7504     stats.which = cps == &first ? 0 : 1;
7505     stats.depth = cpstats->depth;
7506     stats.nodes = cpstats->nodes;
7507     stats.score = cpstats->score;
7508     stats.time = cpstats->time;
7509     stats.pv = cpstats->movelist;
7510     stats.hint = lastHint;
7511     stats.an_move_index = 0;
7512     stats.an_move_count = 0;
7513
7514     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7515         stats.hint = cpstats->move_name;
7516         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7517         stats.an_move_count = cpstats->nr_moves;
7518     }
7519
7520     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
7521
7522     SetProgramStats( &stats );
7523 }
7524
7525 void
7526 ClearEngineOutputPane (int which)
7527 {
7528     static FrontEndProgramStats dummyStats;
7529     dummyStats.which = which;
7530     dummyStats.pv = "#";
7531     SetProgramStats( &dummyStats );
7532 }
7533
7534 #define MAXPLAYERS 500
7535
7536 char *
7537 TourneyStandings (int display)
7538 {
7539     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7540     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7541     char result, *p, *names[MAXPLAYERS];
7542
7543     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7544         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7545     names[0] = p = strdup(appData.participants);
7546     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7547
7548     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7549
7550     while(result = appData.results[nr]) {
7551         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7552         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7553         wScore = bScore = 0;
7554         switch(result) {
7555           case '+': wScore = 2; break;
7556           case '-': bScore = 2; break;
7557           case '=': wScore = bScore = 1; break;
7558           case ' ':
7559           case '*': return strdup("busy"); // tourney not finished
7560         }
7561         score[w] += wScore;
7562         score[b] += bScore;
7563         games[w]++;
7564         games[b]++;
7565         nr++;
7566     }
7567     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7568     for(w=0; w<nPlayers; w++) {
7569         bScore = -1;
7570         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7571         ranking[w] = b; points[w] = bScore; score[b] = -2;
7572     }
7573     p = malloc(nPlayers*34+1);
7574     for(w=0; w<nPlayers && w<display; w++)
7575         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7576     free(names[0]);
7577     return p;
7578 }
7579
7580 void
7581 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7582 {       // count all piece types
7583         int p, f, r;
7584         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7585         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7586         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7587                 p = board[r][f];
7588                 pCnt[p]++;
7589                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7590                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7591                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7592                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7593                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7594                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7595         }
7596 }
7597
7598 int
7599 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7600 {
7601         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7602         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7603
7604         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7605         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7606         if(myPawns == 2 && nMine == 3) // KPP
7607             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7608         if(myPawns == 1 && nMine == 2) // KP
7609             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7610         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7611             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7612         if(myPawns) return FALSE;
7613         if(pCnt[WhiteRook+side])
7614             return pCnt[BlackRook-side] ||
7615                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7616                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7617                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7618         if(pCnt[WhiteCannon+side]) {
7619             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7620             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7621         }
7622         if(pCnt[WhiteKnight+side])
7623             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7624         return FALSE;
7625 }
7626
7627 int
7628 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7629 {
7630         VariantClass v = gameInfo.variant;
7631
7632         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7633         if(v == VariantShatranj) return TRUE; // always winnable through baring
7634         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7635         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7636
7637         if(v == VariantXiangqi) {
7638                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7639
7640                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7641                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7642                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7643                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7644                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7645                 if(stale) // we have at least one last-rank P plus perhaps C
7646                     return majors // KPKX
7647                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7648                 else // KCA*E*
7649                     return pCnt[WhiteFerz+side] // KCAK
7650                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7651                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7652                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7653
7654         } else if(v == VariantKnightmate) {
7655                 if(nMine == 1) return FALSE;
7656                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7657         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7658                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7659
7660                 if(nMine == 1) return FALSE; // bare King
7661                 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
7662                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7663                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7664                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7665                 if(pCnt[WhiteKnight+side])
7666                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7667                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7668                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7669                 if(nBishops)
7670                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7671                 if(pCnt[WhiteAlfil+side])
7672                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7673                 if(pCnt[WhiteWazir+side])
7674                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7675         }
7676
7677         return TRUE;
7678 }
7679
7680 int
7681 CompareWithRights (Board b1, Board b2)
7682 {
7683     int rights = 0;
7684     if(!CompareBoards(b1, b2)) return FALSE;
7685     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7686     /* compare castling rights */
7687     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7688            rights++; /* King lost rights, while rook still had them */
7689     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7690         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7691            rights++; /* but at least one rook lost them */
7692     }
7693     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7694            rights++;
7695     if( b1[CASTLING][5] != NoRights ) {
7696         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7697            rights++;
7698     }
7699     return rights == 0;
7700 }
7701
7702 int
7703 Adjudicate (ChessProgramState *cps)
7704 {       // [HGM] some adjudications useful with buggy engines
7705         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7706         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7707         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7708         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7709         int k, drop, count = 0; static int bare = 1;
7710         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7711         Boolean canAdjudicate = !appData.icsActive;
7712
7713         // most tests only when we understand the game, i.e. legality-checking on
7714             if( appData.testLegality )
7715             {   /* [HGM] Some more adjudications for obstinate engines */
7716                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7717                 static int moveCount = 6;
7718                 ChessMove result;
7719                 char *reason = NULL;
7720
7721                 /* Count what is on board. */
7722                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7723
7724                 /* Some material-based adjudications that have to be made before stalemate test */
7725                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7726                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7727                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7728                      if(canAdjudicate && appData.checkMates) {
7729                          if(engineOpponent)
7730                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7731                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7732                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7733                          return 1;
7734                      }
7735                 }
7736
7737                 /* Bare King in Shatranj (loses) or Losers (wins) */
7738                 if( nrW == 1 || nrB == 1) {
7739                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7740                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7741                      if(canAdjudicate && appData.checkMates) {
7742                          if(engineOpponent)
7743                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7744                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7745                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7746                          return 1;
7747                      }
7748                   } else
7749                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7750                   {    /* bare King */
7751                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7752                         if(canAdjudicate && appData.checkMates) {
7753                             /* but only adjudicate if adjudication enabled */
7754                             if(engineOpponent)
7755                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7756                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7757                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7758                             return 1;
7759                         }
7760                   }
7761                 } else bare = 1;
7762
7763
7764             // don't wait for engine to announce game end if we can judge ourselves
7765             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7766               case MT_CHECK:
7767                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7768                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7769                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7770                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7771                             checkCnt++;
7772                         if(checkCnt >= 2) {
7773                             reason = "Xboard adjudication: 3rd check";
7774                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7775                             break;
7776                         }
7777                     }
7778                 }
7779               case MT_NONE:
7780               default:
7781                 break;
7782               case MT_STALEMATE:
7783               case MT_STAINMATE:
7784                 reason = "Xboard adjudication: Stalemate";
7785                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7786                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7787                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7788                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7789                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7790                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7791                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7792                                                                         EP_CHECKMATE : EP_WINS);
7793                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7794                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7795                 }
7796                 break;
7797               case MT_CHECKMATE:
7798                 reason = "Xboard adjudication: Checkmate";
7799                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7800                 if(gameInfo.variant == VariantShogi) {
7801                     if(forwardMostMove > backwardMostMove
7802                        && moveList[forwardMostMove-1][1] == '@'
7803                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7804                         reason = "XBoard adjudication: pawn-drop mate";
7805                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7806                     }
7807                 }
7808                 break;
7809             }
7810
7811                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7812                     case EP_STALEMATE:
7813                         result = GameIsDrawn; break;
7814                     case EP_CHECKMATE:
7815                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7816                     case EP_WINS:
7817                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7818                     default:
7819                         result = EndOfFile;
7820                 }
7821                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7822                     if(engineOpponent)
7823                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7824                     GameEnds( result, reason, GE_XBOARD );
7825                     return 1;
7826                 }
7827
7828                 /* Next absolutely insufficient mating material. */
7829                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7830                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7831                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7832
7833                      /* always flag draws, for judging claims */
7834                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7835
7836                      if(canAdjudicate && appData.materialDraws) {
7837                          /* but only adjudicate them if adjudication enabled */
7838                          if(engineOpponent) {
7839                            SendToProgram("force\n", engineOpponent); // suppress reply
7840                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7841                          }
7842                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7843                          return 1;
7844                      }
7845                 }
7846
7847                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7848                 if(gameInfo.variant == VariantXiangqi ?
7849                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7850                  : nrW + nrB == 4 &&
7851                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7852                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7853                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7854                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7855                    ) ) {
7856                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7857                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7858                           if(engineOpponent) {
7859                             SendToProgram("force\n", engineOpponent); // suppress reply
7860                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7861                           }
7862                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7863                           return 1;
7864                      }
7865                 } else moveCount = 6;
7866             }
7867
7868         // Repetition draws and 50-move rule can be applied independently of legality testing
7869
7870                 /* Check for rep-draws */
7871                 count = 0;
7872                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7873                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7874                 for(k = forwardMostMove-2;
7875                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7876                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7877                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7878                     k-=2)
7879                 {   int rights=0;
7880                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7881                         /* compare castling rights */
7882                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7883                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7884                                 rights++; /* King lost rights, while rook still had them */
7885                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7886                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7887                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7888                                    rights++; /* but at least one rook lost them */
7889                         }
7890                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7891                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7892                                 rights++;
7893                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7894                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7895                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7896                                    rights++;
7897                         }
7898                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7899                             && appData.drawRepeats > 1) {
7900                              /* adjudicate after user-specified nr of repeats */
7901                              int result = GameIsDrawn;
7902                              char *details = "XBoard adjudication: repetition draw";
7903                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7904                                 // [HGM] xiangqi: check for forbidden perpetuals
7905                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7906                                 for(m=forwardMostMove; m>k; m-=2) {
7907                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7908                                         ourPerpetual = 0; // the current mover did not always check
7909                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7910                                         hisPerpetual = 0; // the opponent did not always check
7911                                 }
7912                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7913                                                                         ourPerpetual, hisPerpetual);
7914                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7915                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7916                                     details = "Xboard adjudication: perpetual checking";
7917                                 } else
7918                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7919                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7920                                 } else
7921                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
7922                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
7923                                         result = BlackWins;
7924                                         details = "Xboard adjudication: repetition";
7925                                     }
7926                                 } else // it must be XQ
7927                                 // Now check for perpetual chases
7928                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7929                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7930                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7931                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7932                                         static char resdet[MSG_SIZ];
7933                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7934                                         details = resdet;
7935                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7936                                     } else
7937                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7938                                         break; // Abort repetition-checking loop.
7939                                 }
7940                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7941                              }
7942                              if(engineOpponent) {
7943                                SendToProgram("force\n", engineOpponent); // suppress reply
7944                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7945                              }
7946                              GameEnds( result, details, GE_XBOARD );
7947                              return 1;
7948                         }
7949                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7950                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7951                     }
7952                 }
7953
7954                 /* Now we test for 50-move draws. Determine ply count */
7955                 count = forwardMostMove;
7956                 /* look for last irreversble move */
7957                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7958                     count--;
7959                 /* if we hit starting position, add initial plies */
7960                 if( count == backwardMostMove )
7961                     count -= initialRulePlies;
7962                 count = forwardMostMove - count;
7963                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7964                         // adjust reversible move counter for checks in Xiangqi
7965                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7966                         if(i < backwardMostMove) i = backwardMostMove;
7967                         while(i <= forwardMostMove) {
7968                                 lastCheck = inCheck; // check evasion does not count
7969                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7970                                 if(inCheck || lastCheck) count--; // check does not count
7971                                 i++;
7972                         }
7973                 }
7974                 if( count >= 100)
7975                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7976                          /* this is used to judge if draw claims are legal */
7977                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7978                          if(engineOpponent) {
7979                            SendToProgram("force\n", engineOpponent); // suppress reply
7980                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7981                          }
7982                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7983                          return 1;
7984                 }
7985
7986                 /* if draw offer is pending, treat it as a draw claim
7987                  * when draw condition present, to allow engines a way to
7988                  * claim draws before making their move to avoid a race
7989                  * condition occurring after their move
7990                  */
7991                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7992                          char *p = NULL;
7993                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7994                              p = "Draw claim: 50-move rule";
7995                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7996                              p = "Draw claim: 3-fold repetition";
7997                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7998                              p = "Draw claim: insufficient mating material";
7999                          if( p != NULL && canAdjudicate) {
8000                              if(engineOpponent) {
8001                                SendToProgram("force\n", engineOpponent); // suppress reply
8002                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8003                              }
8004                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8005                              return 1;
8006                          }
8007                 }
8008
8009                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8010                     if(engineOpponent) {
8011                       SendToProgram("force\n", engineOpponent); // suppress reply
8012                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8013                     }
8014                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8015                     return 1;
8016                 }
8017         return 0;
8018 }
8019
8020 char *
8021 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8022 {   // [HGM] book: this routine intercepts moves to simulate book replies
8023     char *bookHit = NULL;
8024
8025     //first determine if the incoming move brings opponent into his book
8026     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8027         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8028     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8029     if(bookHit != NULL && !cps->bookSuspend) {
8030         // make sure opponent is not going to reply after receiving move to book position
8031         SendToProgram("force\n", cps);
8032         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8033     }
8034     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8035     // now arrange restart after book miss
8036     if(bookHit) {
8037         // after a book hit we never send 'go', and the code after the call to this routine
8038         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8039         char buf[MSG_SIZ], *move = bookHit;
8040         if(cps->useSAN) {
8041             int fromX, fromY, toX, toY;
8042             char promoChar;
8043             ChessMove moveType;
8044             move = buf + 30;
8045             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8046                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8047                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8048                                     PosFlags(forwardMostMove),
8049                                     fromY, fromX, toY, toX, promoChar, move);
8050             } else {
8051                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8052                 bookHit = NULL;
8053             }
8054         }
8055         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8056         SendToProgram(buf, cps);
8057         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8058     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8059         SendToProgram("go\n", cps);
8060         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8061     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8062         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8063             SendToProgram("go\n", cps);
8064         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8065     }
8066     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8067 }
8068
8069 int
8070 LoadError (char *errmess, ChessProgramState *cps)
8071 {   // unloads engine and switches back to -ncp mode if it was first
8072     if(cps->initDone) return FALSE;
8073     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8074     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8075     cps->pr = NoProc;
8076     if(cps == &first) {
8077         appData.noChessProgram = TRUE;
8078         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8079         gameMode = BeginningOfGame; ModeHighlight();
8080         SetNCPMode();
8081     }
8082     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8083     DisplayMessage("", ""); // erase waiting message
8084     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8085     return TRUE;
8086 }
8087
8088 char *savedMessage;
8089 ChessProgramState *savedState;
8090 void
8091 DeferredBookMove (void)
8092 {
8093         if(savedState->lastPing != savedState->lastPong)
8094                     ScheduleDelayedEvent(DeferredBookMove, 10);
8095         else
8096         HandleMachineMove(savedMessage, savedState);
8097 }
8098
8099 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8100 static ChessProgramState *stalledEngine;
8101 static char stashedInputMove[MSG_SIZ];
8102
8103 void
8104 HandleMachineMove (char *message, ChessProgramState *cps)
8105 {
8106     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8107     char realname[MSG_SIZ];
8108     int fromX, fromY, toX, toY;
8109     ChessMove moveType;
8110     char promoChar;
8111     char *p, *pv=buf1;
8112     int machineWhite, oldError;
8113     char *bookHit;
8114
8115     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8116         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8117         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8118             DisplayError(_("Invalid pairing from pairing engine"), 0);
8119             return;
8120         }
8121         pairingReceived = 1;
8122         NextMatchGame();
8123         return; // Skim the pairing messages here.
8124     }
8125
8126     oldError = cps->userError; cps->userError = 0;
8127
8128 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8129     /*
8130      * Kludge to ignore BEL characters
8131      */
8132     while (*message == '\007') message++;
8133
8134     /*
8135      * [HGM] engine debug message: ignore lines starting with '#' character
8136      */
8137     if(cps->debug && *message == '#') return;
8138
8139     /*
8140      * Look for book output
8141      */
8142     if (cps == &first && bookRequested) {
8143         if (message[0] == '\t' || message[0] == ' ') {
8144             /* Part of the book output is here; append it */
8145             strcat(bookOutput, message);
8146             strcat(bookOutput, "  \n");
8147             return;
8148         } else if (bookOutput[0] != NULLCHAR) {
8149             /* All of book output has arrived; display it */
8150             char *p = bookOutput;
8151             while (*p != NULLCHAR) {
8152                 if (*p == '\t') *p = ' ';
8153                 p++;
8154             }
8155             DisplayInformation(bookOutput);
8156             bookRequested = FALSE;
8157             /* Fall through to parse the current output */
8158         }
8159     }
8160
8161     /*
8162      * Look for machine move.
8163      */
8164     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8165         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8166     {
8167         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8168             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8169             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8170             stalledEngine = cps;
8171             if(appData.ponderNextMove) { // bring opponent out of ponder
8172                 if(gameMode == TwoMachinesPlay) {
8173                     if(cps->other->pause)
8174                         PauseEngine(cps->other);
8175                     else
8176                         SendToProgram("easy\n", cps->other);
8177                 }
8178             }
8179             StopClocks();
8180             return;
8181         }
8182
8183         /* This method is only useful on engines that support ping */
8184         if (cps->lastPing != cps->lastPong) {
8185           if (gameMode == BeginningOfGame) {
8186             /* Extra move from before last new; ignore */
8187             if (appData.debugMode) {
8188                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8189             }
8190           } else {
8191             if (appData.debugMode) {
8192                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8193                         cps->which, gameMode);
8194             }
8195
8196             SendToProgram("undo\n", cps);
8197           }
8198           return;
8199         }
8200
8201         switch (gameMode) {
8202           case BeginningOfGame:
8203             /* Extra move from before last reset; ignore */
8204             if (appData.debugMode) {
8205                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8206             }
8207             return;
8208
8209           case EndOfGame:
8210           case IcsIdle:
8211           default:
8212             /* Extra move after we tried to stop.  The mode test is
8213                not a reliable way of detecting this problem, but it's
8214                the best we can do on engines that don't support ping.
8215             */
8216             if (appData.debugMode) {
8217                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8218                         cps->which, gameMode);
8219             }
8220             SendToProgram("undo\n", cps);
8221             return;
8222
8223           case MachinePlaysWhite:
8224           case IcsPlayingWhite:
8225             machineWhite = TRUE;
8226             break;
8227
8228           case MachinePlaysBlack:
8229           case IcsPlayingBlack:
8230             machineWhite = FALSE;
8231             break;
8232
8233           case TwoMachinesPlay:
8234             machineWhite = (cps->twoMachinesColor[0] == 'w');
8235             break;
8236         }
8237         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8238             if (appData.debugMode) {
8239                 fprintf(debugFP,
8240                         "Ignoring move out of turn by %s, gameMode %d"
8241                         ", forwardMost %d\n",
8242                         cps->which, gameMode, forwardMostMove);
8243             }
8244             return;
8245         }
8246
8247         if(cps->alphaRank) AlphaRank(machineMove, 4);
8248         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8249                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8250             /* Machine move could not be parsed; ignore it. */
8251           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8252                     machineMove, _(cps->which));
8253             DisplayMoveError(buf1);
8254             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8255                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8256             if (gameMode == TwoMachinesPlay) {
8257               GameEnds(machineWhite ? BlackWins : WhiteWins,
8258                        buf1, GE_XBOARD);
8259             }
8260             return;
8261         }
8262
8263         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8264         /* So we have to redo legality test with true e.p. status here,  */
8265         /* to make sure an illegal e.p. capture does not slip through,   */
8266         /* to cause a forfeit on a justified illegal-move complaint      */
8267         /* of the opponent.                                              */
8268         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8269            ChessMove moveType;
8270            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8271                              fromY, fromX, toY, toX, promoChar);
8272             if(moveType == IllegalMove) {
8273               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8274                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8275                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8276                            buf1, GE_XBOARD);
8277                 return;
8278            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8279            /* [HGM] Kludge to handle engines that send FRC-style castling
8280               when they shouldn't (like TSCP-Gothic) */
8281            switch(moveType) {
8282              case WhiteASideCastleFR:
8283              case BlackASideCastleFR:
8284                toX+=2;
8285                currentMoveString[2]++;
8286                break;
8287              case WhiteHSideCastleFR:
8288              case BlackHSideCastleFR:
8289                toX--;
8290                currentMoveString[2]--;
8291                break;
8292              default: ; // nothing to do, but suppresses warning of pedantic compilers
8293            }
8294         }
8295         hintRequested = FALSE;
8296         lastHint[0] = NULLCHAR;
8297         bookRequested = FALSE;
8298         /* Program may be pondering now */
8299         cps->maybeThinking = TRUE;
8300         if (cps->sendTime == 2) cps->sendTime = 1;
8301         if (cps->offeredDraw) cps->offeredDraw--;
8302
8303         /* [AS] Save move info*/
8304         pvInfoList[ forwardMostMove ].score = programStats.score;
8305         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8306         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8307
8308         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8309
8310         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8311         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8312             int count = 0;
8313
8314             while( count < adjudicateLossPlies ) {
8315                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8316
8317                 if( count & 1 ) {
8318                     score = -score; /* Flip score for winning side */
8319                 }
8320
8321                 if( score > adjudicateLossThreshold ) {
8322                     break;
8323                 }
8324
8325                 count++;
8326             }
8327
8328             if( count >= adjudicateLossPlies ) {
8329                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8330
8331                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8332                     "Xboard adjudication",
8333                     GE_XBOARD );
8334
8335                 return;
8336             }
8337         }
8338
8339         if(Adjudicate(cps)) {
8340             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8341             return; // [HGM] adjudicate: for all automatic game ends
8342         }
8343
8344 #if ZIPPY
8345         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8346             first.initDone) {
8347           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8348                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8349                 SendToICS("draw ");
8350                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8351           }
8352           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8353           ics_user_moved = 1;
8354           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8355                 char buf[3*MSG_SIZ];
8356
8357                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8358                         programStats.score / 100.,
8359                         programStats.depth,
8360                         programStats.time / 100.,
8361                         (unsigned int)programStats.nodes,
8362                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8363                         programStats.movelist);
8364                 SendToICS(buf);
8365 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8366           }
8367         }
8368 #endif
8369
8370         /* [AS] Clear stats for next move */
8371         ClearProgramStats();
8372         thinkOutput[0] = NULLCHAR;
8373         hiddenThinkOutputState = 0;
8374
8375         bookHit = NULL;
8376         if (gameMode == TwoMachinesPlay) {
8377             /* [HGM] relaying draw offers moved to after reception of move */
8378             /* and interpreting offer as claim if it brings draw condition */
8379             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8380                 SendToProgram("draw\n", cps->other);
8381             }
8382             if (cps->other->sendTime) {
8383                 SendTimeRemaining(cps->other,
8384                                   cps->other->twoMachinesColor[0] == 'w');
8385             }
8386             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8387             if (firstMove && !bookHit) {
8388                 firstMove = FALSE;
8389                 if (cps->other->useColors) {
8390                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8391                 }
8392                 SendToProgram("go\n", cps->other);
8393             }
8394             cps->other->maybeThinking = TRUE;
8395         }
8396
8397         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8398
8399         if (!pausing && appData.ringBellAfterMoves) {
8400             RingBell();
8401         }
8402
8403         /*
8404          * Reenable menu items that were disabled while
8405          * machine was thinking
8406          */
8407         if (gameMode != TwoMachinesPlay)
8408             SetUserThinkingEnables();
8409
8410         // [HGM] book: after book hit opponent has received move and is now in force mode
8411         // force the book reply into it, and then fake that it outputted this move by jumping
8412         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8413         if(bookHit) {
8414                 static char bookMove[MSG_SIZ]; // a bit generous?
8415
8416                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8417                 strcat(bookMove, bookHit);
8418                 message = bookMove;
8419                 cps = cps->other;
8420                 programStats.nodes = programStats.depth = programStats.time =
8421                 programStats.score = programStats.got_only_move = 0;
8422                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8423
8424                 if(cps->lastPing != cps->lastPong) {
8425                     savedMessage = message; // args for deferred call
8426                     savedState = cps;
8427                     ScheduleDelayedEvent(DeferredBookMove, 10);
8428                     return;
8429                 }
8430                 goto FakeBookMove;
8431         }
8432
8433         return;
8434     }
8435
8436     /* Set special modes for chess engines.  Later something general
8437      *  could be added here; for now there is just one kludge feature,
8438      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8439      *  when "xboard" is given as an interactive command.
8440      */
8441     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8442         cps->useSigint = FALSE;
8443         cps->useSigterm = FALSE;
8444     }
8445     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8446       ParseFeatures(message+8, cps);
8447       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8448     }
8449
8450     if (!strncmp(message, "setup ", 6) && 
8451         (!appData.testLegality || gameInfo.variant == VariantFairy || NonStandardBoardSize())
8452                                         ) { // [HGM] allow first engine to define opening position
8453       int dummy, s=6; char buf[MSG_SIZ];
8454       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8455       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8456       if(startedFromSetupPosition) return;
8457       if(sscanf(message+s, "%dx%d+%d", &dummy, &dummy, &dummy) == 3) while(message[s] && message[s++] != ' '); // for compatibility with Alien Edition
8458       ParseFEN(boards[0], &dummy, message+s);
8459       DrawPosition(TRUE, boards[0]);
8460       startedFromSetupPosition = TRUE;
8461       return;
8462     }
8463     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8464      * want this, I was asked to put it in, and obliged.
8465      */
8466     if (!strncmp(message, "setboard ", 9)) {
8467         Board initial_position;
8468
8469         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8470
8471         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8472             DisplayError(_("Bad FEN received from engine"), 0);
8473             return ;
8474         } else {
8475            Reset(TRUE, FALSE);
8476            CopyBoard(boards[0], initial_position);
8477            initialRulePlies = FENrulePlies;
8478            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8479            else gameMode = MachinePlaysBlack;
8480            DrawPosition(FALSE, boards[currentMove]);
8481         }
8482         return;
8483     }
8484
8485     /*
8486      * Look for communication commands
8487      */
8488     if (!strncmp(message, "telluser ", 9)) {
8489         if(message[9] == '\\' && message[10] == '\\')
8490             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8491         PlayTellSound();
8492         DisplayNote(message + 9);
8493         return;
8494     }
8495     if (!strncmp(message, "tellusererror ", 14)) {
8496         cps->userError = 1;
8497         if(message[14] == '\\' && message[15] == '\\')
8498             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8499         PlayTellSound();
8500         DisplayError(message + 14, 0);
8501         return;
8502     }
8503     if (!strncmp(message, "tellopponent ", 13)) {
8504       if (appData.icsActive) {
8505         if (loggedOn) {
8506           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8507           SendToICS(buf1);
8508         }
8509       } else {
8510         DisplayNote(message + 13);
8511       }
8512       return;
8513     }
8514     if (!strncmp(message, "tellothers ", 11)) {
8515       if (appData.icsActive) {
8516         if (loggedOn) {
8517           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8518           SendToICS(buf1);
8519         }
8520       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8521       return;
8522     }
8523     if (!strncmp(message, "tellall ", 8)) {
8524       if (appData.icsActive) {
8525         if (loggedOn) {
8526           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8527           SendToICS(buf1);
8528         }
8529       } else {
8530         DisplayNote(message + 8);
8531       }
8532       return;
8533     }
8534     if (strncmp(message, "warning", 7) == 0) {
8535         /* Undocumented feature, use tellusererror in new code */
8536         DisplayError(message, 0);
8537         return;
8538     }
8539     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8540         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8541         strcat(realname, " query");
8542         AskQuestion(realname, buf2, buf1, cps->pr);
8543         return;
8544     }
8545     /* Commands from the engine directly to ICS.  We don't allow these to be
8546      *  sent until we are logged on. Crafty kibitzes have been known to
8547      *  interfere with the login process.
8548      */
8549     if (loggedOn) {
8550         if (!strncmp(message, "tellics ", 8)) {
8551             SendToICS(message + 8);
8552             SendToICS("\n");
8553             return;
8554         }
8555         if (!strncmp(message, "tellicsnoalias ", 15)) {
8556             SendToICS(ics_prefix);
8557             SendToICS(message + 15);
8558             SendToICS("\n");
8559             return;
8560         }
8561         /* The following are for backward compatibility only */
8562         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8563             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8564             SendToICS(ics_prefix);
8565             SendToICS(message);
8566             SendToICS("\n");
8567             return;
8568         }
8569     }
8570     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8571         return;
8572     }
8573     /*
8574      * If the move is illegal, cancel it and redraw the board.
8575      * Also deal with other error cases.  Matching is rather loose
8576      * here to accommodate engines written before the spec.
8577      */
8578     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8579         strncmp(message, "Error", 5) == 0) {
8580         if (StrStr(message, "name") ||
8581             StrStr(message, "rating") || StrStr(message, "?") ||
8582             StrStr(message, "result") || StrStr(message, "board") ||
8583             StrStr(message, "bk") || StrStr(message, "computer") ||
8584             StrStr(message, "variant") || StrStr(message, "hint") ||
8585             StrStr(message, "random") || StrStr(message, "depth") ||
8586             StrStr(message, "accepted")) {
8587             return;
8588         }
8589         if (StrStr(message, "protover")) {
8590           /* Program is responding to input, so it's apparently done
8591              initializing, and this error message indicates it is
8592              protocol version 1.  So we don't need to wait any longer
8593              for it to initialize and send feature commands. */
8594           FeatureDone(cps, 1);
8595           cps->protocolVersion = 1;
8596           return;
8597         }
8598         cps->maybeThinking = FALSE;
8599
8600         if (StrStr(message, "draw")) {
8601             /* Program doesn't have "draw" command */
8602             cps->sendDrawOffers = 0;
8603             return;
8604         }
8605         if (cps->sendTime != 1 &&
8606             (StrStr(message, "time") || StrStr(message, "otim"))) {
8607           /* Program apparently doesn't have "time" or "otim" command */
8608           cps->sendTime = 0;
8609           return;
8610         }
8611         if (StrStr(message, "analyze")) {
8612             cps->analysisSupport = FALSE;
8613             cps->analyzing = FALSE;
8614 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8615             EditGameEvent(); // [HGM] try to preserve loaded game
8616             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8617             DisplayError(buf2, 0);
8618             return;
8619         }
8620         if (StrStr(message, "(no matching move)st")) {
8621           /* Special kludge for GNU Chess 4 only */
8622           cps->stKludge = TRUE;
8623           SendTimeControl(cps, movesPerSession, timeControl,
8624                           timeIncrement, appData.searchDepth,
8625                           searchTime);
8626           return;
8627         }
8628         if (StrStr(message, "(no matching move)sd")) {
8629           /* Special kludge for GNU Chess 4 only */
8630           cps->sdKludge = TRUE;
8631           SendTimeControl(cps, movesPerSession, timeControl,
8632                           timeIncrement, appData.searchDepth,
8633                           searchTime);
8634           return;
8635         }
8636         if (!StrStr(message, "llegal")) {
8637             return;
8638         }
8639         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8640             gameMode == IcsIdle) return;
8641         if (forwardMostMove <= backwardMostMove) return;
8642         if (pausing) PauseEvent();
8643       if(appData.forceIllegal) {
8644             // [HGM] illegal: machine refused move; force position after move into it
8645           SendToProgram("force\n", cps);
8646           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8647                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8648                 // when black is to move, while there might be nothing on a2 or black
8649                 // might already have the move. So send the board as if white has the move.
8650                 // But first we must change the stm of the engine, as it refused the last move
8651                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8652                 if(WhiteOnMove(forwardMostMove)) {
8653                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8654                     SendBoard(cps, forwardMostMove); // kludgeless board
8655                 } else {
8656                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8657                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8658                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8659                 }
8660           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8661             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8662                  gameMode == TwoMachinesPlay)
8663               SendToProgram("go\n", cps);
8664             return;
8665       } else
8666         if (gameMode == PlayFromGameFile) {
8667             /* Stop reading this game file */
8668             gameMode = EditGame;
8669             ModeHighlight();
8670         }
8671         /* [HGM] illegal-move claim should forfeit game when Xboard */
8672         /* only passes fully legal moves                            */
8673         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8674             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8675                                 "False illegal-move claim", GE_XBOARD );
8676             return; // do not take back move we tested as valid
8677         }
8678         currentMove = forwardMostMove-1;
8679         DisplayMove(currentMove-1); /* before DisplayMoveError */
8680         SwitchClocks(forwardMostMove-1); // [HGM] race
8681         DisplayBothClocks();
8682         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8683                 parseList[currentMove], _(cps->which));
8684         DisplayMoveError(buf1);
8685         DrawPosition(FALSE, boards[currentMove]);
8686
8687         SetUserThinkingEnables();
8688         return;
8689     }
8690     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8691         /* Program has a broken "time" command that
8692            outputs a string not ending in newline.
8693            Don't use it. */
8694         cps->sendTime = 0;
8695     }
8696
8697     /*
8698      * If chess program startup fails, exit with an error message.
8699      * Attempts to recover here are futile. [HGM] Well, we try anyway
8700      */
8701     if ((StrStr(message, "unknown host") != NULL)
8702         || (StrStr(message, "No remote directory") != NULL)
8703         || (StrStr(message, "not found") != NULL)
8704         || (StrStr(message, "No such file") != NULL)
8705         || (StrStr(message, "can't alloc") != NULL)
8706         || (StrStr(message, "Permission denied") != NULL)) {
8707
8708         cps->maybeThinking = FALSE;
8709         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8710                 _(cps->which), cps->program, cps->host, message);
8711         RemoveInputSource(cps->isr);
8712         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8713             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8714             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8715         }
8716         return;
8717     }
8718
8719     /*
8720      * Look for hint output
8721      */
8722     if (sscanf(message, "Hint: %s", buf1) == 1) {
8723         if (cps == &first && hintRequested) {
8724             hintRequested = FALSE;
8725             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8726                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8727                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8728                                     PosFlags(forwardMostMove),
8729                                     fromY, fromX, toY, toX, promoChar, buf1);
8730                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8731                 DisplayInformation(buf2);
8732             } else {
8733                 /* Hint move could not be parsed!? */
8734               snprintf(buf2, sizeof(buf2),
8735                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8736                         buf1, _(cps->which));
8737                 DisplayError(buf2, 0);
8738             }
8739         } else {
8740           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8741         }
8742         return;
8743     }
8744
8745     /*
8746      * Ignore other messages if game is not in progress
8747      */
8748     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8749         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8750
8751     /*
8752      * look for win, lose, draw, or draw offer
8753      */
8754     if (strncmp(message, "1-0", 3) == 0) {
8755         char *p, *q, *r = "";
8756         p = strchr(message, '{');
8757         if (p) {
8758             q = strchr(p, '}');
8759             if (q) {
8760                 *q = NULLCHAR;
8761                 r = p + 1;
8762             }
8763         }
8764         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8765         return;
8766     } else if (strncmp(message, "0-1", 3) == 0) {
8767         char *p, *q, *r = "";
8768         p = strchr(message, '{');
8769         if (p) {
8770             q = strchr(p, '}');
8771             if (q) {
8772                 *q = NULLCHAR;
8773                 r = p + 1;
8774             }
8775         }
8776         /* Kludge for Arasan 4.1 bug */
8777         if (strcmp(r, "Black resigns") == 0) {
8778             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8779             return;
8780         }
8781         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8782         return;
8783     } else if (strncmp(message, "1/2", 3) == 0) {
8784         char *p, *q, *r = "";
8785         p = strchr(message, '{');
8786         if (p) {
8787             q = strchr(p, '}');
8788             if (q) {
8789                 *q = NULLCHAR;
8790                 r = p + 1;
8791             }
8792         }
8793
8794         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8795         return;
8796
8797     } else if (strncmp(message, "White resign", 12) == 0) {
8798         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8799         return;
8800     } else if (strncmp(message, "Black resign", 12) == 0) {
8801         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8802         return;
8803     } else if (strncmp(message, "White matches", 13) == 0 ||
8804                strncmp(message, "Black matches", 13) == 0   ) {
8805         /* [HGM] ignore GNUShogi noises */
8806         return;
8807     } else if (strncmp(message, "White", 5) == 0 &&
8808                message[5] != '(' &&
8809                StrStr(message, "Black") == NULL) {
8810         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8811         return;
8812     } else if (strncmp(message, "Black", 5) == 0 &&
8813                message[5] != '(') {
8814         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8815         return;
8816     } else if (strcmp(message, "resign") == 0 ||
8817                strcmp(message, "computer resigns") == 0) {
8818         switch (gameMode) {
8819           case MachinePlaysBlack:
8820           case IcsPlayingBlack:
8821             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8822             break;
8823           case MachinePlaysWhite:
8824           case IcsPlayingWhite:
8825             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8826             break;
8827           case TwoMachinesPlay:
8828             if (cps->twoMachinesColor[0] == 'w')
8829               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8830             else
8831               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8832             break;
8833           default:
8834             /* can't happen */
8835             break;
8836         }
8837         return;
8838     } else if (strncmp(message, "opponent mates", 14) == 0) {
8839         switch (gameMode) {
8840           case MachinePlaysBlack:
8841           case IcsPlayingBlack:
8842             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8843             break;
8844           case MachinePlaysWhite:
8845           case IcsPlayingWhite:
8846             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8847             break;
8848           case TwoMachinesPlay:
8849             if (cps->twoMachinesColor[0] == 'w')
8850               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8851             else
8852               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8853             break;
8854           default:
8855             /* can't happen */
8856             break;
8857         }
8858         return;
8859     } else if (strncmp(message, "computer mates", 14) == 0) {
8860         switch (gameMode) {
8861           case MachinePlaysBlack:
8862           case IcsPlayingBlack:
8863             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8864             break;
8865           case MachinePlaysWhite:
8866           case IcsPlayingWhite:
8867             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8868             break;
8869           case TwoMachinesPlay:
8870             if (cps->twoMachinesColor[0] == 'w')
8871               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8872             else
8873               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8874             break;
8875           default:
8876             /* can't happen */
8877             break;
8878         }
8879         return;
8880     } else if (strncmp(message, "checkmate", 9) == 0) {
8881         if (WhiteOnMove(forwardMostMove)) {
8882             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8883         } else {
8884             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8885         }
8886         return;
8887     } else if (strstr(message, "Draw") != NULL ||
8888                strstr(message, "game is a draw") != NULL) {
8889         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8890         return;
8891     } else if (strstr(message, "offer") != NULL &&
8892                strstr(message, "draw") != NULL) {
8893 #if ZIPPY
8894         if (appData.zippyPlay && first.initDone) {
8895             /* Relay offer to ICS */
8896             SendToICS(ics_prefix);
8897             SendToICS("draw\n");
8898         }
8899 #endif
8900         cps->offeredDraw = 2; /* valid until this engine moves twice */
8901         if (gameMode == TwoMachinesPlay) {
8902             if (cps->other->offeredDraw) {
8903                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8904             /* [HGM] in two-machine mode we delay relaying draw offer      */
8905             /* until after we also have move, to see if it is really claim */
8906             }
8907         } else if (gameMode == MachinePlaysWhite ||
8908                    gameMode == MachinePlaysBlack) {
8909           if (userOfferedDraw) {
8910             DisplayInformation(_("Machine accepts your draw offer"));
8911             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8912           } else {
8913             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8914           }
8915         }
8916     }
8917
8918
8919     /*
8920      * Look for thinking output
8921      */
8922     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8923           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8924                                 ) {
8925         int plylev, mvleft, mvtot, curscore, time;
8926         char mvname[MOVE_LEN];
8927         u64 nodes; // [DM]
8928         char plyext;
8929         int ignore = FALSE;
8930         int prefixHint = FALSE;
8931         mvname[0] = NULLCHAR;
8932
8933         switch (gameMode) {
8934           case MachinePlaysBlack:
8935           case IcsPlayingBlack:
8936             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8937             break;
8938           case MachinePlaysWhite:
8939           case IcsPlayingWhite:
8940             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8941             break;
8942           case AnalyzeMode:
8943           case AnalyzeFile:
8944             break;
8945           case IcsObserving: /* [DM] icsEngineAnalyze */
8946             if (!appData.icsEngineAnalyze) ignore = TRUE;
8947             break;
8948           case TwoMachinesPlay:
8949             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8950                 ignore = TRUE;
8951             }
8952             break;
8953           default:
8954             ignore = TRUE;
8955             break;
8956         }
8957
8958         if (!ignore) {
8959             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8960             buf1[0] = NULLCHAR;
8961             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8962                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8963
8964                 if (plyext != ' ' && plyext != '\t') {
8965                     time *= 100;
8966                 }
8967
8968                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8969                 if( cps->scoreIsAbsolute &&
8970                     ( gameMode == MachinePlaysBlack ||
8971                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8972                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8973                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8974                      !WhiteOnMove(currentMove)
8975                     ) )
8976                 {
8977                     curscore = -curscore;
8978                 }
8979
8980                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8981
8982                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8983                         char buf[MSG_SIZ];
8984                         FILE *f;
8985                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8986                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8987                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8988                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8989                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8990                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8991                                 fclose(f);
8992                         } else DisplayError(_("failed writing PV"), 0);
8993                 }
8994
8995                 tempStats.depth = plylev;
8996                 tempStats.nodes = nodes;
8997                 tempStats.time = time;
8998                 tempStats.score = curscore;
8999                 tempStats.got_only_move = 0;
9000
9001                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9002                         int ticklen;
9003
9004                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9005                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9006                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9007                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9008                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9009                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9010                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9011                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9012                 }
9013
9014                 /* Buffer overflow protection */
9015                 if (pv[0] != NULLCHAR) {
9016                     if (strlen(pv) >= sizeof(tempStats.movelist)
9017                         && appData.debugMode) {
9018                         fprintf(debugFP,
9019                                 "PV is too long; using the first %u bytes.\n",
9020                                 (unsigned) sizeof(tempStats.movelist) - 1);
9021                     }
9022
9023                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9024                 } else {
9025                     sprintf(tempStats.movelist, " no PV\n");
9026                 }
9027
9028                 if (tempStats.seen_stat) {
9029                     tempStats.ok_to_send = 1;
9030                 }
9031
9032                 if (strchr(tempStats.movelist, '(') != NULL) {
9033                     tempStats.line_is_book = 1;
9034                     tempStats.nr_moves = 0;
9035                     tempStats.moves_left = 0;
9036                 } else {
9037                     tempStats.line_is_book = 0;
9038                 }
9039
9040                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9041                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9042
9043                 SendProgramStatsToFrontend( cps, &tempStats );
9044
9045                 /*
9046                     [AS] Protect the thinkOutput buffer from overflow... this
9047                     is only useful if buf1 hasn't overflowed first!
9048                 */
9049                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9050                          plylev,
9051                          (gameMode == TwoMachinesPlay ?
9052                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9053                          ((double) curscore) / 100.0,
9054                          prefixHint ? lastHint : "",
9055                          prefixHint ? " " : "" );
9056
9057                 if( buf1[0] != NULLCHAR ) {
9058                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9059
9060                     if( strlen(pv) > max_len ) {
9061                         if( appData.debugMode) {
9062                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9063                         }
9064                         pv[max_len+1] = '\0';
9065                     }
9066
9067                     strcat( thinkOutput, pv);
9068                 }
9069
9070                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9071                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9072                     DisplayMove(currentMove - 1);
9073                 }
9074                 return;
9075
9076             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9077                 /* crafty (9.25+) says "(only move) <move>"
9078                  * if there is only 1 legal move
9079                  */
9080                 sscanf(p, "(only move) %s", buf1);
9081                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9082                 sprintf(programStats.movelist, "%s (only move)", buf1);
9083                 programStats.depth = 1;
9084                 programStats.nr_moves = 1;
9085                 programStats.moves_left = 1;
9086                 programStats.nodes = 1;
9087                 programStats.time = 1;
9088                 programStats.got_only_move = 1;
9089
9090                 /* Not really, but we also use this member to
9091                    mean "line isn't going to change" (Crafty
9092                    isn't searching, so stats won't change) */
9093                 programStats.line_is_book = 1;
9094
9095                 SendProgramStatsToFrontend( cps, &programStats );
9096
9097                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9098                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9099                     DisplayMove(currentMove - 1);
9100                 }
9101                 return;
9102             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9103                               &time, &nodes, &plylev, &mvleft,
9104                               &mvtot, mvname) >= 5) {
9105                 /* The stat01: line is from Crafty (9.29+) in response
9106                    to the "." command */
9107                 programStats.seen_stat = 1;
9108                 cps->maybeThinking = TRUE;
9109
9110                 if (programStats.got_only_move || !appData.periodicUpdates)
9111                   return;
9112
9113                 programStats.depth = plylev;
9114                 programStats.time = time;
9115                 programStats.nodes = nodes;
9116                 programStats.moves_left = mvleft;
9117                 programStats.nr_moves = mvtot;
9118                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9119                 programStats.ok_to_send = 1;
9120                 programStats.movelist[0] = '\0';
9121
9122                 SendProgramStatsToFrontend( cps, &programStats );
9123
9124                 return;
9125
9126             } else if (strncmp(message,"++",2) == 0) {
9127                 /* Crafty 9.29+ outputs this */
9128                 programStats.got_fail = 2;
9129                 return;
9130
9131             } else if (strncmp(message,"--",2) == 0) {
9132                 /* Crafty 9.29+ outputs this */
9133                 programStats.got_fail = 1;
9134                 return;
9135
9136             } else if (thinkOutput[0] != NULLCHAR &&
9137                        strncmp(message, "    ", 4) == 0) {
9138                 unsigned message_len;
9139
9140                 p = message;
9141                 while (*p && *p == ' ') p++;
9142
9143                 message_len = strlen( p );
9144
9145                 /* [AS] Avoid buffer overflow */
9146                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9147                     strcat(thinkOutput, " ");
9148                     strcat(thinkOutput, p);
9149                 }
9150
9151                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9152                     strcat(programStats.movelist, " ");
9153                     strcat(programStats.movelist, p);
9154                 }
9155
9156                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9157                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9158                     DisplayMove(currentMove - 1);
9159                 }
9160                 return;
9161             }
9162         }
9163         else {
9164             buf1[0] = NULLCHAR;
9165
9166             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9167                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9168             {
9169                 ChessProgramStats cpstats;
9170
9171                 if (plyext != ' ' && plyext != '\t') {
9172                     time *= 100;
9173                 }
9174
9175                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9176                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9177                     curscore = -curscore;
9178                 }
9179
9180                 cpstats.depth = plylev;
9181                 cpstats.nodes = nodes;
9182                 cpstats.time = time;
9183                 cpstats.score = curscore;
9184                 cpstats.got_only_move = 0;
9185                 cpstats.movelist[0] = '\0';
9186
9187                 if (buf1[0] != NULLCHAR) {
9188                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9189                 }
9190
9191                 cpstats.ok_to_send = 0;
9192                 cpstats.line_is_book = 0;
9193                 cpstats.nr_moves = 0;
9194                 cpstats.moves_left = 0;
9195
9196                 SendProgramStatsToFrontend( cps, &cpstats );
9197             }
9198         }
9199     }
9200 }
9201
9202
9203 /* Parse a game score from the character string "game", and
9204    record it as the history of the current game.  The game
9205    score is NOT assumed to start from the standard position.
9206    The display is not updated in any way.
9207    */
9208 void
9209 ParseGameHistory (char *game)
9210 {
9211     ChessMove moveType;
9212     int fromX, fromY, toX, toY, boardIndex;
9213     char promoChar;
9214     char *p, *q;
9215     char buf[MSG_SIZ];
9216
9217     if (appData.debugMode)
9218       fprintf(debugFP, "Parsing game history: %s\n", game);
9219
9220     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9221     gameInfo.site = StrSave(appData.icsHost);
9222     gameInfo.date = PGNDate();
9223     gameInfo.round = StrSave("-");
9224
9225     /* Parse out names of players */
9226     while (*game == ' ') game++;
9227     p = buf;
9228     while (*game != ' ') *p++ = *game++;
9229     *p = NULLCHAR;
9230     gameInfo.white = StrSave(buf);
9231     while (*game == ' ') game++;
9232     p = buf;
9233     while (*game != ' ' && *game != '\n') *p++ = *game++;
9234     *p = NULLCHAR;
9235     gameInfo.black = StrSave(buf);
9236
9237     /* Parse moves */
9238     boardIndex = blackPlaysFirst ? 1 : 0;
9239     yynewstr(game);
9240     for (;;) {
9241         yyboardindex = boardIndex;
9242         moveType = (ChessMove) Myylex();
9243         switch (moveType) {
9244           case IllegalMove:             /* maybe suicide chess, etc. */
9245   if (appData.debugMode) {
9246     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9247     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9248     setbuf(debugFP, NULL);
9249   }
9250           case WhitePromotion:
9251           case BlackPromotion:
9252           case WhiteNonPromotion:
9253           case BlackNonPromotion:
9254           case NormalMove:
9255           case WhiteCapturesEnPassant:
9256           case BlackCapturesEnPassant:
9257           case WhiteKingSideCastle:
9258           case WhiteQueenSideCastle:
9259           case BlackKingSideCastle:
9260           case BlackQueenSideCastle:
9261           case WhiteKingSideCastleWild:
9262           case WhiteQueenSideCastleWild:
9263           case BlackKingSideCastleWild:
9264           case BlackQueenSideCastleWild:
9265           /* PUSH Fabien */
9266           case WhiteHSideCastleFR:
9267           case WhiteASideCastleFR:
9268           case BlackHSideCastleFR:
9269           case BlackASideCastleFR:
9270           /* POP Fabien */
9271             fromX = currentMoveString[0] - AAA;
9272             fromY = currentMoveString[1] - ONE;
9273             toX = currentMoveString[2] - AAA;
9274             toY = currentMoveString[3] - ONE;
9275             promoChar = currentMoveString[4];
9276             break;
9277           case WhiteDrop:
9278           case BlackDrop:
9279             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9280             fromX = moveType == WhiteDrop ?
9281               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9282             (int) CharToPiece(ToLower(currentMoveString[0]));
9283             fromY = DROP_RANK;
9284             toX = currentMoveString[2] - AAA;
9285             toY = currentMoveString[3] - ONE;
9286             promoChar = NULLCHAR;
9287             break;
9288           case AmbiguousMove:
9289             /* bug? */
9290             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9291   if (appData.debugMode) {
9292     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9293     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9294     setbuf(debugFP, NULL);
9295   }
9296             DisplayError(buf, 0);
9297             return;
9298           case ImpossibleMove:
9299             /* bug? */
9300             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9301   if (appData.debugMode) {
9302     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9303     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9304     setbuf(debugFP, NULL);
9305   }
9306             DisplayError(buf, 0);
9307             return;
9308           case EndOfFile:
9309             if (boardIndex < backwardMostMove) {
9310                 /* Oops, gap.  How did that happen? */
9311                 DisplayError(_("Gap in move list"), 0);
9312                 return;
9313             }
9314             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9315             if (boardIndex > forwardMostMove) {
9316                 forwardMostMove = boardIndex;
9317             }
9318             return;
9319           case ElapsedTime:
9320             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9321                 strcat(parseList[boardIndex-1], " ");
9322                 strcat(parseList[boardIndex-1], yy_text);
9323             }
9324             continue;
9325           case Comment:
9326           case PGNTag:
9327           case NAG:
9328           default:
9329             /* ignore */
9330             continue;
9331           case WhiteWins:
9332           case BlackWins:
9333           case GameIsDrawn:
9334           case GameUnfinished:
9335             if (gameMode == IcsExamining) {
9336                 if (boardIndex < backwardMostMove) {
9337                     /* Oops, gap.  How did that happen? */
9338                     return;
9339                 }
9340                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9341                 return;
9342             }
9343             gameInfo.result = moveType;
9344             p = strchr(yy_text, '{');
9345             if (p == NULL) p = strchr(yy_text, '(');
9346             if (p == NULL) {
9347                 p = yy_text;
9348                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9349             } else {
9350                 q = strchr(p, *p == '{' ? '}' : ')');
9351                 if (q != NULL) *q = NULLCHAR;
9352                 p++;
9353             }
9354             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9355             gameInfo.resultDetails = StrSave(p);
9356             continue;
9357         }
9358         if (boardIndex >= forwardMostMove &&
9359             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9360             backwardMostMove = blackPlaysFirst ? 1 : 0;
9361             return;
9362         }
9363         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9364                                  fromY, fromX, toY, toX, promoChar,
9365                                  parseList[boardIndex]);
9366         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9367         /* currentMoveString is set as a side-effect of yylex */
9368         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9369         strcat(moveList[boardIndex], "\n");
9370         boardIndex++;
9371         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9372         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9373           case MT_NONE:
9374           case MT_STALEMATE:
9375           default:
9376             break;
9377           case MT_CHECK:
9378             if(gameInfo.variant != VariantShogi)
9379                 strcat(parseList[boardIndex - 1], "+");
9380             break;
9381           case MT_CHECKMATE:
9382           case MT_STAINMATE:
9383             strcat(parseList[boardIndex - 1], "#");
9384             break;
9385         }
9386     }
9387 }
9388
9389
9390 /* Apply a move to the given board  */
9391 void
9392 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9393 {
9394   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9395   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9396
9397     /* [HGM] compute & store e.p. status and castling rights for new position */
9398     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9399
9400       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9401       oldEP = (signed char)board[EP_STATUS];
9402       board[EP_STATUS] = EP_NONE;
9403
9404   if (fromY == DROP_RANK) {
9405         /* must be first */
9406         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9407             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9408             return;
9409         }
9410         piece = board[toY][toX] = (ChessSquare) fromX;
9411   } else {
9412       int i;
9413
9414       if( board[toY][toX] != EmptySquare )
9415            board[EP_STATUS] = EP_CAPTURE;
9416
9417       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9418            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9419                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9420       } else
9421       if( board[fromY][fromX] == WhitePawn ) {
9422            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9423                board[EP_STATUS] = EP_PAWN_MOVE;
9424            if( toY-fromY==2) {
9425                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9426                         gameInfo.variant != VariantBerolina || toX < fromX)
9427                       board[EP_STATUS] = toX | berolina;
9428                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9429                         gameInfo.variant != VariantBerolina || toX > fromX)
9430                       board[EP_STATUS] = toX;
9431            }
9432       } else
9433       if( board[fromY][fromX] == BlackPawn ) {
9434            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9435                board[EP_STATUS] = EP_PAWN_MOVE;
9436            if( toY-fromY== -2) {
9437                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9438                         gameInfo.variant != VariantBerolina || toX < fromX)
9439                       board[EP_STATUS] = toX | berolina;
9440                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9441                         gameInfo.variant != VariantBerolina || toX > fromX)
9442                       board[EP_STATUS] = toX;
9443            }
9444        }
9445
9446        for(i=0; i<nrCastlingRights; i++) {
9447            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9448               board[CASTLING][i] == toX   && castlingRank[i] == toY
9449              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9450        }
9451
9452        if(gameInfo.variant == VariantSChess) { // update virginity
9453            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9454            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9455            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9456            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9457        }
9458
9459      if (fromX == toX && fromY == toY) return;
9460
9461      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9462      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9463      if(gameInfo.variant == VariantKnightmate)
9464          king += (int) WhiteUnicorn - (int) WhiteKing;
9465
9466     /* Code added by Tord: */
9467     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9468     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9469         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9470       board[fromY][fromX] = EmptySquare;
9471       board[toY][toX] = EmptySquare;
9472       if((toX > fromX) != (piece == WhiteRook)) {
9473         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9474       } else {
9475         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9476       }
9477     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9478                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9479       board[fromY][fromX] = EmptySquare;
9480       board[toY][toX] = EmptySquare;
9481       if((toX > fromX) != (piece == BlackRook)) {
9482         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9483       } else {
9484         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9485       }
9486     /* End of code added by Tord */
9487
9488     } else if (board[fromY][fromX] == king
9489         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9490         && toY == fromY && toX > fromX+1) {
9491         board[fromY][fromX] = EmptySquare;
9492         board[toY][toX] = king;
9493         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9494         board[fromY][BOARD_RGHT-1] = EmptySquare;
9495     } else if (board[fromY][fromX] == king
9496         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9497                && toY == fromY && toX < fromX-1) {
9498         board[fromY][fromX] = EmptySquare;
9499         board[toY][toX] = king;
9500         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9501         board[fromY][BOARD_LEFT] = EmptySquare;
9502     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9503                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9504                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9505                ) {
9506         /* white pawn promotion */
9507         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9508         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9509             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9510         board[fromY][fromX] = EmptySquare;
9511     } else if ((fromY >= BOARD_HEIGHT>>1)
9512                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9513                && (toX != fromX)
9514                && gameInfo.variant != VariantXiangqi
9515                && gameInfo.variant != VariantBerolina
9516                && (board[fromY][fromX] == WhitePawn)
9517                && (board[toY][toX] == EmptySquare)) {
9518         board[fromY][fromX] = EmptySquare;
9519         board[toY][toX] = WhitePawn;
9520         captured = board[toY - 1][toX];
9521         board[toY - 1][toX] = EmptySquare;
9522     } else if ((fromY == BOARD_HEIGHT-4)
9523                && (toX == fromX)
9524                && gameInfo.variant == VariantBerolina
9525                && (board[fromY][fromX] == WhitePawn)
9526                && (board[toY][toX] == EmptySquare)) {
9527         board[fromY][fromX] = EmptySquare;
9528         board[toY][toX] = WhitePawn;
9529         if(oldEP & EP_BEROLIN_A) {
9530                 captured = board[fromY][fromX-1];
9531                 board[fromY][fromX-1] = EmptySquare;
9532         }else{  captured = board[fromY][fromX+1];
9533                 board[fromY][fromX+1] = EmptySquare;
9534         }
9535     } else if (board[fromY][fromX] == king
9536         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9537                && toY == fromY && toX > fromX+1) {
9538         board[fromY][fromX] = EmptySquare;
9539         board[toY][toX] = king;
9540         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9541         board[fromY][BOARD_RGHT-1] = EmptySquare;
9542     } else if (board[fromY][fromX] == king
9543         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9544                && toY == fromY && toX < fromX-1) {
9545         board[fromY][fromX] = EmptySquare;
9546         board[toY][toX] = king;
9547         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9548         board[fromY][BOARD_LEFT] = EmptySquare;
9549     } else if (fromY == 7 && fromX == 3
9550                && board[fromY][fromX] == BlackKing
9551                && toY == 7 && toX == 5) {
9552         board[fromY][fromX] = EmptySquare;
9553         board[toY][toX] = BlackKing;
9554         board[fromY][7] = EmptySquare;
9555         board[toY][4] = BlackRook;
9556     } else if (fromY == 7 && fromX == 3
9557                && board[fromY][fromX] == BlackKing
9558                && toY == 7 && toX == 1) {
9559         board[fromY][fromX] = EmptySquare;
9560         board[toY][toX] = BlackKing;
9561         board[fromY][0] = EmptySquare;
9562         board[toY][2] = BlackRook;
9563     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9564                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9565                && toY < promoRank && promoChar
9566                ) {
9567         /* black pawn promotion */
9568         board[toY][toX] = CharToPiece(ToLower(promoChar));
9569         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9570             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9571         board[fromY][fromX] = EmptySquare;
9572     } else if ((fromY < BOARD_HEIGHT>>1)
9573                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9574                && (toX != fromX)
9575                && gameInfo.variant != VariantXiangqi
9576                && gameInfo.variant != VariantBerolina
9577                && (board[fromY][fromX] == BlackPawn)
9578                && (board[toY][toX] == EmptySquare)) {
9579         board[fromY][fromX] = EmptySquare;
9580         board[toY][toX] = BlackPawn;
9581         captured = board[toY + 1][toX];
9582         board[toY + 1][toX] = EmptySquare;
9583     } else if ((fromY == 3)
9584                && (toX == fromX)
9585                && gameInfo.variant == VariantBerolina
9586                && (board[fromY][fromX] == BlackPawn)
9587                && (board[toY][toX] == EmptySquare)) {
9588         board[fromY][fromX] = EmptySquare;
9589         board[toY][toX] = BlackPawn;
9590         if(oldEP & EP_BEROLIN_A) {
9591                 captured = board[fromY][fromX-1];
9592                 board[fromY][fromX-1] = EmptySquare;
9593         }else{  captured = board[fromY][fromX+1];
9594                 board[fromY][fromX+1] = EmptySquare;
9595         }
9596     } else {
9597         board[toY][toX] = board[fromY][fromX];
9598         board[fromY][fromX] = EmptySquare;
9599     }
9600   }
9601
9602     if (gameInfo.holdingsWidth != 0) {
9603
9604       /* !!A lot more code needs to be written to support holdings  */
9605       /* [HGM] OK, so I have written it. Holdings are stored in the */
9606       /* penultimate board files, so they are automaticlly stored   */
9607       /* in the game history.                                       */
9608       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9609                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9610         /* Delete from holdings, by decreasing count */
9611         /* and erasing image if necessary            */
9612         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9613         if(p < (int) BlackPawn) { /* white drop */
9614              p -= (int)WhitePawn;
9615                  p = PieceToNumber((ChessSquare)p);
9616              if(p >= gameInfo.holdingsSize) p = 0;
9617              if(--board[p][BOARD_WIDTH-2] <= 0)
9618                   board[p][BOARD_WIDTH-1] = EmptySquare;
9619              if((int)board[p][BOARD_WIDTH-2] < 0)
9620                         board[p][BOARD_WIDTH-2] = 0;
9621         } else {                  /* black drop */
9622              p -= (int)BlackPawn;
9623                  p = PieceToNumber((ChessSquare)p);
9624              if(p >= gameInfo.holdingsSize) p = 0;
9625              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9626                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9627              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9628                         board[BOARD_HEIGHT-1-p][1] = 0;
9629         }
9630       }
9631       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9632           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9633         /* [HGM] holdings: Add to holdings, if holdings exist */
9634         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9635                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9636                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9637         }
9638         p = (int) captured;
9639         if (p >= (int) BlackPawn) {
9640           p -= (int)BlackPawn;
9641           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9642                   /* in Shogi restore piece to its original  first */
9643                   captured = (ChessSquare) (DEMOTED captured);
9644                   p = DEMOTED p;
9645           }
9646           p = PieceToNumber((ChessSquare)p);
9647           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9648           board[p][BOARD_WIDTH-2]++;
9649           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9650         } else {
9651           p -= (int)WhitePawn;
9652           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9653                   captured = (ChessSquare) (DEMOTED captured);
9654                   p = DEMOTED p;
9655           }
9656           p = PieceToNumber((ChessSquare)p);
9657           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9658           board[BOARD_HEIGHT-1-p][1]++;
9659           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9660         }
9661       }
9662     } else if (gameInfo.variant == VariantAtomic) {
9663       if (captured != EmptySquare) {
9664         int y, x;
9665         for (y = toY-1; y <= toY+1; y++) {
9666           for (x = toX-1; x <= toX+1; x++) {
9667             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9668                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9669               board[y][x] = EmptySquare;
9670             }
9671           }
9672         }
9673         board[toY][toX] = EmptySquare;
9674       }
9675     }
9676     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9677         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9678     } else
9679     if(promoChar == '+') {
9680         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9681         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9682     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9683         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9684         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9685            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9686         board[toY][toX] = newPiece;
9687     }
9688     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9689                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9690         // [HGM] superchess: take promotion piece out of holdings
9691         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9692         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9693             if(!--board[k][BOARD_WIDTH-2])
9694                 board[k][BOARD_WIDTH-1] = EmptySquare;
9695         } else {
9696             if(!--board[BOARD_HEIGHT-1-k][1])
9697                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9698         }
9699     }
9700
9701 }
9702
9703 /* Updates forwardMostMove */
9704 void
9705 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9706 {
9707 //    forwardMostMove++; // [HGM] bare: moved downstream
9708
9709     (void) CoordsToAlgebraic(boards[forwardMostMove],
9710                              PosFlags(forwardMostMove),
9711                              fromY, fromX, toY, toX, promoChar,
9712                              parseList[forwardMostMove]);
9713
9714     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9715         int timeLeft; static int lastLoadFlag=0; int king, piece;
9716         piece = boards[forwardMostMove][fromY][fromX];
9717         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9718         if(gameInfo.variant == VariantKnightmate)
9719             king += (int) WhiteUnicorn - (int) WhiteKing;
9720         if(forwardMostMove == 0) {
9721             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9722                 fprintf(serverMoves, "%s;", UserName());
9723             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9724                 fprintf(serverMoves, "%s;", second.tidy);
9725             fprintf(serverMoves, "%s;", first.tidy);
9726             if(gameMode == MachinePlaysWhite)
9727                 fprintf(serverMoves, "%s;", UserName());
9728             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9729                 fprintf(serverMoves, "%s;", second.tidy);
9730         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9731         lastLoadFlag = loadFlag;
9732         // print base move
9733         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9734         // print castling suffix
9735         if( toY == fromY && piece == king ) {
9736             if(toX-fromX > 1)
9737                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9738             if(fromX-toX >1)
9739                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9740         }
9741         // e.p. suffix
9742         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9743              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9744              boards[forwardMostMove][toY][toX] == EmptySquare
9745              && fromX != toX && fromY != toY)
9746                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9747         // promotion suffix
9748         if(promoChar != NULLCHAR) {
9749             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9750                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9751                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9752             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9753         }
9754         if(!loadFlag) {
9755                 char buf[MOVE_LEN*2], *p; int len;
9756             fprintf(serverMoves, "/%d/%d",
9757                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9758             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9759             else                      timeLeft = blackTimeRemaining/1000;
9760             fprintf(serverMoves, "/%d", timeLeft);
9761                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9762                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9763                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9764                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9765             fprintf(serverMoves, "/%s", buf);
9766         }
9767         fflush(serverMoves);
9768     }
9769
9770     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9771         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9772       return;
9773     }
9774     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9775     if (commentList[forwardMostMove+1] != NULL) {
9776         free(commentList[forwardMostMove+1]);
9777         commentList[forwardMostMove+1] = NULL;
9778     }
9779     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9780     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9781     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9782     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9783     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9784     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9785     adjustedClock = FALSE;
9786     gameInfo.result = GameUnfinished;
9787     if (gameInfo.resultDetails != NULL) {
9788         free(gameInfo.resultDetails);
9789         gameInfo.resultDetails = NULL;
9790     }
9791     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9792                               moveList[forwardMostMove - 1]);
9793     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9794       case MT_NONE:
9795       case MT_STALEMATE:
9796       default:
9797         break;
9798       case MT_CHECK:
9799         if(gameInfo.variant != VariantShogi)
9800             strcat(parseList[forwardMostMove - 1], "+");
9801         break;
9802       case MT_CHECKMATE:
9803       case MT_STAINMATE:
9804         strcat(parseList[forwardMostMove - 1], "#");
9805         break;
9806     }
9807
9808 }
9809
9810 /* Updates currentMove if not pausing */
9811 void
9812 ShowMove (int fromX, int fromY, int toX, int toY)
9813 {
9814     int instant = (gameMode == PlayFromGameFile) ?
9815         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9816     if(appData.noGUI) return;
9817     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9818         if (!instant) {
9819             if (forwardMostMove == currentMove + 1) {
9820                 AnimateMove(boards[forwardMostMove - 1],
9821                             fromX, fromY, toX, toY);
9822             }
9823         }
9824         currentMove = forwardMostMove;
9825     }
9826
9827     if (instant) return;
9828
9829     DisplayMove(currentMove - 1);
9830     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9831             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9832                 SetHighlights(fromX, fromY, toX, toY);
9833             }
9834     }
9835     DrawPosition(FALSE, boards[currentMove]);
9836     DisplayBothClocks();
9837     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9838 }
9839
9840 void
9841 SendEgtPath (ChessProgramState *cps)
9842 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9843         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9844
9845         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9846
9847         while(*p) {
9848             char c, *q = name+1, *r, *s;
9849
9850             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9851             while(*p && *p != ',') *q++ = *p++;
9852             *q++ = ':'; *q = 0;
9853             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9854                 strcmp(name, ",nalimov:") == 0 ) {
9855                 // take nalimov path from the menu-changeable option first, if it is defined
9856               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9857                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9858             } else
9859             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9860                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9861                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9862                 s = r = StrStr(s, ":") + 1; // beginning of path info
9863                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9864                 c = *r; *r = 0;             // temporarily null-terminate path info
9865                     *--q = 0;               // strip of trailig ':' from name
9866                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9867                 *r = c;
9868                 SendToProgram(buf,cps);     // send egtbpath command for this format
9869             }
9870             if(*p == ',') p++; // read away comma to position for next format name
9871         }
9872 }
9873
9874 static int
9875 NonStandardBoardSize ()
9876 {
9877       /* [HGM] Awkward testing. Should really be a table */
9878       int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9879       if( gameInfo.variant == VariantXiangqi )
9880            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9881       if( gameInfo.variant == VariantShogi )
9882            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9883       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9884            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9885       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9886           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9887            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9888       if( gameInfo.variant == VariantCourier )
9889            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9890       if( gameInfo.variant == VariantSuper )
9891            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9892       if( gameInfo.variant == VariantGreat )
9893            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9894       if( gameInfo.variant == VariantSChess )
9895            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9896       if( gameInfo.variant == VariantGrand )
9897            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9898       return overruled;
9899 }
9900
9901 void
9902 InitChessProgram (ChessProgramState *cps, int setup)
9903 /* setup needed to setup FRC opening position */
9904 {
9905     char buf[MSG_SIZ], b[MSG_SIZ];
9906     if (appData.noChessProgram) return;
9907     hintRequested = FALSE;
9908     bookRequested = FALSE;
9909
9910     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9911     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9912     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9913     if(cps->memSize) { /* [HGM] memory */
9914       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9915         SendToProgram(buf, cps);
9916     }
9917     SendEgtPath(cps); /* [HGM] EGT */
9918     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9919       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9920         SendToProgram(buf, cps);
9921     }
9922
9923     SendToProgram(cps->initString, cps);
9924     if (gameInfo.variant != VariantNormal &&
9925         gameInfo.variant != VariantLoadable
9926         /* [HGM] also send variant if board size non-standard */
9927         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9928                                             ) {
9929       char *v = VariantName(gameInfo.variant);
9930       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9931         /* [HGM] in protocol 1 we have to assume all variants valid */
9932         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9933         DisplayFatalError(buf, 0, 1);
9934         return;
9935       }
9936
9937       if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
9938         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9939                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9940            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9941            if(StrStr(cps->variants, b) == NULL) {
9942                // specific sized variant not known, check if general sizing allowed
9943                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9944                    if(StrStr(cps->variants, "boardsize") == NULL) {
9945                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9946                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9947                        DisplayFatalError(buf, 0, 1);
9948                        return;
9949                    }
9950                    /* [HGM] here we really should compare with the maximum supported board size */
9951                }
9952            }
9953       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9954       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9955       SendToProgram(buf, cps);
9956     }
9957     currentlyInitializedVariant = gameInfo.variant;
9958
9959     /* [HGM] send opening position in FRC to first engine */
9960     if(setup) {
9961           SendToProgram("force\n", cps);
9962           SendBoard(cps, 0);
9963           /* engine is now in force mode! Set flag to wake it up after first move. */
9964           setboardSpoiledMachineBlack = 1;
9965     }
9966
9967     if (cps->sendICS) {
9968       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9969       SendToProgram(buf, cps);
9970     }
9971     cps->maybeThinking = FALSE;
9972     cps->offeredDraw = 0;
9973     if (!appData.icsActive) {
9974         SendTimeControl(cps, movesPerSession, timeControl,
9975                         timeIncrement, appData.searchDepth,
9976                         searchTime);
9977     }
9978     if (appData.showThinking
9979         // [HGM] thinking: four options require thinking output to be sent
9980         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9981                                 ) {
9982         SendToProgram("post\n", cps);
9983     }
9984     SendToProgram("hard\n", cps);
9985     if (!appData.ponderNextMove) {
9986         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9987            it without being sure what state we are in first.  "hard"
9988            is not a toggle, so that one is OK.
9989          */
9990         SendToProgram("easy\n", cps);
9991     }
9992     if (cps->usePing) {
9993       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9994       SendToProgram(buf, cps);
9995     }
9996     cps->initDone = TRUE;
9997     ClearEngineOutputPane(cps == &second);
9998 }
9999
10000
10001 void
10002 ResendOptions (ChessProgramState *cps)
10003 { // send the stored value of the options
10004   int i;
10005   char buf[MSG_SIZ];
10006   Option *opt = cps->option;
10007   for(i=0; i<cps->nrOptions; i++, opt++) {
10008       switch(opt->type) {
10009         case Spin:
10010         case Slider:
10011         case CheckBox:
10012             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10013           break;
10014         case ComboBox:
10015           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10016           break;
10017         default:
10018             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10019           break;
10020         case Button:
10021         case SaveButton:
10022           continue;
10023       }
10024       SendToProgram(buf, cps);
10025   }
10026 }
10027
10028 void
10029 StartChessProgram (ChessProgramState *cps)
10030 {
10031     char buf[MSG_SIZ];
10032     int err;
10033
10034     if (appData.noChessProgram) return;
10035     cps->initDone = FALSE;
10036
10037     if (strcmp(cps->host, "localhost") == 0) {
10038         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10039     } else if (*appData.remoteShell == NULLCHAR) {
10040         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10041     } else {
10042         if (*appData.remoteUser == NULLCHAR) {
10043           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10044                     cps->program);
10045         } else {
10046           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10047                     cps->host, appData.remoteUser, cps->program);
10048         }
10049         err = StartChildProcess(buf, "", &cps->pr);
10050     }
10051
10052     if (err != 0) {
10053       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10054         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10055         if(cps != &first) return;
10056         appData.noChessProgram = TRUE;
10057         ThawUI();
10058         SetNCPMode();
10059 //      DisplayFatalError(buf, err, 1);
10060 //      cps->pr = NoProc;
10061 //      cps->isr = NULL;
10062         return;
10063     }
10064
10065     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10066     if (cps->protocolVersion > 1) {
10067       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10068       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10069         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10070         cps->comboCnt = 0;  //                and values of combo boxes
10071       }
10072       SendToProgram(buf, cps);
10073       if(cps->reload) ResendOptions(cps);
10074     } else {
10075       SendToProgram("xboard\n", cps);
10076     }
10077 }
10078
10079 void
10080 TwoMachinesEventIfReady P((void))
10081 {
10082   static int curMess = 0;
10083   if (first.lastPing != first.lastPong) {
10084     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10085     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10086     return;
10087   }
10088   if (second.lastPing != second.lastPong) {
10089     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10090     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10091     return;
10092   }
10093   DisplayMessage("", ""); curMess = 0;
10094   TwoMachinesEvent();
10095 }
10096
10097 char *
10098 MakeName (char *template)
10099 {
10100     time_t clock;
10101     struct tm *tm;
10102     static char buf[MSG_SIZ];
10103     char *p = buf;
10104     int i;
10105
10106     clock = time((time_t *)NULL);
10107     tm = localtime(&clock);
10108
10109     while(*p++ = *template++) if(p[-1] == '%') {
10110         switch(*template++) {
10111           case 0:   *p = 0; return buf;
10112           case 'Y': i = tm->tm_year+1900; break;
10113           case 'y': i = tm->tm_year-100; break;
10114           case 'M': i = tm->tm_mon+1; break;
10115           case 'd': i = tm->tm_mday; break;
10116           case 'h': i = tm->tm_hour; break;
10117           case 'm': i = tm->tm_min; break;
10118           case 's': i = tm->tm_sec; break;
10119           default:  i = 0;
10120         }
10121         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10122     }
10123     return buf;
10124 }
10125
10126 int
10127 CountPlayers (char *p)
10128 {
10129     int n = 0;
10130     while(p = strchr(p, '\n')) p++, n++; // count participants
10131     return n;
10132 }
10133
10134 FILE *
10135 WriteTourneyFile (char *results, FILE *f)
10136 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10137     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10138     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10139         // create a file with tournament description
10140         fprintf(f, "-participants {%s}\n", appData.participants);
10141         fprintf(f, "-seedBase %d\n", appData.seedBase);
10142         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10143         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10144         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10145         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10146         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10147         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10148         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10149         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10150         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10151         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10152         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10153         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10154         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10155         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10156         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10157         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10158         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10159         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10160         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10161         fprintf(f, "-smpCores %d\n", appData.smpCores);
10162         if(searchTime > 0)
10163                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10164         else {
10165                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10166                 fprintf(f, "-tc %s\n", appData.timeControl);
10167                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10168         }
10169         fprintf(f, "-results \"%s\"\n", results);
10170     }
10171     return f;
10172 }
10173
10174 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10175
10176 void
10177 Substitute (char *participants, int expunge)
10178 {
10179     int i, changed, changes=0, nPlayers=0;
10180     char *p, *q, *r, buf[MSG_SIZ];
10181     if(participants == NULL) return;
10182     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10183     r = p = participants; q = appData.participants;
10184     while(*p && *p == *q) {
10185         if(*p == '\n') r = p+1, nPlayers++;
10186         p++; q++;
10187     }
10188     if(*p) { // difference
10189         while(*p && *p++ != '\n');
10190         while(*q && *q++ != '\n');
10191       changed = nPlayers;
10192         changes = 1 + (strcmp(p, q) != 0);
10193     }
10194     if(changes == 1) { // a single engine mnemonic was changed
10195         q = r; while(*q) nPlayers += (*q++ == '\n');
10196         p = buf; while(*r && (*p = *r++) != '\n') p++;
10197         *p = NULLCHAR;
10198         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10199         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10200         if(mnemonic[i]) { // The substitute is valid
10201             FILE *f;
10202             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10203                 flock(fileno(f), LOCK_EX);
10204                 ParseArgsFromFile(f);
10205                 fseek(f, 0, SEEK_SET);
10206                 FREE(appData.participants); appData.participants = participants;
10207                 if(expunge) { // erase results of replaced engine
10208                     int len = strlen(appData.results), w, b, dummy;
10209                     for(i=0; i<len; i++) {
10210                         Pairing(i, nPlayers, &w, &b, &dummy);
10211                         if((w == changed || b == changed) && appData.results[i] == '*') {
10212                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10213                             fclose(f);
10214                             return;
10215                         }
10216                     }
10217                     for(i=0; i<len; i++) {
10218                         Pairing(i, nPlayers, &w, &b, &dummy);
10219                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10220                     }
10221                 }
10222                 WriteTourneyFile(appData.results, f);
10223                 fclose(f); // release lock
10224                 return;
10225             }
10226         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10227     }
10228     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10229     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10230     free(participants);
10231     return;
10232 }
10233
10234 int
10235 CheckPlayers (char *participants)
10236 {
10237         int i;
10238         char buf[MSG_SIZ], *p;
10239         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10240         while(p = strchr(participants, '\n')) {
10241             *p = NULLCHAR;
10242             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10243             if(!mnemonic[i]) {
10244                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10245                 *p = '\n';
10246                 DisplayError(buf, 0);
10247                 return 1;
10248             }
10249             *p = '\n';
10250             participants = p + 1;
10251         }
10252         return 0;
10253 }
10254
10255 int
10256 CreateTourney (char *name)
10257 {
10258         FILE *f;
10259         if(matchMode && strcmp(name, appData.tourneyFile)) {
10260              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10261         }
10262         if(name[0] == NULLCHAR) {
10263             if(appData.participants[0])
10264                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10265             return 0;
10266         }
10267         f = fopen(name, "r");
10268         if(f) { // file exists
10269             ASSIGN(appData.tourneyFile, name);
10270             ParseArgsFromFile(f); // parse it
10271         } else {
10272             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10273             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10274                 DisplayError(_("Not enough participants"), 0);
10275                 return 0;
10276             }
10277             if(CheckPlayers(appData.participants)) return 0;
10278             ASSIGN(appData.tourneyFile, name);
10279             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10280             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10281         }
10282         fclose(f);
10283         appData.noChessProgram = FALSE;
10284         appData.clockMode = TRUE;
10285         SetGNUMode();
10286         return 1;
10287 }
10288
10289 int
10290 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10291 {
10292     char buf[MSG_SIZ], *p, *q;
10293     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10294     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10295     skip = !all && group[0]; // if group requested, we start in skip mode
10296     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10297         p = names; q = buf; header = 0;
10298         while(*p && *p != '\n') *q++ = *p++;
10299         *q = 0;
10300         if(*p == '\n') p++;
10301         if(buf[0] == '#') {
10302             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10303             depth++; // we must be entering a new group
10304             if(all) continue; // suppress printing group headers when complete list requested
10305             header = 1;
10306             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10307         }
10308         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10309         if(engineList[i]) free(engineList[i]);
10310         engineList[i] = strdup(buf);
10311         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10312         if(engineMnemonic[i]) free(engineMnemonic[i]);
10313         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10314             strcat(buf, " (");
10315             sscanf(q + 8, "%s", buf + strlen(buf));
10316             strcat(buf, ")");
10317         }
10318         engineMnemonic[i] = strdup(buf);
10319         i++;
10320     }
10321     engineList[i] = engineMnemonic[i] = NULL;
10322     return i;
10323 }
10324
10325 // following implemented as macro to avoid type limitations
10326 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10327
10328 void
10329 SwapEngines (int n)
10330 {   // swap settings for first engine and other engine (so far only some selected options)
10331     int h;
10332     char *p;
10333     if(n == 0) return;
10334     SWAP(directory, p)
10335     SWAP(chessProgram, p)
10336     SWAP(isUCI, h)
10337     SWAP(hasOwnBookUCI, h)
10338     SWAP(protocolVersion, h)
10339     SWAP(reuse, h)
10340     SWAP(scoreIsAbsolute, h)
10341     SWAP(timeOdds, h)
10342     SWAP(logo, p)
10343     SWAP(pgnName, p)
10344     SWAP(pvSAN, h)
10345     SWAP(engOptions, p)
10346     SWAP(engInitString, p)
10347     SWAP(computerString, p)
10348     SWAP(features, p)
10349     SWAP(fenOverride, p)
10350     SWAP(NPS, h)
10351     SWAP(accumulateTC, h)
10352     SWAP(host, p)
10353 }
10354
10355 int
10356 GetEngineLine (char *s, int n)
10357 {
10358     int i;
10359     char buf[MSG_SIZ];
10360     extern char *icsNames;
10361     if(!s || !*s) return 0;
10362     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10363     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10364     if(!mnemonic[i]) return 0;
10365     if(n == 11) return 1; // just testing if there was a match
10366     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10367     if(n == 1) SwapEngines(n);
10368     ParseArgsFromString(buf);
10369     if(n == 1) SwapEngines(n);
10370     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10371         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10372         ParseArgsFromString(buf);
10373     }
10374     return 1;
10375 }
10376
10377 int
10378 SetPlayer (int player, char *p)
10379 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10380     int i;
10381     char buf[MSG_SIZ], *engineName;
10382     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10383     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10384     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10385     if(mnemonic[i]) {
10386         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10387         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10388         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10389         ParseArgsFromString(buf);
10390     } else { // no engine with this nickname is installed!
10391         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10392         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10393         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10394         ModeHighlight();
10395         DisplayError(buf, 0);
10396         return 0;
10397     }
10398     free(engineName);
10399     return i;
10400 }
10401
10402 char *recentEngines;
10403
10404 void
10405 RecentEngineEvent (int nr)
10406 {
10407     int n;
10408 //    SwapEngines(1); // bump first to second
10409 //    ReplaceEngine(&second, 1); // and load it there
10410     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10411     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10412     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10413         ReplaceEngine(&first, 0);
10414         FloatToFront(&appData.recentEngineList, command[n]);
10415     }
10416 }
10417
10418 int
10419 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10420 {   // determine players from game number
10421     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10422
10423     if(appData.tourneyType == 0) {
10424         roundsPerCycle = (nPlayers - 1) | 1;
10425         pairingsPerRound = nPlayers / 2;
10426     } else if(appData.tourneyType > 0) {
10427         roundsPerCycle = nPlayers - appData.tourneyType;
10428         pairingsPerRound = appData.tourneyType;
10429     }
10430     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10431     gamesPerCycle = gamesPerRound * roundsPerCycle;
10432     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10433     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10434     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10435     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10436     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10437     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10438
10439     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10440     if(appData.roundSync) *syncInterval = gamesPerRound;
10441
10442     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10443
10444     if(appData.tourneyType == 0) {
10445         if(curPairing == (nPlayers-1)/2 ) {
10446             *whitePlayer = curRound;
10447             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10448         } else {
10449             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10450             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10451             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10452             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10453         }
10454     } else if(appData.tourneyType > 1) {
10455         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10456         *whitePlayer = curRound + appData.tourneyType;
10457     } else if(appData.tourneyType > 0) {
10458         *whitePlayer = curPairing;
10459         *blackPlayer = curRound + appData.tourneyType;
10460     }
10461
10462     // take care of white/black alternation per round.
10463     // For cycles and games this is already taken care of by default, derived from matchGame!
10464     return curRound & 1;
10465 }
10466
10467 int
10468 NextTourneyGame (int nr, int *swapColors)
10469 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10470     char *p, *q;
10471     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10472     FILE *tf;
10473     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10474     tf = fopen(appData.tourneyFile, "r");
10475     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10476     ParseArgsFromFile(tf); fclose(tf);
10477     InitTimeControls(); // TC might be altered from tourney file
10478
10479     nPlayers = CountPlayers(appData.participants); // count participants
10480     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10481     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10482
10483     if(syncInterval) {
10484         p = q = appData.results;
10485         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10486         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10487             DisplayMessage(_("Waiting for other game(s)"),"");
10488             waitingForGame = TRUE;
10489             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10490             return 0;
10491         }
10492         waitingForGame = FALSE;
10493     }
10494
10495     if(appData.tourneyType < 0) {
10496         if(nr>=0 && !pairingReceived) {
10497             char buf[1<<16];
10498             if(pairing.pr == NoProc) {
10499                 if(!appData.pairingEngine[0]) {
10500                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10501                     return 0;
10502                 }
10503                 StartChessProgram(&pairing); // starts the pairing engine
10504             }
10505             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10506             SendToProgram(buf, &pairing);
10507             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10508             SendToProgram(buf, &pairing);
10509             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10510         }
10511         pairingReceived = 0;                              // ... so we continue here
10512         *swapColors = 0;
10513         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10514         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10515         matchGame = 1; roundNr = nr / syncInterval + 1;
10516     }
10517
10518     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10519
10520     // redefine engines, engine dir, etc.
10521     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10522     if(first.pr == NoProc) {
10523       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10524       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10525     }
10526     if(second.pr == NoProc) {
10527       SwapEngines(1);
10528       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10529       SwapEngines(1);         // and make that valid for second engine by swapping
10530       InitEngine(&second, 1);
10531     }
10532     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10533     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10534     return OK;
10535 }
10536
10537 void
10538 NextMatchGame ()
10539 {   // performs game initialization that does not invoke engines, and then tries to start the game
10540     int res, firstWhite, swapColors = 0;
10541     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10542     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
10543         char buf[MSG_SIZ];
10544         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10545         if(strcmp(buf, currentDebugFile)) { // name has changed
10546             FILE *f = fopen(buf, "w");
10547             if(f) { // if opening the new file failed, just keep using the old one
10548                 ASSIGN(currentDebugFile, buf);
10549                 fclose(debugFP);
10550                 debugFP = f;
10551             }
10552             if(appData.serverFileName) {
10553                 if(serverFP) fclose(serverFP);
10554                 serverFP = fopen(appData.serverFileName, "w");
10555                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10556                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10557             }
10558         }
10559     }
10560     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10561     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10562     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10563     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10564     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10565     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10566     Reset(FALSE, first.pr != NoProc);
10567     res = LoadGameOrPosition(matchGame); // setup game
10568     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10569     if(!res) return; // abort when bad game/pos file
10570     TwoMachinesEvent();
10571 }
10572
10573 void
10574 UserAdjudicationEvent (int result)
10575 {
10576     ChessMove gameResult = GameIsDrawn;
10577
10578     if( result > 0 ) {
10579         gameResult = WhiteWins;
10580     }
10581     else if( result < 0 ) {
10582         gameResult = BlackWins;
10583     }
10584
10585     if( gameMode == TwoMachinesPlay ) {
10586         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10587     }
10588 }
10589
10590
10591 // [HGM] save: calculate checksum of game to make games easily identifiable
10592 int
10593 StringCheckSum (char *s)
10594 {
10595         int i = 0;
10596         if(s==NULL) return 0;
10597         while(*s) i = i*259 + *s++;
10598         return i;
10599 }
10600
10601 int
10602 GameCheckSum ()
10603 {
10604         int i, sum=0;
10605         for(i=backwardMostMove; i<forwardMostMove; i++) {
10606                 sum += pvInfoList[i].depth;
10607                 sum += StringCheckSum(parseList[i]);
10608                 sum += StringCheckSum(commentList[i]);
10609                 sum *= 261;
10610         }
10611         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10612         return sum + StringCheckSum(commentList[i]);
10613 } // end of save patch
10614
10615 void
10616 GameEnds (ChessMove result, char *resultDetails, int whosays)
10617 {
10618     GameMode nextGameMode;
10619     int isIcsGame;
10620     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10621
10622     if(endingGame) return; /* [HGM] crash: forbid recursion */
10623     endingGame = 1;
10624     if(twoBoards) { // [HGM] dual: switch back to one board
10625         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10626         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10627     }
10628     if (appData.debugMode) {
10629       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10630               result, resultDetails ? resultDetails : "(null)", whosays);
10631     }
10632
10633     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10634
10635     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10636
10637     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10638         /* If we are playing on ICS, the server decides when the
10639            game is over, but the engine can offer to draw, claim
10640            a draw, or resign.
10641          */
10642 #if ZIPPY
10643         if (appData.zippyPlay && first.initDone) {
10644             if (result == GameIsDrawn) {
10645                 /* In case draw still needs to be claimed */
10646                 SendToICS(ics_prefix);
10647                 SendToICS("draw\n");
10648             } else if (StrCaseStr(resultDetails, "resign")) {
10649                 SendToICS(ics_prefix);
10650                 SendToICS("resign\n");
10651             }
10652         }
10653 #endif
10654         endingGame = 0; /* [HGM] crash */
10655         return;
10656     }
10657
10658     /* If we're loading the game from a file, stop */
10659     if (whosays == GE_FILE) {
10660       (void) StopLoadGameTimer();
10661       gameFileFP = NULL;
10662     }
10663
10664     /* Cancel draw offers */
10665     first.offeredDraw = second.offeredDraw = 0;
10666
10667     /* If this is an ICS game, only ICS can really say it's done;
10668        if not, anyone can. */
10669     isIcsGame = (gameMode == IcsPlayingWhite ||
10670                  gameMode == IcsPlayingBlack ||
10671                  gameMode == IcsObserving    ||
10672                  gameMode == IcsExamining);
10673
10674     if (!isIcsGame || whosays == GE_ICS) {
10675         /* OK -- not an ICS game, or ICS said it was done */
10676         StopClocks();
10677         if (!isIcsGame && !appData.noChessProgram)
10678           SetUserThinkingEnables();
10679
10680         /* [HGM] if a machine claims the game end we verify this claim */
10681         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10682             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10683                 char claimer;
10684                 ChessMove trueResult = (ChessMove) -1;
10685
10686                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10687                                             first.twoMachinesColor[0] :
10688                                             second.twoMachinesColor[0] ;
10689
10690                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10691                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10692                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10693                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10694                 } else
10695                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10696                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10697                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10698                 } else
10699                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10700                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10701                 }
10702
10703                 // now verify win claims, but not in drop games, as we don't understand those yet
10704                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10705                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10706                     (result == WhiteWins && claimer == 'w' ||
10707                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10708                       if (appData.debugMode) {
10709                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10710                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10711                       }
10712                       if(result != trueResult) {
10713                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10714                               result = claimer == 'w' ? BlackWins : WhiteWins;
10715                               resultDetails = buf;
10716                       }
10717                 } else
10718                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10719                     && (forwardMostMove <= backwardMostMove ||
10720                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10721                         (claimer=='b')==(forwardMostMove&1))
10722                                                                                   ) {
10723                       /* [HGM] verify: draws that were not flagged are false claims */
10724                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10725                       result = claimer == 'w' ? BlackWins : WhiteWins;
10726                       resultDetails = buf;
10727                 }
10728                 /* (Claiming a loss is accepted no questions asked!) */
10729             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10730                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10731                 result = GameUnfinished;
10732                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10733             }
10734             /* [HGM] bare: don't allow bare King to win */
10735             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10736                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10737                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10738                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10739                && result != GameIsDrawn)
10740             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10741                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10742                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10743                         if(p >= 0 && p <= (int)WhiteKing) k++;
10744                 }
10745                 if (appData.debugMode) {
10746                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10747                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10748                 }
10749                 if(k <= 1) {
10750                         result = GameIsDrawn;
10751                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10752                         resultDetails = buf;
10753                 }
10754             }
10755         }
10756
10757
10758         if(serverMoves != NULL && !loadFlag) { char c = '=';
10759             if(result==WhiteWins) c = '+';
10760             if(result==BlackWins) c = '-';
10761             if(resultDetails != NULL)
10762                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10763         }
10764         if (resultDetails != NULL) {
10765             gameInfo.result = result;
10766             gameInfo.resultDetails = StrSave(resultDetails);
10767
10768             /* display last move only if game was not loaded from file */
10769             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10770                 DisplayMove(currentMove - 1);
10771
10772             if (forwardMostMove != 0) {
10773                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10774                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10775                                                                 ) {
10776                     if (*appData.saveGameFile != NULLCHAR) {
10777                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10778                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10779                         else
10780                         SaveGameToFile(appData.saveGameFile, TRUE);
10781                     } else if (appData.autoSaveGames) {
10782                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
10783                     }
10784                     if (*appData.savePositionFile != NULLCHAR) {
10785                         SavePositionToFile(appData.savePositionFile);
10786                     }
10787                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10788                 }
10789             }
10790
10791             /* Tell program how game ended in case it is learning */
10792             /* [HGM] Moved this to after saving the PGN, just in case */
10793             /* engine died and we got here through time loss. In that */
10794             /* case we will get a fatal error writing the pipe, which */
10795             /* would otherwise lose us the PGN.                       */
10796             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10797             /* output during GameEnds should never be fatal anymore   */
10798             if (gameMode == MachinePlaysWhite ||
10799                 gameMode == MachinePlaysBlack ||
10800                 gameMode == TwoMachinesPlay ||
10801                 gameMode == IcsPlayingWhite ||
10802                 gameMode == IcsPlayingBlack ||
10803                 gameMode == BeginningOfGame) {
10804                 char buf[MSG_SIZ];
10805                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10806                         resultDetails);
10807                 if (first.pr != NoProc) {
10808                     SendToProgram(buf, &first);
10809                 }
10810                 if (second.pr != NoProc &&
10811                     gameMode == TwoMachinesPlay) {
10812                     SendToProgram(buf, &second);
10813                 }
10814             }
10815         }
10816
10817         if (appData.icsActive) {
10818             if (appData.quietPlay &&
10819                 (gameMode == IcsPlayingWhite ||
10820                  gameMode == IcsPlayingBlack)) {
10821                 SendToICS(ics_prefix);
10822                 SendToICS("set shout 1\n");
10823             }
10824             nextGameMode = IcsIdle;
10825             ics_user_moved = FALSE;
10826             /* clean up premove.  It's ugly when the game has ended and the
10827              * premove highlights are still on the board.
10828              */
10829             if (gotPremove) {
10830               gotPremove = FALSE;
10831               ClearPremoveHighlights();
10832               DrawPosition(FALSE, boards[currentMove]);
10833             }
10834             if (whosays == GE_ICS) {
10835                 switch (result) {
10836                 case WhiteWins:
10837                     if (gameMode == IcsPlayingWhite)
10838                         PlayIcsWinSound();
10839                     else if(gameMode == IcsPlayingBlack)
10840                         PlayIcsLossSound();
10841                     break;
10842                 case BlackWins:
10843                     if (gameMode == IcsPlayingBlack)
10844                         PlayIcsWinSound();
10845                     else if(gameMode == IcsPlayingWhite)
10846                         PlayIcsLossSound();
10847                     break;
10848                 case GameIsDrawn:
10849                     PlayIcsDrawSound();
10850                     break;
10851                 default:
10852                     PlayIcsUnfinishedSound();
10853                 }
10854             }
10855         } else if (gameMode == EditGame ||
10856                    gameMode == PlayFromGameFile ||
10857                    gameMode == AnalyzeMode ||
10858                    gameMode == AnalyzeFile) {
10859             nextGameMode = gameMode;
10860         } else {
10861             nextGameMode = EndOfGame;
10862         }
10863         pausing = FALSE;
10864         ModeHighlight();
10865     } else {
10866         nextGameMode = gameMode;
10867     }
10868
10869     if (appData.noChessProgram) {
10870         gameMode = nextGameMode;
10871         ModeHighlight();
10872         endingGame = 0; /* [HGM] crash */
10873         return;
10874     }
10875
10876     if (first.reuse) {
10877         /* Put first chess program into idle state */
10878         if (first.pr != NoProc &&
10879             (gameMode == MachinePlaysWhite ||
10880              gameMode == MachinePlaysBlack ||
10881              gameMode == TwoMachinesPlay ||
10882              gameMode == IcsPlayingWhite ||
10883              gameMode == IcsPlayingBlack ||
10884              gameMode == BeginningOfGame)) {
10885             SendToProgram("force\n", &first);
10886             if (first.usePing) {
10887               char buf[MSG_SIZ];
10888               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10889               SendToProgram(buf, &first);
10890             }
10891         }
10892     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10893         /* Kill off first chess program */
10894         if (first.isr != NULL)
10895           RemoveInputSource(first.isr);
10896         first.isr = NULL;
10897
10898         if (first.pr != NoProc) {
10899             ExitAnalyzeMode();
10900             DoSleep( appData.delayBeforeQuit );
10901             SendToProgram("quit\n", &first);
10902             DoSleep( appData.delayAfterQuit );
10903             DestroyChildProcess(first.pr, first.useSigterm);
10904             first.reload = TRUE;
10905         }
10906         first.pr = NoProc;
10907     }
10908     if (second.reuse) {
10909         /* Put second chess program into idle state */
10910         if (second.pr != NoProc &&
10911             gameMode == TwoMachinesPlay) {
10912             SendToProgram("force\n", &second);
10913             if (second.usePing) {
10914               char buf[MSG_SIZ];
10915               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10916               SendToProgram(buf, &second);
10917             }
10918         }
10919     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10920         /* Kill off second chess program */
10921         if (second.isr != NULL)
10922           RemoveInputSource(second.isr);
10923         second.isr = NULL;
10924
10925         if (second.pr != NoProc) {
10926             DoSleep( appData.delayBeforeQuit );
10927             SendToProgram("quit\n", &second);
10928             DoSleep( appData.delayAfterQuit );
10929             DestroyChildProcess(second.pr, second.useSigterm);
10930             second.reload = TRUE;
10931         }
10932         second.pr = NoProc;
10933     }
10934
10935     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
10936         char resChar = '=';
10937         switch (result) {
10938         case WhiteWins:
10939           resChar = '+';
10940           if (first.twoMachinesColor[0] == 'w') {
10941             first.matchWins++;
10942           } else {
10943             second.matchWins++;
10944           }
10945           break;
10946         case BlackWins:
10947           resChar = '-';
10948           if (first.twoMachinesColor[0] == 'b') {
10949             first.matchWins++;
10950           } else {
10951             second.matchWins++;
10952           }
10953           break;
10954         case GameUnfinished:
10955           resChar = ' ';
10956         default:
10957           break;
10958         }
10959
10960         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10961         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10962             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10963             ReserveGame(nextGame, resChar); // sets nextGame
10964             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10965             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10966         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10967
10968         if (nextGame <= appData.matchGames && !abortMatch) {
10969             gameMode = nextGameMode;
10970             matchGame = nextGame; // this will be overruled in tourney mode!
10971             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10972             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10973             endingGame = 0; /* [HGM] crash */
10974             return;
10975         } else {
10976             gameMode = nextGameMode;
10977             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10978                      first.tidy, second.tidy,
10979                      first.matchWins, second.matchWins,
10980                      appData.matchGames - (first.matchWins + second.matchWins));
10981             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10982             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10983             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10984             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10985                 first.twoMachinesColor = "black\n";
10986                 second.twoMachinesColor = "white\n";
10987             } else {
10988                 first.twoMachinesColor = "white\n";
10989                 second.twoMachinesColor = "black\n";
10990             }
10991         }
10992     }
10993     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10994         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10995       ExitAnalyzeMode();
10996     gameMode = nextGameMode;
10997     ModeHighlight();
10998     endingGame = 0;  /* [HGM] crash */
10999     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11000         if(matchMode == TRUE) { // match through command line: exit with or without popup
11001             if(ranking) {
11002                 ToNrEvent(forwardMostMove);
11003                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11004                 else ExitEvent(0);
11005             } else DisplayFatalError(buf, 0, 0);
11006         } else { // match through menu; just stop, with or without popup
11007             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11008             ModeHighlight();
11009             if(ranking){
11010                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11011             } else DisplayNote(buf);
11012       }
11013       if(ranking) free(ranking);
11014     }
11015 }
11016
11017 /* Assumes program was just initialized (initString sent).
11018    Leaves program in force mode. */
11019 void
11020 FeedMovesToProgram (ChessProgramState *cps, int upto)
11021 {
11022     int i;
11023
11024     if (appData.debugMode)
11025       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11026               startedFromSetupPosition ? "position and " : "",
11027               backwardMostMove, upto, cps->which);
11028     if(currentlyInitializedVariant != gameInfo.variant) {
11029       char buf[MSG_SIZ];
11030         // [HGM] variantswitch: make engine aware of new variant
11031         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11032                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11033         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11034         SendToProgram(buf, cps);
11035         currentlyInitializedVariant = gameInfo.variant;
11036     }
11037     SendToProgram("force\n", cps);
11038     if (startedFromSetupPosition) {
11039         SendBoard(cps, backwardMostMove);
11040     if (appData.debugMode) {
11041         fprintf(debugFP, "feedMoves\n");
11042     }
11043     }
11044     for (i = backwardMostMove; i < upto; i++) {
11045         SendMoveToProgram(i, cps);
11046     }
11047 }
11048
11049
11050 int
11051 ResurrectChessProgram ()
11052 {
11053      /* The chess program may have exited.
11054         If so, restart it and feed it all the moves made so far. */
11055     static int doInit = 0;
11056
11057     if (appData.noChessProgram) return 1;
11058
11059     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11060         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11061         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11062         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11063     } else {
11064         if (first.pr != NoProc) return 1;
11065         StartChessProgram(&first);
11066     }
11067     InitChessProgram(&first, FALSE);
11068     FeedMovesToProgram(&first, currentMove);
11069
11070     if (!first.sendTime) {
11071         /* can't tell gnuchess what its clock should read,
11072            so we bow to its notion. */
11073         ResetClocks();
11074         timeRemaining[0][currentMove] = whiteTimeRemaining;
11075         timeRemaining[1][currentMove] = blackTimeRemaining;
11076     }
11077
11078     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11079                 appData.icsEngineAnalyze) && first.analysisSupport) {
11080       SendToProgram("analyze\n", &first);
11081       first.analyzing = TRUE;
11082     }
11083     return 1;
11084 }
11085
11086 /*
11087  * Button procedures
11088  */
11089 void
11090 Reset (int redraw, int init)
11091 {
11092     int i;
11093
11094     if (appData.debugMode) {
11095         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11096                 redraw, init, gameMode);
11097     }
11098     CleanupTail(); // [HGM] vari: delete any stored variations
11099     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11100     pausing = pauseExamInvalid = FALSE;
11101     startedFromSetupPosition = blackPlaysFirst = FALSE;
11102     firstMove = TRUE;
11103     whiteFlag = blackFlag = FALSE;
11104     userOfferedDraw = FALSE;
11105     hintRequested = bookRequested = FALSE;
11106     first.maybeThinking = FALSE;
11107     second.maybeThinking = FALSE;
11108     first.bookSuspend = FALSE; // [HGM] book
11109     second.bookSuspend = FALSE;
11110     thinkOutput[0] = NULLCHAR;
11111     lastHint[0] = NULLCHAR;
11112     ClearGameInfo(&gameInfo);
11113     gameInfo.variant = StringToVariant(appData.variant);
11114     ics_user_moved = ics_clock_paused = FALSE;
11115     ics_getting_history = H_FALSE;
11116     ics_gamenum = -1;
11117     white_holding[0] = black_holding[0] = NULLCHAR;
11118     ClearProgramStats();
11119     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11120
11121     ResetFrontEnd();
11122     ClearHighlights();
11123     flipView = appData.flipView;
11124     ClearPremoveHighlights();
11125     gotPremove = FALSE;
11126     alarmSounded = FALSE;
11127
11128     GameEnds(EndOfFile, NULL, GE_PLAYER);
11129     if(appData.serverMovesName != NULL) {
11130         /* [HGM] prepare to make moves file for broadcasting */
11131         clock_t t = clock();
11132         if(serverMoves != NULL) fclose(serverMoves);
11133         serverMoves = fopen(appData.serverMovesName, "r");
11134         if(serverMoves != NULL) {
11135             fclose(serverMoves);
11136             /* delay 15 sec before overwriting, so all clients can see end */
11137             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11138         }
11139         serverMoves = fopen(appData.serverMovesName, "w");
11140     }
11141
11142     ExitAnalyzeMode();
11143     gameMode = BeginningOfGame;
11144     ModeHighlight();
11145     if(appData.icsActive) gameInfo.variant = VariantNormal;
11146     currentMove = forwardMostMove = backwardMostMove = 0;
11147     MarkTargetSquares(1);
11148     InitPosition(redraw);
11149     for (i = 0; i < MAX_MOVES; i++) {
11150         if (commentList[i] != NULL) {
11151             free(commentList[i]);
11152             commentList[i] = NULL;
11153         }
11154     }
11155     ResetClocks();
11156     timeRemaining[0][0] = whiteTimeRemaining;
11157     timeRemaining[1][0] = blackTimeRemaining;
11158
11159     if (first.pr == NoProc) {
11160         StartChessProgram(&first);
11161     }
11162     if (init) {
11163             InitChessProgram(&first, startedFromSetupPosition);
11164     }
11165     DisplayTitle("");
11166     DisplayMessage("", "");
11167     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11168     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11169     ClearMap();        // [HGM] exclude: invalidate map
11170 }
11171
11172 void
11173 AutoPlayGameLoop ()
11174 {
11175     for (;;) {
11176         if (!AutoPlayOneMove())
11177           return;
11178         if (matchMode || appData.timeDelay == 0)
11179           continue;
11180         if (appData.timeDelay < 0)
11181           return;
11182         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11183         break;
11184     }
11185 }
11186
11187 void
11188 AnalyzeNextGame()
11189 {
11190     ReloadGame(1); // next game
11191 }
11192
11193 int
11194 AutoPlayOneMove ()
11195 {
11196     int fromX, fromY, toX, toY;
11197
11198     if (appData.debugMode) {
11199       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11200     }
11201
11202     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11203       return FALSE;
11204
11205     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11206       pvInfoList[currentMove].depth = programStats.depth;
11207       pvInfoList[currentMove].score = programStats.score;
11208       pvInfoList[currentMove].time  = 0;
11209       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11210       else { // append analysis of final position as comment
11211         char buf[MSG_SIZ];
11212         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11213         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11214       }
11215       programStats.depth = 0;
11216     }
11217
11218     if (currentMove >= forwardMostMove) {
11219       if(gameMode == AnalyzeFile) {
11220           if(appData.loadGameIndex == -1) {
11221             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11222           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11223           } else {
11224           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11225         }
11226       }
11227 //      gameMode = EndOfGame;
11228 //      ModeHighlight();
11229
11230       /* [AS] Clear current move marker at the end of a game */
11231       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11232
11233       return FALSE;
11234     }
11235
11236     toX = moveList[currentMove][2] - AAA;
11237     toY = moveList[currentMove][3] - ONE;
11238
11239     if (moveList[currentMove][1] == '@') {
11240         if (appData.highlightLastMove) {
11241             SetHighlights(-1, -1, toX, toY);
11242         }
11243     } else {
11244         fromX = moveList[currentMove][0] - AAA;
11245         fromY = moveList[currentMove][1] - ONE;
11246
11247         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11248
11249         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11250
11251         if (appData.highlightLastMove) {
11252             SetHighlights(fromX, fromY, toX, toY);
11253         }
11254     }
11255     DisplayMove(currentMove);
11256     SendMoveToProgram(currentMove++, &first);
11257     DisplayBothClocks();
11258     DrawPosition(FALSE, boards[currentMove]);
11259     // [HGM] PV info: always display, routine tests if empty
11260     DisplayComment(currentMove - 1, commentList[currentMove]);
11261     return TRUE;
11262 }
11263
11264
11265 int
11266 LoadGameOneMove (ChessMove readAhead)
11267 {
11268     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11269     char promoChar = NULLCHAR;
11270     ChessMove moveType;
11271     char move[MSG_SIZ];
11272     char *p, *q;
11273
11274     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11275         gameMode != AnalyzeMode && gameMode != Training) {
11276         gameFileFP = NULL;
11277         return FALSE;
11278     }
11279
11280     yyboardindex = forwardMostMove;
11281     if (readAhead != EndOfFile) {
11282       moveType = readAhead;
11283     } else {
11284       if (gameFileFP == NULL)
11285           return FALSE;
11286       moveType = (ChessMove) Myylex();
11287     }
11288
11289     done = FALSE;
11290     switch (moveType) {
11291       case Comment:
11292         if (appData.debugMode)
11293           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11294         p = yy_text;
11295
11296         /* append the comment but don't display it */
11297         AppendComment(currentMove, p, FALSE);
11298         return TRUE;
11299
11300       case WhiteCapturesEnPassant:
11301       case BlackCapturesEnPassant:
11302       case WhitePromotion:
11303       case BlackPromotion:
11304       case WhiteNonPromotion:
11305       case BlackNonPromotion:
11306       case NormalMove:
11307       case WhiteKingSideCastle:
11308       case WhiteQueenSideCastle:
11309       case BlackKingSideCastle:
11310       case BlackQueenSideCastle:
11311       case WhiteKingSideCastleWild:
11312       case WhiteQueenSideCastleWild:
11313       case BlackKingSideCastleWild:
11314       case BlackQueenSideCastleWild:
11315       /* PUSH Fabien */
11316       case WhiteHSideCastleFR:
11317       case WhiteASideCastleFR:
11318       case BlackHSideCastleFR:
11319       case BlackASideCastleFR:
11320       /* POP Fabien */
11321         if (appData.debugMode)
11322           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11323         fromX = currentMoveString[0] - AAA;
11324         fromY = currentMoveString[1] - ONE;
11325         toX = currentMoveString[2] - AAA;
11326         toY = currentMoveString[3] - ONE;
11327         promoChar = currentMoveString[4];
11328         break;
11329
11330       case WhiteDrop:
11331       case BlackDrop:
11332         if (appData.debugMode)
11333           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11334         fromX = moveType == WhiteDrop ?
11335           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11336         (int) CharToPiece(ToLower(currentMoveString[0]));
11337         fromY = DROP_RANK;
11338         toX = currentMoveString[2] - AAA;
11339         toY = currentMoveString[3] - ONE;
11340         break;
11341
11342       case WhiteWins:
11343       case BlackWins:
11344       case GameIsDrawn:
11345       case GameUnfinished:
11346         if (appData.debugMode)
11347           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11348         p = strchr(yy_text, '{');
11349         if (p == NULL) p = strchr(yy_text, '(');
11350         if (p == NULL) {
11351             p = yy_text;
11352             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11353         } else {
11354             q = strchr(p, *p == '{' ? '}' : ')');
11355             if (q != NULL) *q = NULLCHAR;
11356             p++;
11357         }
11358         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11359         GameEnds(moveType, p, GE_FILE);
11360         done = TRUE;
11361         if (cmailMsgLoaded) {
11362             ClearHighlights();
11363             flipView = WhiteOnMove(currentMove);
11364             if (moveType == GameUnfinished) flipView = !flipView;
11365             if (appData.debugMode)
11366               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11367         }
11368         break;
11369
11370       case EndOfFile:
11371         if (appData.debugMode)
11372           fprintf(debugFP, "Parser hit end of file\n");
11373         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11374           case MT_NONE:
11375           case MT_CHECK:
11376             break;
11377           case MT_CHECKMATE:
11378           case MT_STAINMATE:
11379             if (WhiteOnMove(currentMove)) {
11380                 GameEnds(BlackWins, "Black mates", GE_FILE);
11381             } else {
11382                 GameEnds(WhiteWins, "White mates", GE_FILE);
11383             }
11384             break;
11385           case MT_STALEMATE:
11386             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11387             break;
11388         }
11389         done = TRUE;
11390         break;
11391
11392       case MoveNumberOne:
11393         if (lastLoadGameStart == GNUChessGame) {
11394             /* GNUChessGames have numbers, but they aren't move numbers */
11395             if (appData.debugMode)
11396               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11397                       yy_text, (int) moveType);
11398             return LoadGameOneMove(EndOfFile); /* tail recursion */
11399         }
11400         /* else fall thru */
11401
11402       case XBoardGame:
11403       case GNUChessGame:
11404       case PGNTag:
11405         /* Reached start of next game in file */
11406         if (appData.debugMode)
11407           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11408         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11409           case MT_NONE:
11410           case MT_CHECK:
11411             break;
11412           case MT_CHECKMATE:
11413           case MT_STAINMATE:
11414             if (WhiteOnMove(currentMove)) {
11415                 GameEnds(BlackWins, "Black mates", GE_FILE);
11416             } else {
11417                 GameEnds(WhiteWins, "White mates", GE_FILE);
11418             }
11419             break;
11420           case MT_STALEMATE:
11421             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11422             break;
11423         }
11424         done = TRUE;
11425         break;
11426
11427       case PositionDiagram:     /* should not happen; ignore */
11428       case ElapsedTime:         /* ignore */
11429       case NAG:                 /* ignore */
11430         if (appData.debugMode)
11431           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11432                   yy_text, (int) moveType);
11433         return LoadGameOneMove(EndOfFile); /* tail recursion */
11434
11435       case IllegalMove:
11436         if (appData.testLegality) {
11437             if (appData.debugMode)
11438               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11439             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11440                     (forwardMostMove / 2) + 1,
11441                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11442             DisplayError(move, 0);
11443             done = TRUE;
11444         } else {
11445             if (appData.debugMode)
11446               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11447                       yy_text, currentMoveString);
11448             fromX = currentMoveString[0] - AAA;
11449             fromY = currentMoveString[1] - ONE;
11450             toX = currentMoveString[2] - AAA;
11451             toY = currentMoveString[3] - ONE;
11452             promoChar = currentMoveString[4];
11453         }
11454         break;
11455
11456       case AmbiguousMove:
11457         if (appData.debugMode)
11458           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11459         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11460                 (forwardMostMove / 2) + 1,
11461                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11462         DisplayError(move, 0);
11463         done = TRUE;
11464         break;
11465
11466       default:
11467       case ImpossibleMove:
11468         if (appData.debugMode)
11469           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11470         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11471                 (forwardMostMove / 2) + 1,
11472                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11473         DisplayError(move, 0);
11474         done = TRUE;
11475         break;
11476     }
11477
11478     if (done) {
11479         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11480             DrawPosition(FALSE, boards[currentMove]);
11481             DisplayBothClocks();
11482             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11483               DisplayComment(currentMove - 1, commentList[currentMove]);
11484         }
11485         (void) StopLoadGameTimer();
11486         gameFileFP = NULL;
11487         cmailOldMove = forwardMostMove;
11488         return FALSE;
11489     } else {
11490         /* currentMoveString is set as a side-effect of yylex */
11491
11492         thinkOutput[0] = NULLCHAR;
11493         MakeMove(fromX, fromY, toX, toY, promoChar);
11494         currentMove = forwardMostMove;
11495         return TRUE;
11496     }
11497 }
11498
11499 /* Load the nth game from the given file */
11500 int
11501 LoadGameFromFile (char *filename, int n, char *title, int useList)
11502 {
11503     FILE *f;
11504     char buf[MSG_SIZ];
11505
11506     if (strcmp(filename, "-") == 0) {
11507         f = stdin;
11508         title = "stdin";
11509     } else {
11510         f = fopen(filename, "rb");
11511         if (f == NULL) {
11512           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11513             DisplayError(buf, errno);
11514             return FALSE;
11515         }
11516     }
11517     if (fseek(f, 0, 0) == -1) {
11518         /* f is not seekable; probably a pipe */
11519         useList = FALSE;
11520     }
11521     if (useList && n == 0) {
11522         int error = GameListBuild(f);
11523         if (error) {
11524             DisplayError(_("Cannot build game list"), error);
11525         } else if (!ListEmpty(&gameList) &&
11526                    ((ListGame *) gameList.tailPred)->number > 1) {
11527             GameListPopUp(f, title);
11528             return TRUE;
11529         }
11530         GameListDestroy();
11531         n = 1;
11532     }
11533     if (n == 0) n = 1;
11534     return LoadGame(f, n, title, FALSE);
11535 }
11536
11537
11538 void
11539 MakeRegisteredMove ()
11540 {
11541     int fromX, fromY, toX, toY;
11542     char promoChar;
11543     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11544         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11545           case CMAIL_MOVE:
11546           case CMAIL_DRAW:
11547             if (appData.debugMode)
11548               fprintf(debugFP, "Restoring %s for game %d\n",
11549                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11550
11551             thinkOutput[0] = NULLCHAR;
11552             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11553             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11554             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11555             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11556             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11557             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11558             MakeMove(fromX, fromY, toX, toY, promoChar);
11559             ShowMove(fromX, fromY, toX, toY);
11560
11561             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11562               case MT_NONE:
11563               case MT_CHECK:
11564                 break;
11565
11566               case MT_CHECKMATE:
11567               case MT_STAINMATE:
11568                 if (WhiteOnMove(currentMove)) {
11569                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11570                 } else {
11571                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11572                 }
11573                 break;
11574
11575               case MT_STALEMATE:
11576                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11577                 break;
11578             }
11579
11580             break;
11581
11582           case CMAIL_RESIGN:
11583             if (WhiteOnMove(currentMove)) {
11584                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11585             } else {
11586                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11587             }
11588             break;
11589
11590           case CMAIL_ACCEPT:
11591             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11592             break;
11593
11594           default:
11595             break;
11596         }
11597     }
11598
11599     return;
11600 }
11601
11602 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11603 int
11604 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11605 {
11606     int retVal;
11607
11608     if (gameNumber > nCmailGames) {
11609         DisplayError(_("No more games in this message"), 0);
11610         return FALSE;
11611     }
11612     if (f == lastLoadGameFP) {
11613         int offset = gameNumber - lastLoadGameNumber;
11614         if (offset == 0) {
11615             cmailMsg[0] = NULLCHAR;
11616             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11617                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11618                 nCmailMovesRegistered--;
11619             }
11620             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11621             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11622                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11623             }
11624         } else {
11625             if (! RegisterMove()) return FALSE;
11626         }
11627     }
11628
11629     retVal = LoadGame(f, gameNumber, title, useList);
11630
11631     /* Make move registered during previous look at this game, if any */
11632     MakeRegisteredMove();
11633
11634     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11635         commentList[currentMove]
11636           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11637         DisplayComment(currentMove - 1, commentList[currentMove]);
11638     }
11639
11640     return retVal;
11641 }
11642
11643 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11644 int
11645 ReloadGame (int offset)
11646 {
11647     int gameNumber = lastLoadGameNumber + offset;
11648     if (lastLoadGameFP == NULL) {
11649         DisplayError(_("No game has been loaded yet"), 0);
11650         return FALSE;
11651     }
11652     if (gameNumber <= 0) {
11653         DisplayError(_("Can't back up any further"), 0);
11654         return FALSE;
11655     }
11656     if (cmailMsgLoaded) {
11657         return CmailLoadGame(lastLoadGameFP, gameNumber,
11658                              lastLoadGameTitle, lastLoadGameUseList);
11659     } else {
11660         return LoadGame(lastLoadGameFP, gameNumber,
11661                         lastLoadGameTitle, lastLoadGameUseList);
11662     }
11663 }
11664
11665 int keys[EmptySquare+1];
11666
11667 int
11668 PositionMatches (Board b1, Board b2)
11669 {
11670     int r, f, sum=0;
11671     switch(appData.searchMode) {
11672         case 1: return CompareWithRights(b1, b2);
11673         case 2:
11674             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11675                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11676             }
11677             return TRUE;
11678         case 3:
11679             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11680               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11681                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11682             }
11683             return sum==0;
11684         case 4:
11685             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11686                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11687             }
11688             return sum==0;
11689     }
11690     return TRUE;
11691 }
11692
11693 #define Q_PROMO  4
11694 #define Q_EP     3
11695 #define Q_BCASTL 2
11696 #define Q_WCASTL 1
11697
11698 int pieceList[256], quickBoard[256];
11699 ChessSquare pieceType[256] = { EmptySquare };
11700 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11701 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11702 int soughtTotal, turn;
11703 Boolean epOK, flipSearch;
11704
11705 typedef struct {
11706     unsigned char piece, to;
11707 } Move;
11708
11709 #define DSIZE (250000)
11710
11711 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11712 Move *moveDatabase = initialSpace;
11713 unsigned int movePtr, dataSize = DSIZE;
11714
11715 int
11716 MakePieceList (Board board, int *counts)
11717 {
11718     int r, f, n=Q_PROMO, total=0;
11719     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11720     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11721         int sq = f + (r<<4);
11722         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11723             quickBoard[sq] = ++n;
11724             pieceList[n] = sq;
11725             pieceType[n] = board[r][f];
11726             counts[board[r][f]]++;
11727             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11728             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11729             total++;
11730         }
11731     }
11732     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11733     return total;
11734 }
11735
11736 void
11737 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11738 {
11739     int sq = fromX + (fromY<<4);
11740     int piece = quickBoard[sq];
11741     quickBoard[sq] = 0;
11742     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11743     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11744         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11745         moveDatabase[movePtr++].piece = Q_WCASTL;
11746         quickBoard[sq] = piece;
11747         piece = quickBoard[from]; quickBoard[from] = 0;
11748         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11749     } else
11750     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11751         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11752         moveDatabase[movePtr++].piece = Q_BCASTL;
11753         quickBoard[sq] = piece;
11754         piece = quickBoard[from]; quickBoard[from] = 0;
11755         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11756     } else
11757     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11758         quickBoard[(fromY<<4)+toX] = 0;
11759         moveDatabase[movePtr].piece = Q_EP;
11760         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11761         moveDatabase[movePtr].to = sq;
11762     } else
11763     if(promoPiece != pieceType[piece]) {
11764         moveDatabase[movePtr++].piece = Q_PROMO;
11765         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11766     }
11767     moveDatabase[movePtr].piece = piece;
11768     quickBoard[sq] = piece;
11769     movePtr++;
11770 }
11771
11772 int
11773 PackGame (Board board)
11774 {
11775     Move *newSpace = NULL;
11776     moveDatabase[movePtr].piece = 0; // terminate previous game
11777     if(movePtr > dataSize) {
11778         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11779         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11780         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11781         if(newSpace) {
11782             int i;
11783             Move *p = moveDatabase, *q = newSpace;
11784             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11785             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11786             moveDatabase = newSpace;
11787         } else { // calloc failed, we must be out of memory. Too bad...
11788             dataSize = 0; // prevent calloc events for all subsequent games
11789             return 0;     // and signal this one isn't cached
11790         }
11791     }
11792     movePtr++;
11793     MakePieceList(board, counts);
11794     return movePtr;
11795 }
11796
11797 int
11798 QuickCompare (Board board, int *minCounts, int *maxCounts)
11799 {   // compare according to search mode
11800     int r, f;
11801     switch(appData.searchMode)
11802     {
11803       case 1: // exact position match
11804         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11805         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11806             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11807         }
11808         break;
11809       case 2: // can have extra material on empty squares
11810         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11811             if(board[r][f] == EmptySquare) continue;
11812             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11813         }
11814         break;
11815       case 3: // material with exact Pawn structure
11816         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11817             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11818             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11819         } // fall through to material comparison
11820       case 4: // exact material
11821         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11822         break;
11823       case 6: // material range with given imbalance
11824         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11825         // fall through to range comparison
11826       case 5: // material range
11827         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11828     }
11829     return TRUE;
11830 }
11831
11832 int
11833 QuickScan (Board board, Move *move)
11834 {   // reconstruct game,and compare all positions in it
11835     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11836     do {
11837         int piece = move->piece;
11838         int to = move->to, from = pieceList[piece];
11839         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11840           if(!piece) return -1;
11841           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11842             piece = (++move)->piece;
11843             from = pieceList[piece];
11844             counts[pieceType[piece]]--;
11845             pieceType[piece] = (ChessSquare) move->to;
11846             counts[move->to]++;
11847           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11848             counts[pieceType[quickBoard[to]]]--;
11849             quickBoard[to] = 0; total--;
11850             move++;
11851             continue;
11852           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11853             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11854             from  = pieceList[piece]; // so this must be King
11855             quickBoard[from] = 0;
11856             pieceList[piece] = to;
11857             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11858             quickBoard[from] = 0; // rook
11859             quickBoard[to] = piece;
11860             to = move->to; piece = move->piece;
11861             goto aftercastle;
11862           }
11863         }
11864         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11865         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11866         quickBoard[from] = 0;
11867       aftercastle:
11868         quickBoard[to] = piece;
11869         pieceList[piece] = to;
11870         cnt++; turn ^= 3;
11871         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11872            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11873            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11874                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11875           ) {
11876             static int lastCounts[EmptySquare+1];
11877             int i;
11878             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11879             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11880         } else stretch = 0;
11881         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11882         move++;
11883     } while(1);
11884 }
11885
11886 void
11887 InitSearch ()
11888 {
11889     int r, f;
11890     flipSearch = FALSE;
11891     CopyBoard(soughtBoard, boards[currentMove]);
11892     soughtTotal = MakePieceList(soughtBoard, maxSought);
11893     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11894     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11895     CopyBoard(reverseBoard, boards[currentMove]);
11896     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11897         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11898         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11899         reverseBoard[r][f] = piece;
11900     }
11901     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11902     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11903     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11904                  || (boards[currentMove][CASTLING][2] == NoRights ||
11905                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11906                  && (boards[currentMove][CASTLING][5] == NoRights ||
11907                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11908       ) {
11909         flipSearch = TRUE;
11910         CopyBoard(flipBoard, soughtBoard);
11911         CopyBoard(rotateBoard, reverseBoard);
11912         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11913             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11914             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11915         }
11916     }
11917     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11918     if(appData.searchMode >= 5) {
11919         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11920         MakePieceList(soughtBoard, minSought);
11921         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11922     }
11923     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11924         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11925 }
11926
11927 GameInfo dummyInfo;
11928 static int creatingBook;
11929
11930 int
11931 GameContainsPosition (FILE *f, ListGame *lg)
11932 {
11933     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11934     int fromX, fromY, toX, toY;
11935     char promoChar;
11936     static int initDone=FALSE;
11937
11938     // weed out games based on numerical tag comparison
11939     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11940     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11941     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11942     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11943     if(!initDone) {
11944         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11945         initDone = TRUE;
11946     }
11947     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11948     else CopyBoard(boards[scratch], initialPosition); // default start position
11949     if(lg->moves) {
11950         turn = btm + 1;
11951         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11952         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11953     }
11954     if(btm) plyNr++;
11955     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11956     fseek(f, lg->offset, 0);
11957     yynewfile(f);
11958     while(1) {
11959         yyboardindex = scratch;
11960         quickFlag = plyNr+1;
11961         next = Myylex();
11962         quickFlag = 0;
11963         switch(next) {
11964             case PGNTag:
11965                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11966             default:
11967                 continue;
11968
11969             case XBoardGame:
11970             case GNUChessGame:
11971                 if(plyNr) return -1; // after we have seen moves, this is for new game
11972               continue;
11973
11974             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11975             case ImpossibleMove:
11976             case WhiteWins: // game ends here with these four
11977             case BlackWins:
11978             case GameIsDrawn:
11979             case GameUnfinished:
11980                 return -1;
11981
11982             case IllegalMove:
11983                 if(appData.testLegality) return -1;
11984             case WhiteCapturesEnPassant:
11985             case BlackCapturesEnPassant:
11986             case WhitePromotion:
11987             case BlackPromotion:
11988             case WhiteNonPromotion:
11989             case BlackNonPromotion:
11990             case NormalMove:
11991             case WhiteKingSideCastle:
11992             case WhiteQueenSideCastle:
11993             case BlackKingSideCastle:
11994             case BlackQueenSideCastle:
11995             case WhiteKingSideCastleWild:
11996             case WhiteQueenSideCastleWild:
11997             case BlackKingSideCastleWild:
11998             case BlackQueenSideCastleWild:
11999             case WhiteHSideCastleFR:
12000             case WhiteASideCastleFR:
12001             case BlackHSideCastleFR:
12002             case BlackASideCastleFR:
12003                 fromX = currentMoveString[0] - AAA;
12004                 fromY = currentMoveString[1] - ONE;
12005                 toX = currentMoveString[2] - AAA;
12006                 toY = currentMoveString[3] - ONE;
12007                 promoChar = currentMoveString[4];
12008                 break;
12009             case WhiteDrop:
12010             case BlackDrop:
12011                 fromX = next == WhiteDrop ?
12012                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12013                   (int) CharToPiece(ToLower(currentMoveString[0]));
12014                 fromY = DROP_RANK;
12015                 toX = currentMoveString[2] - AAA;
12016                 toY = currentMoveString[3] - ONE;
12017                 promoChar = 0;
12018                 break;
12019         }
12020         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12021         plyNr++;
12022         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12023         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12024         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12025         if(appData.findMirror) {
12026             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12027             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12028         }
12029     }
12030 }
12031
12032 /* Load the nth game from open file f */
12033 int
12034 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12035 {
12036     ChessMove cm;
12037     char buf[MSG_SIZ];
12038     int gn = gameNumber;
12039     ListGame *lg = NULL;
12040     int numPGNTags = 0;
12041     int err, pos = -1;
12042     GameMode oldGameMode;
12043     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12044
12045     if (appData.debugMode)
12046         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12047
12048     if (gameMode == Training )
12049         SetTrainingModeOff();
12050
12051     oldGameMode = gameMode;
12052     if (gameMode != BeginningOfGame) {
12053       Reset(FALSE, TRUE);
12054     }
12055
12056     gameFileFP = f;
12057     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12058         fclose(lastLoadGameFP);
12059     }
12060
12061     if (useList) {
12062         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12063
12064         if (lg) {
12065             fseek(f, lg->offset, 0);
12066             GameListHighlight(gameNumber);
12067             pos = lg->position;
12068             gn = 1;
12069         }
12070         else {
12071             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12072               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12073             else
12074             DisplayError(_("Game number out of range"), 0);
12075             return FALSE;
12076         }
12077     } else {
12078         GameListDestroy();
12079         if (fseek(f, 0, 0) == -1) {
12080             if (f == lastLoadGameFP ?
12081                 gameNumber == lastLoadGameNumber + 1 :
12082                 gameNumber == 1) {
12083                 gn = 1;
12084             } else {
12085                 DisplayError(_("Can't seek on game file"), 0);
12086                 return FALSE;
12087             }
12088         }
12089     }
12090     lastLoadGameFP = f;
12091     lastLoadGameNumber = gameNumber;
12092     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12093     lastLoadGameUseList = useList;
12094
12095     yynewfile(f);
12096
12097     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12098       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12099                 lg->gameInfo.black);
12100             DisplayTitle(buf);
12101     } else if (*title != NULLCHAR) {
12102         if (gameNumber > 1) {
12103           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12104             DisplayTitle(buf);
12105         } else {
12106             DisplayTitle(title);
12107         }
12108     }
12109
12110     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12111         gameMode = PlayFromGameFile;
12112         ModeHighlight();
12113     }
12114
12115     currentMove = forwardMostMove = backwardMostMove = 0;
12116     CopyBoard(boards[0], initialPosition);
12117     StopClocks();
12118
12119     /*
12120      * Skip the first gn-1 games in the file.
12121      * Also skip over anything that precedes an identifiable
12122      * start of game marker, to avoid being confused by
12123      * garbage at the start of the file.  Currently
12124      * recognized start of game markers are the move number "1",
12125      * the pattern "gnuchess .* game", the pattern
12126      * "^[#;%] [^ ]* game file", and a PGN tag block.
12127      * A game that starts with one of the latter two patterns
12128      * will also have a move number 1, possibly
12129      * following a position diagram.
12130      * 5-4-02: Let's try being more lenient and allowing a game to
12131      * start with an unnumbered move.  Does that break anything?
12132      */
12133     cm = lastLoadGameStart = EndOfFile;
12134     while (gn > 0) {
12135         yyboardindex = forwardMostMove;
12136         cm = (ChessMove) Myylex();
12137         switch (cm) {
12138           case EndOfFile:
12139             if (cmailMsgLoaded) {
12140                 nCmailGames = CMAIL_MAX_GAMES - gn;
12141             } else {
12142                 Reset(TRUE, TRUE);
12143                 DisplayError(_("Game not found in file"), 0);
12144             }
12145             return FALSE;
12146
12147           case GNUChessGame:
12148           case XBoardGame:
12149             gn--;
12150             lastLoadGameStart = cm;
12151             break;
12152
12153           case MoveNumberOne:
12154             switch (lastLoadGameStart) {
12155               case GNUChessGame:
12156               case XBoardGame:
12157               case PGNTag:
12158                 break;
12159               case MoveNumberOne:
12160               case EndOfFile:
12161                 gn--;           /* count this game */
12162                 lastLoadGameStart = cm;
12163                 break;
12164               default:
12165                 /* impossible */
12166                 break;
12167             }
12168             break;
12169
12170           case PGNTag:
12171             switch (lastLoadGameStart) {
12172               case GNUChessGame:
12173               case PGNTag:
12174               case MoveNumberOne:
12175               case EndOfFile:
12176                 gn--;           /* count this game */
12177                 lastLoadGameStart = cm;
12178                 break;
12179               case XBoardGame:
12180                 lastLoadGameStart = cm; /* game counted already */
12181                 break;
12182               default:
12183                 /* impossible */
12184                 break;
12185             }
12186             if (gn > 0) {
12187                 do {
12188                     yyboardindex = forwardMostMove;
12189                     cm = (ChessMove) Myylex();
12190                 } while (cm == PGNTag || cm == Comment);
12191             }
12192             break;
12193
12194           case WhiteWins:
12195           case BlackWins:
12196           case GameIsDrawn:
12197             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12198                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12199                     != CMAIL_OLD_RESULT) {
12200                     nCmailResults ++ ;
12201                     cmailResult[  CMAIL_MAX_GAMES
12202                                 - gn - 1] = CMAIL_OLD_RESULT;
12203                 }
12204             }
12205             break;
12206
12207           case NormalMove:
12208             /* Only a NormalMove can be at the start of a game
12209              * without a position diagram. */
12210             if (lastLoadGameStart == EndOfFile ) {
12211               gn--;
12212               lastLoadGameStart = MoveNumberOne;
12213             }
12214             break;
12215
12216           default:
12217             break;
12218         }
12219     }
12220
12221     if (appData.debugMode)
12222       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12223
12224     if (cm == XBoardGame) {
12225         /* Skip any header junk before position diagram and/or move 1 */
12226         for (;;) {
12227             yyboardindex = forwardMostMove;
12228             cm = (ChessMove) Myylex();
12229
12230             if (cm == EndOfFile ||
12231                 cm == GNUChessGame || cm == XBoardGame) {
12232                 /* Empty game; pretend end-of-file and handle later */
12233                 cm = EndOfFile;
12234                 break;
12235             }
12236
12237             if (cm == MoveNumberOne || cm == PositionDiagram ||
12238                 cm == PGNTag || cm == Comment)
12239               break;
12240         }
12241     } else if (cm == GNUChessGame) {
12242         if (gameInfo.event != NULL) {
12243             free(gameInfo.event);
12244         }
12245         gameInfo.event = StrSave(yy_text);
12246     }
12247
12248     startedFromSetupPosition = FALSE;
12249     while (cm == PGNTag) {
12250         if (appData.debugMode)
12251           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12252         err = ParsePGNTag(yy_text, &gameInfo);
12253         if (!err) numPGNTags++;
12254
12255         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12256         if(gameInfo.variant != oldVariant) {
12257             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12258             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12259             InitPosition(TRUE);
12260             oldVariant = gameInfo.variant;
12261             if (appData.debugMode)
12262               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12263         }
12264
12265
12266         if (gameInfo.fen != NULL) {
12267           Board initial_position;
12268           startedFromSetupPosition = TRUE;
12269           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12270             Reset(TRUE, TRUE);
12271             DisplayError(_("Bad FEN position in file"), 0);
12272             return FALSE;
12273           }
12274           CopyBoard(boards[0], initial_position);
12275           if (blackPlaysFirst) {
12276             currentMove = forwardMostMove = backwardMostMove = 1;
12277             CopyBoard(boards[1], initial_position);
12278             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12279             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12280             timeRemaining[0][1] = whiteTimeRemaining;
12281             timeRemaining[1][1] = blackTimeRemaining;
12282             if (commentList[0] != NULL) {
12283               commentList[1] = commentList[0];
12284               commentList[0] = NULL;
12285             }
12286           } else {
12287             currentMove = forwardMostMove = backwardMostMove = 0;
12288           }
12289           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12290           {   int i;
12291               initialRulePlies = FENrulePlies;
12292               for( i=0; i< nrCastlingRights; i++ )
12293                   initialRights[i] = initial_position[CASTLING][i];
12294           }
12295           yyboardindex = forwardMostMove;
12296           free(gameInfo.fen);
12297           gameInfo.fen = NULL;
12298         }
12299
12300         yyboardindex = forwardMostMove;
12301         cm = (ChessMove) Myylex();
12302
12303         /* Handle comments interspersed among the tags */
12304         while (cm == Comment) {
12305             char *p;
12306             if (appData.debugMode)
12307               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12308             p = yy_text;
12309             AppendComment(currentMove, p, FALSE);
12310             yyboardindex = forwardMostMove;
12311             cm = (ChessMove) Myylex();
12312         }
12313     }
12314
12315     /* don't rely on existence of Event tag since if game was
12316      * pasted from clipboard the Event tag may not exist
12317      */
12318     if (numPGNTags > 0){
12319         char *tags;
12320         if (gameInfo.variant == VariantNormal) {
12321           VariantClass v = StringToVariant(gameInfo.event);
12322           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12323           if(v < VariantShogi) gameInfo.variant = v;
12324         }
12325         if (!matchMode) {
12326           if( appData.autoDisplayTags ) {
12327             tags = PGNTags(&gameInfo);
12328             TagsPopUp(tags, CmailMsg());
12329             free(tags);
12330           }
12331         }
12332     } else {
12333         /* Make something up, but don't display it now */
12334         SetGameInfo();
12335         TagsPopDown();
12336     }
12337
12338     if (cm == PositionDiagram) {
12339         int i, j;
12340         char *p;
12341         Board initial_position;
12342
12343         if (appData.debugMode)
12344           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12345
12346         if (!startedFromSetupPosition) {
12347             p = yy_text;
12348             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12349               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12350                 switch (*p) {
12351                   case '{':
12352                   case '[':
12353                   case '-':
12354                   case ' ':
12355                   case '\t':
12356                   case '\n':
12357                   case '\r':
12358                     break;
12359                   default:
12360                     initial_position[i][j++] = CharToPiece(*p);
12361                     break;
12362                 }
12363             while (*p == ' ' || *p == '\t' ||
12364                    *p == '\n' || *p == '\r') p++;
12365
12366             if (strncmp(p, "black", strlen("black"))==0)
12367               blackPlaysFirst = TRUE;
12368             else
12369               blackPlaysFirst = FALSE;
12370             startedFromSetupPosition = TRUE;
12371
12372             CopyBoard(boards[0], initial_position);
12373             if (blackPlaysFirst) {
12374                 currentMove = forwardMostMove = backwardMostMove = 1;
12375                 CopyBoard(boards[1], initial_position);
12376                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12377                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12378                 timeRemaining[0][1] = whiteTimeRemaining;
12379                 timeRemaining[1][1] = blackTimeRemaining;
12380                 if (commentList[0] != NULL) {
12381                     commentList[1] = commentList[0];
12382                     commentList[0] = NULL;
12383                 }
12384             } else {
12385                 currentMove = forwardMostMove = backwardMostMove = 0;
12386             }
12387         }
12388         yyboardindex = forwardMostMove;
12389         cm = (ChessMove) Myylex();
12390     }
12391
12392   if(!creatingBook) {
12393     if (first.pr == NoProc) {
12394         StartChessProgram(&first);
12395     }
12396     InitChessProgram(&first, FALSE);
12397     SendToProgram("force\n", &first);
12398     if (startedFromSetupPosition) {
12399         SendBoard(&first, forwardMostMove);
12400     if (appData.debugMode) {
12401         fprintf(debugFP, "Load Game\n");
12402     }
12403         DisplayBothClocks();
12404     }
12405   }
12406
12407     /* [HGM] server: flag to write setup moves in broadcast file as one */
12408     loadFlag = appData.suppressLoadMoves;
12409
12410     while (cm == Comment) {
12411         char *p;
12412         if (appData.debugMode)
12413           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12414         p = yy_text;
12415         AppendComment(currentMove, p, FALSE);
12416         yyboardindex = forwardMostMove;
12417         cm = (ChessMove) Myylex();
12418     }
12419
12420     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12421         cm == WhiteWins || cm == BlackWins ||
12422         cm == GameIsDrawn || cm == GameUnfinished) {
12423         DisplayMessage("", _("No moves in game"));
12424         if (cmailMsgLoaded) {
12425             if (appData.debugMode)
12426               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12427             ClearHighlights();
12428             flipView = FALSE;
12429         }
12430         DrawPosition(FALSE, boards[currentMove]);
12431         DisplayBothClocks();
12432         gameMode = EditGame;
12433         ModeHighlight();
12434         gameFileFP = NULL;
12435         cmailOldMove = 0;
12436         return TRUE;
12437     }
12438
12439     // [HGM] PV info: routine tests if comment empty
12440     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12441         DisplayComment(currentMove - 1, commentList[currentMove]);
12442     }
12443     if (!matchMode && appData.timeDelay != 0)
12444       DrawPosition(FALSE, boards[currentMove]);
12445
12446     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12447       programStats.ok_to_send = 1;
12448     }
12449
12450     /* if the first token after the PGN tags is a move
12451      * and not move number 1, retrieve it from the parser
12452      */
12453     if (cm != MoveNumberOne)
12454         LoadGameOneMove(cm);
12455
12456     /* load the remaining moves from the file */
12457     while (LoadGameOneMove(EndOfFile)) {
12458       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12459       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12460     }
12461
12462     /* rewind to the start of the game */
12463     currentMove = backwardMostMove;
12464
12465     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12466
12467     if (oldGameMode == AnalyzeFile) {
12468       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12469       AnalyzeFileEvent();
12470     } else
12471     if (oldGameMode == AnalyzeMode) {
12472       AnalyzeFileEvent();
12473     }
12474
12475     if(creatingBook) return TRUE;
12476     if (!matchMode && pos > 0) {
12477         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12478     } else
12479     if (matchMode || appData.timeDelay == 0) {
12480       ToEndEvent();
12481     } else if (appData.timeDelay > 0) {
12482       AutoPlayGameLoop();
12483     }
12484
12485     if (appData.debugMode)
12486         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12487
12488     loadFlag = 0; /* [HGM] true game starts */
12489     return TRUE;
12490 }
12491
12492 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12493 int
12494 ReloadPosition (int offset)
12495 {
12496     int positionNumber = lastLoadPositionNumber + offset;
12497     if (lastLoadPositionFP == NULL) {
12498         DisplayError(_("No position has been loaded yet"), 0);
12499         return FALSE;
12500     }
12501     if (positionNumber <= 0) {
12502         DisplayError(_("Can't back up any further"), 0);
12503         return FALSE;
12504     }
12505     return LoadPosition(lastLoadPositionFP, positionNumber,
12506                         lastLoadPositionTitle);
12507 }
12508
12509 /* Load the nth position from the given file */
12510 int
12511 LoadPositionFromFile (char *filename, int n, char *title)
12512 {
12513     FILE *f;
12514     char buf[MSG_SIZ];
12515
12516     if (strcmp(filename, "-") == 0) {
12517         return LoadPosition(stdin, n, "stdin");
12518     } else {
12519         f = fopen(filename, "rb");
12520         if (f == NULL) {
12521             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12522             DisplayError(buf, errno);
12523             return FALSE;
12524         } else {
12525             return LoadPosition(f, n, title);
12526         }
12527     }
12528 }
12529
12530 /* Load the nth position from the given open file, and close it */
12531 int
12532 LoadPosition (FILE *f, int positionNumber, char *title)
12533 {
12534     char *p, line[MSG_SIZ];
12535     Board initial_position;
12536     int i, j, fenMode, pn;
12537
12538     if (gameMode == Training )
12539         SetTrainingModeOff();
12540
12541     if (gameMode != BeginningOfGame) {
12542         Reset(FALSE, TRUE);
12543     }
12544     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12545         fclose(lastLoadPositionFP);
12546     }
12547     if (positionNumber == 0) positionNumber = 1;
12548     lastLoadPositionFP = f;
12549     lastLoadPositionNumber = positionNumber;
12550     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12551     if (first.pr == NoProc && !appData.noChessProgram) {
12552       StartChessProgram(&first);
12553       InitChessProgram(&first, FALSE);
12554     }
12555     pn = positionNumber;
12556     if (positionNumber < 0) {
12557         /* Negative position number means to seek to that byte offset */
12558         if (fseek(f, -positionNumber, 0) == -1) {
12559             DisplayError(_("Can't seek on position file"), 0);
12560             return FALSE;
12561         };
12562         pn = 1;
12563     } else {
12564         if (fseek(f, 0, 0) == -1) {
12565             if (f == lastLoadPositionFP ?
12566                 positionNumber == lastLoadPositionNumber + 1 :
12567                 positionNumber == 1) {
12568                 pn = 1;
12569             } else {
12570                 DisplayError(_("Can't seek on position file"), 0);
12571                 return FALSE;
12572             }
12573         }
12574     }
12575     /* See if this file is FEN or old-style xboard */
12576     if (fgets(line, MSG_SIZ, f) == NULL) {
12577         DisplayError(_("Position not found in file"), 0);
12578         return FALSE;
12579     }
12580     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12581     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12582
12583     if (pn >= 2) {
12584         if (fenMode || line[0] == '#') pn--;
12585         while (pn > 0) {
12586             /* skip positions before number pn */
12587             if (fgets(line, MSG_SIZ, f) == NULL) {
12588                 Reset(TRUE, TRUE);
12589                 DisplayError(_("Position not found in file"), 0);
12590                 return FALSE;
12591             }
12592             if (fenMode || line[0] == '#') pn--;
12593         }
12594     }
12595
12596     if (fenMode) {
12597         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12598             DisplayError(_("Bad FEN position in file"), 0);
12599             return FALSE;
12600         }
12601     } else {
12602         (void) fgets(line, MSG_SIZ, f);
12603         (void) fgets(line, MSG_SIZ, f);
12604
12605         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12606             (void) fgets(line, MSG_SIZ, f);
12607             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12608                 if (*p == ' ')
12609                   continue;
12610                 initial_position[i][j++] = CharToPiece(*p);
12611             }
12612         }
12613
12614         blackPlaysFirst = FALSE;
12615         if (!feof(f)) {
12616             (void) fgets(line, MSG_SIZ, f);
12617             if (strncmp(line, "black", strlen("black"))==0)
12618               blackPlaysFirst = TRUE;
12619         }
12620     }
12621     startedFromSetupPosition = TRUE;
12622
12623     CopyBoard(boards[0], initial_position);
12624     if (blackPlaysFirst) {
12625         currentMove = forwardMostMove = backwardMostMove = 1;
12626         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12627         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12628         CopyBoard(boards[1], initial_position);
12629         DisplayMessage("", _("Black to play"));
12630     } else {
12631         currentMove = forwardMostMove = backwardMostMove = 0;
12632         DisplayMessage("", _("White to play"));
12633     }
12634     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12635     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12636         SendToProgram("force\n", &first);
12637         SendBoard(&first, forwardMostMove);
12638     }
12639     if (appData.debugMode) {
12640 int i, j;
12641   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12642   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12643         fprintf(debugFP, "Load Position\n");
12644     }
12645
12646     if (positionNumber > 1) {
12647       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12648         DisplayTitle(line);
12649     } else {
12650         DisplayTitle(title);
12651     }
12652     gameMode = EditGame;
12653     ModeHighlight();
12654     ResetClocks();
12655     timeRemaining[0][1] = whiteTimeRemaining;
12656     timeRemaining[1][1] = blackTimeRemaining;
12657     DrawPosition(FALSE, boards[currentMove]);
12658
12659     return TRUE;
12660 }
12661
12662
12663 void
12664 CopyPlayerNameIntoFileName (char **dest, char *src)
12665 {
12666     while (*src != NULLCHAR && *src != ',') {
12667         if (*src == ' ') {
12668             *(*dest)++ = '_';
12669             src++;
12670         } else {
12671             *(*dest)++ = *src++;
12672         }
12673     }
12674 }
12675
12676 char *
12677 DefaultFileName (char *ext)
12678 {
12679     static char def[MSG_SIZ];
12680     char *p;
12681
12682     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12683         p = def;
12684         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12685         *p++ = '-';
12686         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12687         *p++ = '.';
12688         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12689     } else {
12690         def[0] = NULLCHAR;
12691     }
12692     return def;
12693 }
12694
12695 /* Save the current game to the given file */
12696 int
12697 SaveGameToFile (char *filename, int append)
12698 {
12699     FILE *f;
12700     char buf[MSG_SIZ];
12701     int result, i, t,tot=0;
12702
12703     if (strcmp(filename, "-") == 0) {
12704         return SaveGame(stdout, 0, NULL);
12705     } else {
12706         for(i=0; i<10; i++) { // upto 10 tries
12707              f = fopen(filename, append ? "a" : "w");
12708              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12709              if(f || errno != 13) break;
12710              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12711              tot += t;
12712         }
12713         if (f == NULL) {
12714             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12715             DisplayError(buf, errno);
12716             return FALSE;
12717         } else {
12718             safeStrCpy(buf, lastMsg, MSG_SIZ);
12719             DisplayMessage(_("Waiting for access to save file"), "");
12720             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12721             DisplayMessage(_("Saving game"), "");
12722             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12723             result = SaveGame(f, 0, NULL);
12724             DisplayMessage(buf, "");
12725             return result;
12726         }
12727     }
12728 }
12729
12730 char *
12731 SavePart (char *str)
12732 {
12733     static char buf[MSG_SIZ];
12734     char *p;
12735
12736     p = strchr(str, ' ');
12737     if (p == NULL) return str;
12738     strncpy(buf, str, p - str);
12739     buf[p - str] = NULLCHAR;
12740     return buf;
12741 }
12742
12743 #define PGN_MAX_LINE 75
12744
12745 #define PGN_SIDE_WHITE  0
12746 #define PGN_SIDE_BLACK  1
12747
12748 static int
12749 FindFirstMoveOutOfBook (int side)
12750 {
12751     int result = -1;
12752
12753     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12754         int index = backwardMostMove;
12755         int has_book_hit = 0;
12756
12757         if( (index % 2) != side ) {
12758             index++;
12759         }
12760
12761         while( index < forwardMostMove ) {
12762             /* Check to see if engine is in book */
12763             int depth = pvInfoList[index].depth;
12764             int score = pvInfoList[index].score;
12765             int in_book = 0;
12766
12767             if( depth <= 2 ) {
12768                 in_book = 1;
12769             }
12770             else if( score == 0 && depth == 63 ) {
12771                 in_book = 1; /* Zappa */
12772             }
12773             else if( score == 2 && depth == 99 ) {
12774                 in_book = 1; /* Abrok */
12775             }
12776
12777             has_book_hit += in_book;
12778
12779             if( ! in_book ) {
12780                 result = index;
12781
12782                 break;
12783             }
12784
12785             index += 2;
12786         }
12787     }
12788
12789     return result;
12790 }
12791
12792 void
12793 GetOutOfBookInfo (char * buf)
12794 {
12795     int oob[2];
12796     int i;
12797     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12798
12799     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12800     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12801
12802     *buf = '\0';
12803
12804     if( oob[0] >= 0 || oob[1] >= 0 ) {
12805         for( i=0; i<2; i++ ) {
12806             int idx = oob[i];
12807
12808             if( idx >= 0 ) {
12809                 if( i > 0 && oob[0] >= 0 ) {
12810                     strcat( buf, "   " );
12811                 }
12812
12813                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12814                 sprintf( buf+strlen(buf), "%s%.2f",
12815                     pvInfoList[idx].score >= 0 ? "+" : "",
12816                     pvInfoList[idx].score / 100.0 );
12817             }
12818         }
12819     }
12820 }
12821
12822 /* Save game in PGN style and close the file */
12823 int
12824 SaveGamePGN (FILE *f)
12825 {
12826     int i, offset, linelen, newblock;
12827 //    char *movetext;
12828     char numtext[32];
12829     int movelen, numlen, blank;
12830     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12831
12832     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12833
12834     PrintPGNTags(f, &gameInfo);
12835
12836     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12837
12838     if (backwardMostMove > 0 || startedFromSetupPosition) {
12839         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
12840         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12841         fprintf(f, "\n{--------------\n");
12842         PrintPosition(f, backwardMostMove);
12843         fprintf(f, "--------------}\n");
12844         free(fen);
12845     }
12846     else {
12847         /* [AS] Out of book annotation */
12848         if( appData.saveOutOfBookInfo ) {
12849             char buf[64];
12850
12851             GetOutOfBookInfo( buf );
12852
12853             if( buf[0] != '\0' ) {
12854                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12855             }
12856         }
12857
12858         fprintf(f, "\n");
12859     }
12860
12861     i = backwardMostMove;
12862     linelen = 0;
12863     newblock = TRUE;
12864
12865     while (i < forwardMostMove) {
12866         /* Print comments preceding this move */
12867         if (commentList[i] != NULL) {
12868             if (linelen > 0) fprintf(f, "\n");
12869             fprintf(f, "%s", commentList[i]);
12870             linelen = 0;
12871             newblock = TRUE;
12872         }
12873
12874         /* Format move number */
12875         if ((i % 2) == 0)
12876           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12877         else
12878           if (newblock)
12879             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12880           else
12881             numtext[0] = NULLCHAR;
12882
12883         numlen = strlen(numtext);
12884         newblock = FALSE;
12885
12886         /* Print move number */
12887         blank = linelen > 0 && numlen > 0;
12888         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12889             fprintf(f, "\n");
12890             linelen = 0;
12891             blank = 0;
12892         }
12893         if (blank) {
12894             fprintf(f, " ");
12895             linelen++;
12896         }
12897         fprintf(f, "%s", numtext);
12898         linelen += numlen;
12899
12900         /* Get move */
12901         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12902         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12903
12904         /* Print move */
12905         blank = linelen > 0 && movelen > 0;
12906         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12907             fprintf(f, "\n");
12908             linelen = 0;
12909             blank = 0;
12910         }
12911         if (blank) {
12912             fprintf(f, " ");
12913             linelen++;
12914         }
12915         fprintf(f, "%s", move_buffer);
12916         linelen += movelen;
12917
12918         /* [AS] Add PV info if present */
12919         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12920             /* [HGM] add time */
12921             char buf[MSG_SIZ]; int seconds;
12922
12923             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12924
12925             if( seconds <= 0)
12926               buf[0] = 0;
12927             else
12928               if( seconds < 30 )
12929                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12930               else
12931                 {
12932                   seconds = (seconds + 4)/10; // round to full seconds
12933                   if( seconds < 60 )
12934                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12935                   else
12936                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12937                 }
12938
12939             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12940                       pvInfoList[i].score >= 0 ? "+" : "",
12941                       pvInfoList[i].score / 100.0,
12942                       pvInfoList[i].depth,
12943                       buf );
12944
12945             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12946
12947             /* Print score/depth */
12948             blank = linelen > 0 && movelen > 0;
12949             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12950                 fprintf(f, "\n");
12951                 linelen = 0;
12952                 blank = 0;
12953             }
12954             if (blank) {
12955                 fprintf(f, " ");
12956                 linelen++;
12957             }
12958             fprintf(f, "%s", move_buffer);
12959             linelen += movelen;
12960         }
12961
12962         i++;
12963     }
12964
12965     /* Start a new line */
12966     if (linelen > 0) fprintf(f, "\n");
12967
12968     /* Print comments after last move */
12969     if (commentList[i] != NULL) {
12970         fprintf(f, "%s\n", commentList[i]);
12971     }
12972
12973     /* Print result */
12974     if (gameInfo.resultDetails != NULL &&
12975         gameInfo.resultDetails[0] != NULLCHAR) {
12976         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12977                 PGNResult(gameInfo.result));
12978     } else {
12979         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12980     }
12981
12982     fclose(f);
12983     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12984     return TRUE;
12985 }
12986
12987 /* Save game in old style and close the file */
12988 int
12989 SaveGameOldStyle (FILE *f)
12990 {
12991     int i, offset;
12992     time_t tm;
12993
12994     tm = time((time_t *) NULL);
12995
12996     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12997     PrintOpponents(f);
12998
12999     if (backwardMostMove > 0 || startedFromSetupPosition) {
13000         fprintf(f, "\n[--------------\n");
13001         PrintPosition(f, backwardMostMove);
13002         fprintf(f, "--------------]\n");
13003     } else {
13004         fprintf(f, "\n");
13005     }
13006
13007     i = backwardMostMove;
13008     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13009
13010     while (i < forwardMostMove) {
13011         if (commentList[i] != NULL) {
13012             fprintf(f, "[%s]\n", commentList[i]);
13013         }
13014
13015         if ((i % 2) == 1) {
13016             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13017             i++;
13018         } else {
13019             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13020             i++;
13021             if (commentList[i] != NULL) {
13022                 fprintf(f, "\n");
13023                 continue;
13024             }
13025             if (i >= forwardMostMove) {
13026                 fprintf(f, "\n");
13027                 break;
13028             }
13029             fprintf(f, "%s\n", parseList[i]);
13030             i++;
13031         }
13032     }
13033
13034     if (commentList[i] != NULL) {
13035         fprintf(f, "[%s]\n", commentList[i]);
13036     }
13037
13038     /* This isn't really the old style, but it's close enough */
13039     if (gameInfo.resultDetails != NULL &&
13040         gameInfo.resultDetails[0] != NULLCHAR) {
13041         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13042                 gameInfo.resultDetails);
13043     } else {
13044         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13045     }
13046
13047     fclose(f);
13048     return TRUE;
13049 }
13050
13051 /* Save the current game to open file f and close the file */
13052 int
13053 SaveGame (FILE *f, int dummy, char *dummy2)
13054 {
13055     if (gameMode == EditPosition) EditPositionDone(TRUE);
13056     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13057     if (appData.oldSaveStyle)
13058       return SaveGameOldStyle(f);
13059     else
13060       return SaveGamePGN(f);
13061 }
13062
13063 /* Save the current position to the given file */
13064 int
13065 SavePositionToFile (char *filename)
13066 {
13067     FILE *f;
13068     char buf[MSG_SIZ];
13069
13070     if (strcmp(filename, "-") == 0) {
13071         return SavePosition(stdout, 0, NULL);
13072     } else {
13073         f = fopen(filename, "a");
13074         if (f == NULL) {
13075             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13076             DisplayError(buf, errno);
13077             return FALSE;
13078         } else {
13079             safeStrCpy(buf, lastMsg, MSG_SIZ);
13080             DisplayMessage(_("Waiting for access to save file"), "");
13081             flock(fileno(f), LOCK_EX); // [HGM] lock
13082             DisplayMessage(_("Saving position"), "");
13083             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13084             SavePosition(f, 0, NULL);
13085             DisplayMessage(buf, "");
13086             return TRUE;
13087         }
13088     }
13089 }
13090
13091 /* Save the current position to the given open file and close the file */
13092 int
13093 SavePosition (FILE *f, int dummy, char *dummy2)
13094 {
13095     time_t tm;
13096     char *fen;
13097
13098     if (gameMode == EditPosition) EditPositionDone(TRUE);
13099     if (appData.oldSaveStyle) {
13100         tm = time((time_t *) NULL);
13101
13102         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13103         PrintOpponents(f);
13104         fprintf(f, "[--------------\n");
13105         PrintPosition(f, currentMove);
13106         fprintf(f, "--------------]\n");
13107     } else {
13108         fen = PositionToFEN(currentMove, NULL, 1);
13109         fprintf(f, "%s\n", fen);
13110         free(fen);
13111     }
13112     fclose(f);
13113     return TRUE;
13114 }
13115
13116 void
13117 ReloadCmailMsgEvent (int unregister)
13118 {
13119 #if !WIN32
13120     static char *inFilename = NULL;
13121     static char *outFilename;
13122     int i;
13123     struct stat inbuf, outbuf;
13124     int status;
13125
13126     /* Any registered moves are unregistered if unregister is set, */
13127     /* i.e. invoked by the signal handler */
13128     if (unregister) {
13129         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13130             cmailMoveRegistered[i] = FALSE;
13131             if (cmailCommentList[i] != NULL) {
13132                 free(cmailCommentList[i]);
13133                 cmailCommentList[i] = NULL;
13134             }
13135         }
13136         nCmailMovesRegistered = 0;
13137     }
13138
13139     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13140         cmailResult[i] = CMAIL_NOT_RESULT;
13141     }
13142     nCmailResults = 0;
13143
13144     if (inFilename == NULL) {
13145         /* Because the filenames are static they only get malloced once  */
13146         /* and they never get freed                                      */
13147         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13148         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13149
13150         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13151         sprintf(outFilename, "%s.out", appData.cmailGameName);
13152     }
13153
13154     status = stat(outFilename, &outbuf);
13155     if (status < 0) {
13156         cmailMailedMove = FALSE;
13157     } else {
13158         status = stat(inFilename, &inbuf);
13159         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13160     }
13161
13162     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13163        counts the games, notes how each one terminated, etc.
13164
13165        It would be nice to remove this kludge and instead gather all
13166        the information while building the game list.  (And to keep it
13167        in the game list nodes instead of having a bunch of fixed-size
13168        parallel arrays.)  Note this will require getting each game's
13169        termination from the PGN tags, as the game list builder does
13170        not process the game moves.  --mann
13171        */
13172     cmailMsgLoaded = TRUE;
13173     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13174
13175     /* Load first game in the file or popup game menu */
13176     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13177
13178 #endif /* !WIN32 */
13179     return;
13180 }
13181
13182 int
13183 RegisterMove ()
13184 {
13185     FILE *f;
13186     char string[MSG_SIZ];
13187
13188     if (   cmailMailedMove
13189         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13190         return TRUE;            /* Allow free viewing  */
13191     }
13192
13193     /* Unregister move to ensure that we don't leave RegisterMove        */
13194     /* with the move registered when the conditions for registering no   */
13195     /* longer hold                                                       */
13196     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13197         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13198         nCmailMovesRegistered --;
13199
13200         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13201           {
13202               free(cmailCommentList[lastLoadGameNumber - 1]);
13203               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13204           }
13205     }
13206
13207     if (cmailOldMove == -1) {
13208         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13209         return FALSE;
13210     }
13211
13212     if (currentMove > cmailOldMove + 1) {
13213         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13214         return FALSE;
13215     }
13216
13217     if (currentMove < cmailOldMove) {
13218         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13219         return FALSE;
13220     }
13221
13222     if (forwardMostMove > currentMove) {
13223         /* Silently truncate extra moves */
13224         TruncateGame();
13225     }
13226
13227     if (   (currentMove == cmailOldMove + 1)
13228         || (   (currentMove == cmailOldMove)
13229             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13230                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13231         if (gameInfo.result != GameUnfinished) {
13232             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13233         }
13234
13235         if (commentList[currentMove] != NULL) {
13236             cmailCommentList[lastLoadGameNumber - 1]
13237               = StrSave(commentList[currentMove]);
13238         }
13239         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13240
13241         if (appData.debugMode)
13242           fprintf(debugFP, "Saving %s for game %d\n",
13243                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13244
13245         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13246
13247         f = fopen(string, "w");
13248         if (appData.oldSaveStyle) {
13249             SaveGameOldStyle(f); /* also closes the file */
13250
13251             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13252             f = fopen(string, "w");
13253             SavePosition(f, 0, NULL); /* also closes the file */
13254         } else {
13255             fprintf(f, "{--------------\n");
13256             PrintPosition(f, currentMove);
13257             fprintf(f, "--------------}\n\n");
13258
13259             SaveGame(f, 0, NULL); /* also closes the file*/
13260         }
13261
13262         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13263         nCmailMovesRegistered ++;
13264     } else if (nCmailGames == 1) {
13265         DisplayError(_("You have not made a move yet"), 0);
13266         return FALSE;
13267     }
13268
13269     return TRUE;
13270 }
13271
13272 void
13273 MailMoveEvent ()
13274 {
13275 #if !WIN32
13276     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13277     FILE *commandOutput;
13278     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13279     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13280     int nBuffers;
13281     int i;
13282     int archived;
13283     char *arcDir;
13284
13285     if (! cmailMsgLoaded) {
13286         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13287         return;
13288     }
13289
13290     if (nCmailGames == nCmailResults) {
13291         DisplayError(_("No unfinished games"), 0);
13292         return;
13293     }
13294
13295 #if CMAIL_PROHIBIT_REMAIL
13296     if (cmailMailedMove) {
13297       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);
13298         DisplayError(msg, 0);
13299         return;
13300     }
13301 #endif
13302
13303     if (! (cmailMailedMove || RegisterMove())) return;
13304
13305     if (   cmailMailedMove
13306         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13307       snprintf(string, MSG_SIZ, partCommandString,
13308                appData.debugMode ? " -v" : "", appData.cmailGameName);
13309         commandOutput = popen(string, "r");
13310
13311         if (commandOutput == NULL) {
13312             DisplayError(_("Failed to invoke cmail"), 0);
13313         } else {
13314             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13315                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13316             }
13317             if (nBuffers > 1) {
13318                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13319                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13320                 nBytes = MSG_SIZ - 1;
13321             } else {
13322                 (void) memcpy(msg, buffer, nBytes);
13323             }
13324             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13325
13326             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13327                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13328
13329                 archived = TRUE;
13330                 for (i = 0; i < nCmailGames; i ++) {
13331                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13332                         archived = FALSE;
13333                     }
13334                 }
13335                 if (   archived
13336                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13337                         != NULL)) {
13338                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13339                            arcDir,
13340                            appData.cmailGameName,
13341                            gameInfo.date);
13342                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13343                     cmailMsgLoaded = FALSE;
13344                 }
13345             }
13346
13347             DisplayInformation(msg);
13348             pclose(commandOutput);
13349         }
13350     } else {
13351         if ((*cmailMsg) != '\0') {
13352             DisplayInformation(cmailMsg);
13353         }
13354     }
13355
13356     return;
13357 #endif /* !WIN32 */
13358 }
13359
13360 char *
13361 CmailMsg ()
13362 {
13363 #if WIN32
13364     return NULL;
13365 #else
13366     int  prependComma = 0;
13367     char number[5];
13368     char string[MSG_SIZ];       /* Space for game-list */
13369     int  i;
13370
13371     if (!cmailMsgLoaded) return "";
13372
13373     if (cmailMailedMove) {
13374       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13375     } else {
13376         /* Create a list of games left */
13377       snprintf(string, MSG_SIZ, "[");
13378         for (i = 0; i < nCmailGames; i ++) {
13379             if (! (   cmailMoveRegistered[i]
13380                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13381                 if (prependComma) {
13382                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13383                 } else {
13384                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13385                     prependComma = 1;
13386                 }
13387
13388                 strcat(string, number);
13389             }
13390         }
13391         strcat(string, "]");
13392
13393         if (nCmailMovesRegistered + nCmailResults == 0) {
13394             switch (nCmailGames) {
13395               case 1:
13396                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13397                 break;
13398
13399               case 2:
13400                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13401                 break;
13402
13403               default:
13404                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13405                          nCmailGames);
13406                 break;
13407             }
13408         } else {
13409             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13410               case 1:
13411                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13412                          string);
13413                 break;
13414
13415               case 0:
13416                 if (nCmailResults == nCmailGames) {
13417                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13418                 } else {
13419                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13420                 }
13421                 break;
13422
13423               default:
13424                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13425                          string);
13426             }
13427         }
13428     }
13429     return cmailMsg;
13430 #endif /* WIN32 */
13431 }
13432
13433 void
13434 ResetGameEvent ()
13435 {
13436     if (gameMode == Training)
13437       SetTrainingModeOff();
13438
13439     Reset(TRUE, TRUE);
13440     cmailMsgLoaded = FALSE;
13441     if (appData.icsActive) {
13442       SendToICS(ics_prefix);
13443       SendToICS("refresh\n");
13444     }
13445 }
13446
13447 void
13448 ExitEvent (int status)
13449 {
13450     exiting++;
13451     if (exiting > 2) {
13452       /* Give up on clean exit */
13453       exit(status);
13454     }
13455     if (exiting > 1) {
13456       /* Keep trying for clean exit */
13457       return;
13458     }
13459
13460     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13461
13462     if (telnetISR != NULL) {
13463       RemoveInputSource(telnetISR);
13464     }
13465     if (icsPR != NoProc) {
13466       DestroyChildProcess(icsPR, TRUE);
13467     }
13468
13469     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13470     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13471
13472     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13473     /* make sure this other one finishes before killing it!                  */
13474     if(endingGame) { int count = 0;
13475         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13476         while(endingGame && count++ < 10) DoSleep(1);
13477         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13478     }
13479
13480     /* Kill off chess programs */
13481     if (first.pr != NoProc) {
13482         ExitAnalyzeMode();
13483
13484         DoSleep( appData.delayBeforeQuit );
13485         SendToProgram("quit\n", &first);
13486         DoSleep( appData.delayAfterQuit );
13487         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13488     }
13489     if (second.pr != NoProc) {
13490         DoSleep( appData.delayBeforeQuit );
13491         SendToProgram("quit\n", &second);
13492         DoSleep( appData.delayAfterQuit );
13493         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13494     }
13495     if (first.isr != NULL) {
13496         RemoveInputSource(first.isr);
13497     }
13498     if (second.isr != NULL) {
13499         RemoveInputSource(second.isr);
13500     }
13501
13502     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13503     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13504
13505     ShutDownFrontEnd();
13506     exit(status);
13507 }
13508
13509 void
13510 PauseEngine (ChessProgramState *cps)
13511 {
13512     SendToProgram("pause\n", cps);
13513     cps->pause = 2;
13514 }
13515
13516 void
13517 UnPauseEngine (ChessProgramState *cps)
13518 {
13519     SendToProgram("resume\n", cps);
13520     cps->pause = 1;
13521 }
13522
13523 void
13524 PauseEvent ()
13525 {
13526     if (appData.debugMode)
13527         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13528     if (pausing) {
13529         pausing = FALSE;
13530         ModeHighlight();
13531         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13532             StartClocks();
13533             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13534                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13535                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13536             }
13537             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13538             HandleMachineMove(stashedInputMove, stalledEngine);
13539             stalledEngine = NULL;
13540             return;
13541         }
13542         if (gameMode == MachinePlaysWhite ||
13543             gameMode == TwoMachinesPlay   ||
13544             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13545             if(first.pause)  UnPauseEngine(&first);
13546             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13547             if(second.pause) UnPauseEngine(&second);
13548             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13549             StartClocks();
13550         } else {
13551             DisplayBothClocks();
13552         }
13553         if (gameMode == PlayFromGameFile) {
13554             if (appData.timeDelay >= 0)
13555                 AutoPlayGameLoop();
13556         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13557             Reset(FALSE, TRUE);
13558             SendToICS(ics_prefix);
13559             SendToICS("refresh\n");
13560         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13561             ForwardInner(forwardMostMove);
13562         }
13563         pauseExamInvalid = FALSE;
13564     } else {
13565         switch (gameMode) {
13566           default:
13567             return;
13568           case IcsExamining:
13569             pauseExamForwardMostMove = forwardMostMove;
13570             pauseExamInvalid = FALSE;
13571             /* fall through */
13572           case IcsObserving:
13573           case IcsPlayingWhite:
13574           case IcsPlayingBlack:
13575             pausing = TRUE;
13576             ModeHighlight();
13577             return;
13578           case PlayFromGameFile:
13579             (void) StopLoadGameTimer();
13580             pausing = TRUE;
13581             ModeHighlight();
13582             break;
13583           case BeginningOfGame:
13584             if (appData.icsActive) return;
13585             /* else fall through */
13586           case MachinePlaysWhite:
13587           case MachinePlaysBlack:
13588           case TwoMachinesPlay:
13589             if (forwardMostMove == 0)
13590               return;           /* don't pause if no one has moved */
13591             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13592                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13593                 if(onMove->pause) {           // thinking engine can be paused
13594                     PauseEngine(onMove);      // do it
13595                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13596                         PauseEngine(onMove->other);
13597                     else
13598                         SendToProgram("easy\n", onMove->other);
13599                     StopClocks();
13600                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13601             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13602                 if(first.pause) {
13603                     PauseEngine(&first);
13604                     StopClocks();
13605                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13606             } else { // human on move, pause pondering by either method
13607                 if(first.pause)
13608                     PauseEngine(&first);
13609                 else if(appData.ponderNextMove)
13610                     SendToProgram("easy\n", &first);
13611                 StopClocks();
13612             }
13613             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13614           case AnalyzeMode:
13615             pausing = TRUE;
13616             ModeHighlight();
13617             break;
13618         }
13619     }
13620 }
13621
13622 void
13623 EditCommentEvent ()
13624 {
13625     char title[MSG_SIZ];
13626
13627     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13628       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13629     } else {
13630       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13631                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13632                parseList[currentMove - 1]);
13633     }
13634
13635     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13636 }
13637
13638
13639 void
13640 EditTagsEvent ()
13641 {
13642     char *tags = PGNTags(&gameInfo);
13643     bookUp = FALSE;
13644     EditTagsPopUp(tags, NULL);
13645     free(tags);
13646 }
13647
13648 void
13649 ToggleSecond ()
13650 {
13651   if(second.analyzing) {
13652     SendToProgram("exit\n", &second);
13653     second.analyzing = FALSE;
13654   } else {
13655     if (second.pr == NoProc) StartChessProgram(&second);
13656     InitChessProgram(&second, FALSE);
13657     FeedMovesToProgram(&second, currentMove);
13658
13659     SendToProgram("analyze\n", &second);
13660     second.analyzing = TRUE;
13661   }
13662 }
13663
13664 /* Toggle ShowThinking */
13665 void
13666 ToggleShowThinking()
13667 {
13668   appData.showThinking = !appData.showThinking;
13669   ShowThinkingEvent();
13670 }
13671
13672 int
13673 AnalyzeModeEvent ()
13674 {
13675     char buf[MSG_SIZ];
13676
13677     if (!first.analysisSupport) {
13678       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13679       DisplayError(buf, 0);
13680       return 0;
13681     }
13682     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13683     if (appData.icsActive) {
13684         if (gameMode != IcsObserving) {
13685           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13686             DisplayError(buf, 0);
13687             /* secure check */
13688             if (appData.icsEngineAnalyze) {
13689                 if (appData.debugMode)
13690                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13691                 ExitAnalyzeMode();
13692                 ModeHighlight();
13693             }
13694             return 0;
13695         }
13696         /* if enable, user wants to disable icsEngineAnalyze */
13697         if (appData.icsEngineAnalyze) {
13698                 ExitAnalyzeMode();
13699                 ModeHighlight();
13700                 return 0;
13701         }
13702         appData.icsEngineAnalyze = TRUE;
13703         if (appData.debugMode)
13704             fprintf(debugFP, "ICS engine analyze starting... \n");
13705     }
13706
13707     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13708     if (appData.noChessProgram || gameMode == AnalyzeMode)
13709       return 0;
13710
13711     if (gameMode != AnalyzeFile) {
13712         if (!appData.icsEngineAnalyze) {
13713                EditGameEvent();
13714                if (gameMode != EditGame) return 0;
13715         }
13716         if (!appData.showThinking) ToggleShowThinking();
13717         ResurrectChessProgram();
13718         SendToProgram("analyze\n", &first);
13719         first.analyzing = TRUE;
13720         /*first.maybeThinking = TRUE;*/
13721         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13722         EngineOutputPopUp();
13723     }
13724     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13725     pausing = FALSE;
13726     ModeHighlight();
13727     SetGameInfo();
13728
13729     StartAnalysisClock();
13730     GetTimeMark(&lastNodeCountTime);
13731     lastNodeCount = 0;
13732     return 1;
13733 }
13734
13735 void
13736 AnalyzeFileEvent ()
13737 {
13738     if (appData.noChessProgram || gameMode == AnalyzeFile)
13739       return;
13740
13741     if (!first.analysisSupport) {
13742       char buf[MSG_SIZ];
13743       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13744       DisplayError(buf, 0);
13745       return;
13746     }
13747
13748     if (gameMode != AnalyzeMode) {
13749         keepInfo = 1; // mere annotating should not alter PGN tags
13750         EditGameEvent();
13751         keepInfo = 0;
13752         if (gameMode != EditGame) return;
13753         if (!appData.showThinking) ToggleShowThinking();
13754         ResurrectChessProgram();
13755         SendToProgram("analyze\n", &first);
13756         first.analyzing = TRUE;
13757         /*first.maybeThinking = TRUE;*/
13758         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13759         EngineOutputPopUp();
13760     }
13761     gameMode = AnalyzeFile;
13762     pausing = FALSE;
13763     ModeHighlight();
13764
13765     StartAnalysisClock();
13766     GetTimeMark(&lastNodeCountTime);
13767     lastNodeCount = 0;
13768     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13769     AnalysisPeriodicEvent(1);
13770 }
13771
13772 void
13773 MachineWhiteEvent ()
13774 {
13775     char buf[MSG_SIZ];
13776     char *bookHit = NULL;
13777
13778     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13779       return;
13780
13781
13782     if (gameMode == PlayFromGameFile ||
13783         gameMode == TwoMachinesPlay  ||
13784         gameMode == Training         ||
13785         gameMode == AnalyzeMode      ||
13786         gameMode == EndOfGame)
13787         EditGameEvent();
13788
13789     if (gameMode == EditPosition)
13790         EditPositionDone(TRUE);
13791
13792     if (!WhiteOnMove(currentMove)) {
13793         DisplayError(_("It is not White's turn"), 0);
13794         return;
13795     }
13796
13797     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13798       ExitAnalyzeMode();
13799
13800     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13801         gameMode == AnalyzeFile)
13802         TruncateGame();
13803
13804     ResurrectChessProgram();    /* in case it isn't running */
13805     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13806         gameMode = MachinePlaysWhite;
13807         ResetClocks();
13808     } else
13809     gameMode = MachinePlaysWhite;
13810     pausing = FALSE;
13811     ModeHighlight();
13812     SetGameInfo();
13813     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13814     DisplayTitle(buf);
13815     if (first.sendName) {
13816       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13817       SendToProgram(buf, &first);
13818     }
13819     if (first.sendTime) {
13820       if (first.useColors) {
13821         SendToProgram("black\n", &first); /*gnu kludge*/
13822       }
13823       SendTimeRemaining(&first, TRUE);
13824     }
13825     if (first.useColors) {
13826       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13827     }
13828     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13829     SetMachineThinkingEnables();
13830     first.maybeThinking = TRUE;
13831     StartClocks();
13832     firstMove = FALSE;
13833
13834     if (appData.autoFlipView && !flipView) {
13835       flipView = !flipView;
13836       DrawPosition(FALSE, NULL);
13837       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13838     }
13839
13840     if(bookHit) { // [HGM] book: simulate book reply
13841         static char bookMove[MSG_SIZ]; // a bit generous?
13842
13843         programStats.nodes = programStats.depth = programStats.time =
13844         programStats.score = programStats.got_only_move = 0;
13845         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13846
13847         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13848         strcat(bookMove, bookHit);
13849         HandleMachineMove(bookMove, &first);
13850     }
13851 }
13852
13853 void
13854 MachineBlackEvent ()
13855 {
13856   char buf[MSG_SIZ];
13857   char *bookHit = NULL;
13858
13859     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13860         return;
13861
13862
13863     if (gameMode == PlayFromGameFile ||
13864         gameMode == TwoMachinesPlay  ||
13865         gameMode == Training         ||
13866         gameMode == AnalyzeMode      ||
13867         gameMode == EndOfGame)
13868         EditGameEvent();
13869
13870     if (gameMode == EditPosition)
13871         EditPositionDone(TRUE);
13872
13873     if (WhiteOnMove(currentMove)) {
13874         DisplayError(_("It is not Black's turn"), 0);
13875         return;
13876     }
13877
13878     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13879       ExitAnalyzeMode();
13880
13881     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13882         gameMode == AnalyzeFile)
13883         TruncateGame();
13884
13885     ResurrectChessProgram();    /* in case it isn't running */
13886     gameMode = MachinePlaysBlack;
13887     pausing = FALSE;
13888     ModeHighlight();
13889     SetGameInfo();
13890     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13891     DisplayTitle(buf);
13892     if (first.sendName) {
13893       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13894       SendToProgram(buf, &first);
13895     }
13896     if (first.sendTime) {
13897       if (first.useColors) {
13898         SendToProgram("white\n", &first); /*gnu kludge*/
13899       }
13900       SendTimeRemaining(&first, FALSE);
13901     }
13902     if (first.useColors) {
13903       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13904     }
13905     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13906     SetMachineThinkingEnables();
13907     first.maybeThinking = TRUE;
13908     StartClocks();
13909
13910     if (appData.autoFlipView && flipView) {
13911       flipView = !flipView;
13912       DrawPosition(FALSE, NULL);
13913       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13914     }
13915     if(bookHit) { // [HGM] book: simulate book reply
13916         static char bookMove[MSG_SIZ]; // a bit generous?
13917
13918         programStats.nodes = programStats.depth = programStats.time =
13919         programStats.score = programStats.got_only_move = 0;
13920         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13921
13922         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13923         strcat(bookMove, bookHit);
13924         HandleMachineMove(bookMove, &first);
13925     }
13926 }
13927
13928
13929 void
13930 DisplayTwoMachinesTitle ()
13931 {
13932     char buf[MSG_SIZ];
13933     if (appData.matchGames > 0) {
13934         if(appData.tourneyFile[0]) {
13935           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13936                    gameInfo.white, _("vs."), gameInfo.black,
13937                    nextGame+1, appData.matchGames+1,
13938                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13939         } else
13940         if (first.twoMachinesColor[0] == 'w') {
13941           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13942                    gameInfo.white, _("vs."),  gameInfo.black,
13943                    first.matchWins, second.matchWins,
13944                    matchGame - 1 - (first.matchWins + second.matchWins));
13945         } else {
13946           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13947                    gameInfo.white, _("vs."), gameInfo.black,
13948                    second.matchWins, first.matchWins,
13949                    matchGame - 1 - (first.matchWins + second.matchWins));
13950         }
13951     } else {
13952       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13953     }
13954     DisplayTitle(buf);
13955 }
13956
13957 void
13958 SettingsMenuIfReady ()
13959 {
13960   if (second.lastPing != second.lastPong) {
13961     DisplayMessage("", _("Waiting for second chess program"));
13962     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13963     return;
13964   }
13965   ThawUI();
13966   DisplayMessage("", "");
13967   SettingsPopUp(&second);
13968 }
13969
13970 int
13971 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13972 {
13973     char buf[MSG_SIZ];
13974     if (cps->pr == NoProc) {
13975         StartChessProgram(cps);
13976         if (cps->protocolVersion == 1) {
13977           retry();
13978           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
13979         } else {
13980           /* kludge: allow timeout for initial "feature" command */
13981           if(retry != TwoMachinesEventIfReady) FreezeUI();
13982           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13983           DisplayMessage("", buf);
13984           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13985         }
13986         return 1;
13987     }
13988     return 0;
13989 }
13990
13991 void
13992 TwoMachinesEvent P((void))
13993 {
13994     int i;
13995     char buf[MSG_SIZ];
13996     ChessProgramState *onmove;
13997     char *bookHit = NULL;
13998     static int stalling = 0;
13999     TimeMark now;
14000     long wait;
14001
14002     if (appData.noChessProgram) return;
14003
14004     switch (gameMode) {
14005       case TwoMachinesPlay:
14006         return;
14007       case MachinePlaysWhite:
14008       case MachinePlaysBlack:
14009         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14010             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14011             return;
14012         }
14013         /* fall through */
14014       case BeginningOfGame:
14015       case PlayFromGameFile:
14016       case EndOfGame:
14017         EditGameEvent();
14018         if (gameMode != EditGame) return;
14019         break;
14020       case EditPosition:
14021         EditPositionDone(TRUE);
14022         break;
14023       case AnalyzeMode:
14024       case AnalyzeFile:
14025         ExitAnalyzeMode();
14026         break;
14027       case EditGame:
14028       default:
14029         break;
14030     }
14031
14032 //    forwardMostMove = currentMove;
14033     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14034     startingEngine = TRUE;
14035
14036     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14037
14038     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14039     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14040       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14041       return;
14042     }
14043     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14044
14045     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14046         startingEngine = FALSE;
14047         DisplayError("second engine does not play this", 0);
14048         return;
14049     }
14050
14051     if(!stalling) {
14052       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14053       SendToProgram("force\n", &second);
14054       stalling = 1;
14055       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14056       return;
14057     }
14058     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14059     if(appData.matchPause>10000 || appData.matchPause<10)
14060                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14061     wait = SubtractTimeMarks(&now, &pauseStart);
14062     if(wait < appData.matchPause) {
14063         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14064         return;
14065     }
14066     // we are now committed to starting the game
14067     stalling = 0;
14068     DisplayMessage("", "");
14069     if (startedFromSetupPosition) {
14070         SendBoard(&second, backwardMostMove);
14071     if (appData.debugMode) {
14072         fprintf(debugFP, "Two Machines\n");
14073     }
14074     }
14075     for (i = backwardMostMove; i < forwardMostMove; i++) {
14076         SendMoveToProgram(i, &second);
14077     }
14078
14079     gameMode = TwoMachinesPlay;
14080     pausing = startingEngine = FALSE;
14081     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14082     SetGameInfo();
14083     DisplayTwoMachinesTitle();
14084     firstMove = TRUE;
14085     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14086         onmove = &first;
14087     } else {
14088         onmove = &second;
14089     }
14090     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14091     SendToProgram(first.computerString, &first);
14092     if (first.sendName) {
14093       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14094       SendToProgram(buf, &first);
14095     }
14096     SendToProgram(second.computerString, &second);
14097     if (second.sendName) {
14098       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14099       SendToProgram(buf, &second);
14100     }
14101
14102     ResetClocks();
14103     if (!first.sendTime || !second.sendTime) {
14104         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14105         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14106     }
14107     if (onmove->sendTime) {
14108       if (onmove->useColors) {
14109         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14110       }
14111       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14112     }
14113     if (onmove->useColors) {
14114       SendToProgram(onmove->twoMachinesColor, onmove);
14115     }
14116     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14117 //    SendToProgram("go\n", onmove);
14118     onmove->maybeThinking = TRUE;
14119     SetMachineThinkingEnables();
14120
14121     StartClocks();
14122
14123     if(bookHit) { // [HGM] book: simulate book reply
14124         static char bookMove[MSG_SIZ]; // a bit generous?
14125
14126         programStats.nodes = programStats.depth = programStats.time =
14127         programStats.score = programStats.got_only_move = 0;
14128         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14129
14130         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14131         strcat(bookMove, bookHit);
14132         savedMessage = bookMove; // args for deferred call
14133         savedState = onmove;
14134         ScheduleDelayedEvent(DeferredBookMove, 1);
14135     }
14136 }
14137
14138 void
14139 TrainingEvent ()
14140 {
14141     if (gameMode == Training) {
14142       SetTrainingModeOff();
14143       gameMode = PlayFromGameFile;
14144       DisplayMessage("", _("Training mode off"));
14145     } else {
14146       gameMode = Training;
14147       animateTraining = appData.animate;
14148
14149       /* make sure we are not already at the end of the game */
14150       if (currentMove < forwardMostMove) {
14151         SetTrainingModeOn();
14152         DisplayMessage("", _("Training mode on"));
14153       } else {
14154         gameMode = PlayFromGameFile;
14155         DisplayError(_("Already at end of game"), 0);
14156       }
14157     }
14158     ModeHighlight();
14159 }
14160
14161 void
14162 IcsClientEvent ()
14163 {
14164     if (!appData.icsActive) return;
14165     switch (gameMode) {
14166       case IcsPlayingWhite:
14167       case IcsPlayingBlack:
14168       case IcsObserving:
14169       case IcsIdle:
14170       case BeginningOfGame:
14171       case IcsExamining:
14172         return;
14173
14174       case EditGame:
14175         break;
14176
14177       case EditPosition:
14178         EditPositionDone(TRUE);
14179         break;
14180
14181       case AnalyzeMode:
14182       case AnalyzeFile:
14183         ExitAnalyzeMode();
14184         break;
14185
14186       default:
14187         EditGameEvent();
14188         break;
14189     }
14190
14191     gameMode = IcsIdle;
14192     ModeHighlight();
14193     return;
14194 }
14195
14196 void
14197 EditGameEvent ()
14198 {
14199     int i;
14200
14201     switch (gameMode) {
14202       case Training:
14203         SetTrainingModeOff();
14204         break;
14205       case MachinePlaysWhite:
14206       case MachinePlaysBlack:
14207       case BeginningOfGame:
14208         SendToProgram("force\n", &first);
14209         SetUserThinkingEnables();
14210         break;
14211       case PlayFromGameFile:
14212         (void) StopLoadGameTimer();
14213         if (gameFileFP != NULL) {
14214             gameFileFP = NULL;
14215         }
14216         break;
14217       case EditPosition:
14218         EditPositionDone(TRUE);
14219         break;
14220       case AnalyzeMode:
14221       case AnalyzeFile:
14222         ExitAnalyzeMode();
14223         SendToProgram("force\n", &first);
14224         break;
14225       case TwoMachinesPlay:
14226         GameEnds(EndOfFile, NULL, GE_PLAYER);
14227         ResurrectChessProgram();
14228         SetUserThinkingEnables();
14229         break;
14230       case EndOfGame:
14231         ResurrectChessProgram();
14232         break;
14233       case IcsPlayingBlack:
14234       case IcsPlayingWhite:
14235         DisplayError(_("Warning: You are still playing a game"), 0);
14236         break;
14237       case IcsObserving:
14238         DisplayError(_("Warning: You are still observing a game"), 0);
14239         break;
14240       case IcsExamining:
14241         DisplayError(_("Warning: You are still examining a game"), 0);
14242         break;
14243       case IcsIdle:
14244         break;
14245       case EditGame:
14246       default:
14247         return;
14248     }
14249
14250     pausing = FALSE;
14251     StopClocks();
14252     first.offeredDraw = second.offeredDraw = 0;
14253
14254     if (gameMode == PlayFromGameFile) {
14255         whiteTimeRemaining = timeRemaining[0][currentMove];
14256         blackTimeRemaining = timeRemaining[1][currentMove];
14257         DisplayTitle("");
14258     }
14259
14260     if (gameMode == MachinePlaysWhite ||
14261         gameMode == MachinePlaysBlack ||
14262         gameMode == TwoMachinesPlay ||
14263         gameMode == EndOfGame) {
14264         i = forwardMostMove;
14265         while (i > currentMove) {
14266             SendToProgram("undo\n", &first);
14267             i--;
14268         }
14269         if(!adjustedClock) {
14270         whiteTimeRemaining = timeRemaining[0][currentMove];
14271         blackTimeRemaining = timeRemaining[1][currentMove];
14272         DisplayBothClocks();
14273         }
14274         if (whiteFlag || blackFlag) {
14275             whiteFlag = blackFlag = 0;
14276         }
14277         DisplayTitle("");
14278     }
14279
14280     gameMode = EditGame;
14281     ModeHighlight();
14282     SetGameInfo();
14283 }
14284
14285
14286 void
14287 EditPositionEvent ()
14288 {
14289     if (gameMode == EditPosition) {
14290         EditGameEvent();
14291         return;
14292     }
14293
14294     EditGameEvent();
14295     if (gameMode != EditGame) return;
14296
14297     gameMode = EditPosition;
14298     ModeHighlight();
14299     SetGameInfo();
14300     if (currentMove > 0)
14301       CopyBoard(boards[0], boards[currentMove]);
14302
14303     blackPlaysFirst = !WhiteOnMove(currentMove);
14304     ResetClocks();
14305     currentMove = forwardMostMove = backwardMostMove = 0;
14306     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14307     DisplayMove(-1);
14308     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14309 }
14310
14311 void
14312 ExitAnalyzeMode ()
14313 {
14314     /* [DM] icsEngineAnalyze - possible call from other functions */
14315     if (appData.icsEngineAnalyze) {
14316         appData.icsEngineAnalyze = FALSE;
14317
14318         DisplayMessage("",_("Close ICS engine analyze..."));
14319     }
14320     if (first.analysisSupport && first.analyzing) {
14321       SendToBoth("exit\n");
14322       first.analyzing = second.analyzing = FALSE;
14323     }
14324     thinkOutput[0] = NULLCHAR;
14325 }
14326
14327 void
14328 EditPositionDone (Boolean fakeRights)
14329 {
14330     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14331
14332     startedFromSetupPosition = TRUE;
14333     InitChessProgram(&first, FALSE);
14334     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14335       boards[0][EP_STATUS] = EP_NONE;
14336       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14337       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14338         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14339         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14340       } else boards[0][CASTLING][2] = NoRights;
14341       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14342         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14343         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14344       } else boards[0][CASTLING][5] = NoRights;
14345       if(gameInfo.variant == VariantSChess) {
14346         int i;
14347         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14348           boards[0][VIRGIN][i] = 0;
14349           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14350           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14351         }
14352       }
14353     }
14354     SendToProgram("force\n", &first);
14355     if (blackPlaysFirst) {
14356         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14357         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14358         currentMove = forwardMostMove = backwardMostMove = 1;
14359         CopyBoard(boards[1], boards[0]);
14360     } else {
14361         currentMove = forwardMostMove = backwardMostMove = 0;
14362     }
14363     SendBoard(&first, forwardMostMove);
14364     if (appData.debugMode) {
14365         fprintf(debugFP, "EditPosDone\n");
14366     }
14367     DisplayTitle("");
14368     DisplayMessage("", "");
14369     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14370     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14371     gameMode = EditGame;
14372     ModeHighlight();
14373     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14374     ClearHighlights(); /* [AS] */
14375 }
14376
14377 /* Pause for `ms' milliseconds */
14378 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14379 void
14380 TimeDelay (long ms)
14381 {
14382     TimeMark m1, m2;
14383
14384     GetTimeMark(&m1);
14385     do {
14386         GetTimeMark(&m2);
14387     } while (SubtractTimeMarks(&m2, &m1) < ms);
14388 }
14389
14390 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14391 void
14392 SendMultiLineToICS (char *buf)
14393 {
14394     char temp[MSG_SIZ+1], *p;
14395     int len;
14396
14397     len = strlen(buf);
14398     if (len > MSG_SIZ)
14399       len = MSG_SIZ;
14400
14401     strncpy(temp, buf, len);
14402     temp[len] = 0;
14403
14404     p = temp;
14405     while (*p) {
14406         if (*p == '\n' || *p == '\r')
14407           *p = ' ';
14408         ++p;
14409     }
14410
14411     strcat(temp, "\n");
14412     SendToICS(temp);
14413     SendToPlayer(temp, strlen(temp));
14414 }
14415
14416 void
14417 SetWhiteToPlayEvent ()
14418 {
14419     if (gameMode == EditPosition) {
14420         blackPlaysFirst = FALSE;
14421         DisplayBothClocks();    /* works because currentMove is 0 */
14422     } else if (gameMode == IcsExamining) {
14423         SendToICS(ics_prefix);
14424         SendToICS("tomove white\n");
14425     }
14426 }
14427
14428 void
14429 SetBlackToPlayEvent ()
14430 {
14431     if (gameMode == EditPosition) {
14432         blackPlaysFirst = TRUE;
14433         currentMove = 1;        /* kludge */
14434         DisplayBothClocks();
14435         currentMove = 0;
14436     } else if (gameMode == IcsExamining) {
14437         SendToICS(ics_prefix);
14438         SendToICS("tomove black\n");
14439     }
14440 }
14441
14442 void
14443 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14444 {
14445     char buf[MSG_SIZ];
14446     ChessSquare piece = boards[0][y][x];
14447
14448     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14449
14450     switch (selection) {
14451       case ClearBoard:
14452         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14453             SendToICS(ics_prefix);
14454             SendToICS("bsetup clear\n");
14455         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14456             SendToICS(ics_prefix);
14457             SendToICS("clearboard\n");
14458         } else {
14459             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14460                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14461                 for (y = 0; y < BOARD_HEIGHT; y++) {
14462                     if (gameMode == IcsExamining) {
14463                         if (boards[currentMove][y][x] != EmptySquare) {
14464                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14465                                     AAA + x, ONE + y);
14466                             SendToICS(buf);
14467                         }
14468                     } else {
14469                         boards[0][y][x] = p;
14470                     }
14471                 }
14472             }
14473         }
14474         if (gameMode == EditPosition) {
14475             DrawPosition(FALSE, boards[0]);
14476         }
14477         break;
14478
14479       case WhitePlay:
14480         SetWhiteToPlayEvent();
14481         break;
14482
14483       case BlackPlay:
14484         SetBlackToPlayEvent();
14485         break;
14486
14487       case EmptySquare:
14488         if (gameMode == IcsExamining) {
14489             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14490             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14491             SendToICS(buf);
14492         } else {
14493             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14494                 if(x == BOARD_LEFT-2) {
14495                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14496                     boards[0][y][1] = 0;
14497                 } else
14498                 if(x == BOARD_RGHT+1) {
14499                     if(y >= gameInfo.holdingsSize) break;
14500                     boards[0][y][BOARD_WIDTH-2] = 0;
14501                 } else break;
14502             }
14503             boards[0][y][x] = EmptySquare;
14504             DrawPosition(FALSE, boards[0]);
14505         }
14506         break;
14507
14508       case PromotePiece:
14509         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14510            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14511             selection = (ChessSquare) (PROMOTED piece);
14512         } else if(piece == EmptySquare) selection = WhiteSilver;
14513         else selection = (ChessSquare)((int)piece - 1);
14514         goto defaultlabel;
14515
14516       case DemotePiece:
14517         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14518            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14519             selection = (ChessSquare) (DEMOTED piece);
14520         } else if(piece == EmptySquare) selection = BlackSilver;
14521         else selection = (ChessSquare)((int)piece + 1);
14522         goto defaultlabel;
14523
14524       case WhiteQueen:
14525       case BlackQueen:
14526         if(gameInfo.variant == VariantShatranj ||
14527            gameInfo.variant == VariantXiangqi  ||
14528            gameInfo.variant == VariantCourier  ||
14529            gameInfo.variant == VariantASEAN    ||
14530            gameInfo.variant == VariantMakruk     )
14531             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14532         goto defaultlabel;
14533
14534       case WhiteKing:
14535       case BlackKing:
14536         if(gameInfo.variant == VariantXiangqi)
14537             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14538         if(gameInfo.variant == VariantKnightmate)
14539             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14540       default:
14541         defaultlabel:
14542         if (gameMode == IcsExamining) {
14543             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14544             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14545                      PieceToChar(selection), AAA + x, ONE + y);
14546             SendToICS(buf);
14547         } else {
14548             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14549                 int n;
14550                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14551                     n = PieceToNumber(selection - BlackPawn);
14552                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14553                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14554                     boards[0][BOARD_HEIGHT-1-n][1]++;
14555                 } else
14556                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14557                     n = PieceToNumber(selection);
14558                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14559                     boards[0][n][BOARD_WIDTH-1] = selection;
14560                     boards[0][n][BOARD_WIDTH-2]++;
14561                 }
14562             } else
14563             boards[0][y][x] = selection;
14564             DrawPosition(TRUE, boards[0]);
14565             ClearHighlights();
14566             fromX = fromY = -1;
14567         }
14568         break;
14569     }
14570 }
14571
14572
14573 void
14574 DropMenuEvent (ChessSquare selection, int x, int y)
14575 {
14576     ChessMove moveType;
14577
14578     switch (gameMode) {
14579       case IcsPlayingWhite:
14580       case MachinePlaysBlack:
14581         if (!WhiteOnMove(currentMove)) {
14582             DisplayMoveError(_("It is Black's turn"));
14583             return;
14584         }
14585         moveType = WhiteDrop;
14586         break;
14587       case IcsPlayingBlack:
14588       case MachinePlaysWhite:
14589         if (WhiteOnMove(currentMove)) {
14590             DisplayMoveError(_("It is White's turn"));
14591             return;
14592         }
14593         moveType = BlackDrop;
14594         break;
14595       case EditGame:
14596         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14597         break;
14598       default:
14599         return;
14600     }
14601
14602     if (moveType == BlackDrop && selection < BlackPawn) {
14603       selection = (ChessSquare) ((int) selection
14604                                  + (int) BlackPawn - (int) WhitePawn);
14605     }
14606     if (boards[currentMove][y][x] != EmptySquare) {
14607         DisplayMoveError(_("That square is occupied"));
14608         return;
14609     }
14610
14611     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14612 }
14613
14614 void
14615 AcceptEvent ()
14616 {
14617     /* Accept a pending offer of any kind from opponent */
14618
14619     if (appData.icsActive) {
14620         SendToICS(ics_prefix);
14621         SendToICS("accept\n");
14622     } else if (cmailMsgLoaded) {
14623         if (currentMove == cmailOldMove &&
14624             commentList[cmailOldMove] != NULL &&
14625             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14626                    "Black offers a draw" : "White offers a draw")) {
14627             TruncateGame();
14628             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14629             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14630         } else {
14631             DisplayError(_("There is no pending offer on this move"), 0);
14632             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14633         }
14634     } else {
14635         /* Not used for offers from chess program */
14636     }
14637 }
14638
14639 void
14640 DeclineEvent ()
14641 {
14642     /* Decline a pending offer of any kind from opponent */
14643
14644     if (appData.icsActive) {
14645         SendToICS(ics_prefix);
14646         SendToICS("decline\n");
14647     } else if (cmailMsgLoaded) {
14648         if (currentMove == cmailOldMove &&
14649             commentList[cmailOldMove] != NULL &&
14650             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14651                    "Black offers a draw" : "White offers a draw")) {
14652 #ifdef NOTDEF
14653             AppendComment(cmailOldMove, "Draw declined", TRUE);
14654             DisplayComment(cmailOldMove - 1, "Draw declined");
14655 #endif /*NOTDEF*/
14656         } else {
14657             DisplayError(_("There is no pending offer on this move"), 0);
14658         }
14659     } else {
14660         /* Not used for offers from chess program */
14661     }
14662 }
14663
14664 void
14665 RematchEvent ()
14666 {
14667     /* Issue ICS rematch command */
14668     if (appData.icsActive) {
14669         SendToICS(ics_prefix);
14670         SendToICS("rematch\n");
14671     }
14672 }
14673
14674 void
14675 CallFlagEvent ()
14676 {
14677     /* Call your opponent's flag (claim a win on time) */
14678     if (appData.icsActive) {
14679         SendToICS(ics_prefix);
14680         SendToICS("flag\n");
14681     } else {
14682         switch (gameMode) {
14683           default:
14684             return;
14685           case MachinePlaysWhite:
14686             if (whiteFlag) {
14687                 if (blackFlag)
14688                   GameEnds(GameIsDrawn, "Both players ran out of time",
14689                            GE_PLAYER);
14690                 else
14691                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14692             } else {
14693                 DisplayError(_("Your opponent is not out of time"), 0);
14694             }
14695             break;
14696           case MachinePlaysBlack:
14697             if (blackFlag) {
14698                 if (whiteFlag)
14699                   GameEnds(GameIsDrawn, "Both players ran out of time",
14700                            GE_PLAYER);
14701                 else
14702                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14703             } else {
14704                 DisplayError(_("Your opponent is not out of time"), 0);
14705             }
14706             break;
14707         }
14708     }
14709 }
14710
14711 void
14712 ClockClick (int which)
14713 {       // [HGM] code moved to back-end from winboard.c
14714         if(which) { // black clock
14715           if (gameMode == EditPosition || gameMode == IcsExamining) {
14716             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14717             SetBlackToPlayEvent();
14718           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14719           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14720           } else if (shiftKey) {
14721             AdjustClock(which, -1);
14722           } else if (gameMode == IcsPlayingWhite ||
14723                      gameMode == MachinePlaysBlack) {
14724             CallFlagEvent();
14725           }
14726         } else { // white clock
14727           if (gameMode == EditPosition || gameMode == IcsExamining) {
14728             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14729             SetWhiteToPlayEvent();
14730           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14731           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14732           } else if (shiftKey) {
14733             AdjustClock(which, -1);
14734           } else if (gameMode == IcsPlayingBlack ||
14735                    gameMode == MachinePlaysWhite) {
14736             CallFlagEvent();
14737           }
14738         }
14739 }
14740
14741 void
14742 DrawEvent ()
14743 {
14744     /* Offer draw or accept pending draw offer from opponent */
14745
14746     if (appData.icsActive) {
14747         /* Note: tournament rules require draw offers to be
14748            made after you make your move but before you punch
14749            your clock.  Currently ICS doesn't let you do that;
14750            instead, you immediately punch your clock after making
14751            a move, but you can offer a draw at any time. */
14752
14753         SendToICS(ics_prefix);
14754         SendToICS("draw\n");
14755         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14756     } else if (cmailMsgLoaded) {
14757         if (currentMove == cmailOldMove &&
14758             commentList[cmailOldMove] != NULL &&
14759             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14760                    "Black offers a draw" : "White offers a draw")) {
14761             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14762             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14763         } else if (currentMove == cmailOldMove + 1) {
14764             char *offer = WhiteOnMove(cmailOldMove) ?
14765               "White offers a draw" : "Black offers a draw";
14766             AppendComment(currentMove, offer, TRUE);
14767             DisplayComment(currentMove - 1, offer);
14768             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14769         } else {
14770             DisplayError(_("You must make your move before offering a draw"), 0);
14771             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14772         }
14773     } else if (first.offeredDraw) {
14774         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14775     } else {
14776         if (first.sendDrawOffers) {
14777             SendToProgram("draw\n", &first);
14778             userOfferedDraw = TRUE;
14779         }
14780     }
14781 }
14782
14783 void
14784 AdjournEvent ()
14785 {
14786     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14787
14788     if (appData.icsActive) {
14789         SendToICS(ics_prefix);
14790         SendToICS("adjourn\n");
14791     } else {
14792         /* Currently GNU Chess doesn't offer or accept Adjourns */
14793     }
14794 }
14795
14796
14797 void
14798 AbortEvent ()
14799 {
14800     /* Offer Abort or accept pending Abort offer from opponent */
14801
14802     if (appData.icsActive) {
14803         SendToICS(ics_prefix);
14804         SendToICS("abort\n");
14805     } else {
14806         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14807     }
14808 }
14809
14810 void
14811 ResignEvent ()
14812 {
14813     /* Resign.  You can do this even if it's not your turn. */
14814
14815     if (appData.icsActive) {
14816         SendToICS(ics_prefix);
14817         SendToICS("resign\n");
14818     } else {
14819         switch (gameMode) {
14820           case MachinePlaysWhite:
14821             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14822             break;
14823           case MachinePlaysBlack:
14824             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14825             break;
14826           case EditGame:
14827             if (cmailMsgLoaded) {
14828                 TruncateGame();
14829                 if (WhiteOnMove(cmailOldMove)) {
14830                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14831                 } else {
14832                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14833                 }
14834                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14835             }
14836             break;
14837           default:
14838             break;
14839         }
14840     }
14841 }
14842
14843
14844 void
14845 StopObservingEvent ()
14846 {
14847     /* Stop observing current games */
14848     SendToICS(ics_prefix);
14849     SendToICS("unobserve\n");
14850 }
14851
14852 void
14853 StopExaminingEvent ()
14854 {
14855     /* Stop observing current game */
14856     SendToICS(ics_prefix);
14857     SendToICS("unexamine\n");
14858 }
14859
14860 void
14861 ForwardInner (int target)
14862 {
14863     int limit; int oldSeekGraphUp = seekGraphUp;
14864
14865     if (appData.debugMode)
14866         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14867                 target, currentMove, forwardMostMove);
14868
14869     if (gameMode == EditPosition)
14870       return;
14871
14872     seekGraphUp = FALSE;
14873     MarkTargetSquares(1);
14874
14875     if (gameMode == PlayFromGameFile && !pausing)
14876       PauseEvent();
14877
14878     if (gameMode == IcsExamining && pausing)
14879       limit = pauseExamForwardMostMove;
14880     else
14881       limit = forwardMostMove;
14882
14883     if (target > limit) target = limit;
14884
14885     if (target > 0 && moveList[target - 1][0]) {
14886         int fromX, fromY, toX, toY;
14887         toX = moveList[target - 1][2] - AAA;
14888         toY = moveList[target - 1][3] - ONE;
14889         if (moveList[target - 1][1] == '@') {
14890             if (appData.highlightLastMove) {
14891                 SetHighlights(-1, -1, toX, toY);
14892             }
14893         } else {
14894             fromX = moveList[target - 1][0] - AAA;
14895             fromY = moveList[target - 1][1] - ONE;
14896             if (target == currentMove + 1) {
14897                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14898             }
14899             if (appData.highlightLastMove) {
14900                 SetHighlights(fromX, fromY, toX, toY);
14901             }
14902         }
14903     }
14904     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14905         gameMode == Training || gameMode == PlayFromGameFile ||
14906         gameMode == AnalyzeFile) {
14907         while (currentMove < target) {
14908             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14909             SendMoveToProgram(currentMove++, &first);
14910         }
14911     } else {
14912         currentMove = target;
14913     }
14914
14915     if (gameMode == EditGame || gameMode == EndOfGame) {
14916         whiteTimeRemaining = timeRemaining[0][currentMove];
14917         blackTimeRemaining = timeRemaining[1][currentMove];
14918     }
14919     DisplayBothClocks();
14920     DisplayMove(currentMove - 1);
14921     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14922     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14923     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14924         DisplayComment(currentMove - 1, commentList[currentMove]);
14925     }
14926     ClearMap(); // [HGM] exclude: invalidate map
14927 }
14928
14929
14930 void
14931 ForwardEvent ()
14932 {
14933     if (gameMode == IcsExamining && !pausing) {
14934         SendToICS(ics_prefix);
14935         SendToICS("forward\n");
14936     } else {
14937         ForwardInner(currentMove + 1);
14938     }
14939 }
14940
14941 void
14942 ToEndEvent ()
14943 {
14944     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14945         /* to optimze, we temporarily turn off analysis mode while we feed
14946          * the remaining moves to the engine. Otherwise we get analysis output
14947          * after each move.
14948          */
14949         if (first.analysisSupport) {
14950           SendToProgram("exit\nforce\n", &first);
14951           first.analyzing = FALSE;
14952         }
14953     }
14954
14955     if (gameMode == IcsExamining && !pausing) {
14956         SendToICS(ics_prefix);
14957         SendToICS("forward 999999\n");
14958     } else {
14959         ForwardInner(forwardMostMove);
14960     }
14961
14962     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14963         /* we have fed all the moves, so reactivate analysis mode */
14964         SendToProgram("analyze\n", &first);
14965         first.analyzing = TRUE;
14966         /*first.maybeThinking = TRUE;*/
14967         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14968     }
14969 }
14970
14971 void
14972 BackwardInner (int target)
14973 {
14974     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14975
14976     if (appData.debugMode)
14977         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14978                 target, currentMove, forwardMostMove);
14979
14980     if (gameMode == EditPosition) return;
14981     seekGraphUp = FALSE;
14982     MarkTargetSquares(1);
14983     if (currentMove <= backwardMostMove) {
14984         ClearHighlights();
14985         DrawPosition(full_redraw, boards[currentMove]);
14986         return;
14987     }
14988     if (gameMode == PlayFromGameFile && !pausing)
14989       PauseEvent();
14990
14991     if (moveList[target][0]) {
14992         int fromX, fromY, toX, toY;
14993         toX = moveList[target][2] - AAA;
14994         toY = moveList[target][3] - ONE;
14995         if (moveList[target][1] == '@') {
14996             if (appData.highlightLastMove) {
14997                 SetHighlights(-1, -1, toX, toY);
14998             }
14999         } else {
15000             fromX = moveList[target][0] - AAA;
15001             fromY = moveList[target][1] - ONE;
15002             if (target == currentMove - 1) {
15003                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15004             }
15005             if (appData.highlightLastMove) {
15006                 SetHighlights(fromX, fromY, toX, toY);
15007             }
15008         }
15009     }
15010     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15011         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15012         while (currentMove > target) {
15013             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15014                 // null move cannot be undone. Reload program with move history before it.
15015                 int i;
15016                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15017                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15018                 }
15019                 SendBoard(&first, i);
15020               if(second.analyzing) SendBoard(&second, i);
15021                 for(currentMove=i; currentMove<target; currentMove++) {
15022                     SendMoveToProgram(currentMove, &first);
15023                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15024                 }
15025                 break;
15026             }
15027             SendToBoth("undo\n");
15028             currentMove--;
15029         }
15030     } else {
15031         currentMove = target;
15032     }
15033
15034     if (gameMode == EditGame || gameMode == EndOfGame) {
15035         whiteTimeRemaining = timeRemaining[0][currentMove];
15036         blackTimeRemaining = timeRemaining[1][currentMove];
15037     }
15038     DisplayBothClocks();
15039     DisplayMove(currentMove - 1);
15040     DrawPosition(full_redraw, boards[currentMove]);
15041     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15042     // [HGM] PV info: routine tests if comment empty
15043     DisplayComment(currentMove - 1, commentList[currentMove]);
15044     ClearMap(); // [HGM] exclude: invalidate map
15045 }
15046
15047 void
15048 BackwardEvent ()
15049 {
15050     if (gameMode == IcsExamining && !pausing) {
15051         SendToICS(ics_prefix);
15052         SendToICS("backward\n");
15053     } else {
15054         BackwardInner(currentMove - 1);
15055     }
15056 }
15057
15058 void
15059 ToStartEvent ()
15060 {
15061     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15062         /* to optimize, we temporarily turn off analysis mode while we undo
15063          * all the moves. Otherwise we get analysis output after each undo.
15064          */
15065         if (first.analysisSupport) {
15066           SendToProgram("exit\nforce\n", &first);
15067           first.analyzing = FALSE;
15068         }
15069     }
15070
15071     if (gameMode == IcsExamining && !pausing) {
15072         SendToICS(ics_prefix);
15073         SendToICS("backward 999999\n");
15074     } else {
15075         BackwardInner(backwardMostMove);
15076     }
15077
15078     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15079         /* we have fed all the moves, so reactivate analysis mode */
15080         SendToProgram("analyze\n", &first);
15081         first.analyzing = TRUE;
15082         /*first.maybeThinking = TRUE;*/
15083         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15084     }
15085 }
15086
15087 void
15088 ToNrEvent (int to)
15089 {
15090   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15091   if (to >= forwardMostMove) to = forwardMostMove;
15092   if (to <= backwardMostMove) to = backwardMostMove;
15093   if (to < currentMove) {
15094     BackwardInner(to);
15095   } else {
15096     ForwardInner(to);
15097   }
15098 }
15099
15100 void
15101 RevertEvent (Boolean annotate)
15102 {
15103     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15104         return;
15105     }
15106     if (gameMode != IcsExamining) {
15107         DisplayError(_("You are not examining a game"), 0);
15108         return;
15109     }
15110     if (pausing) {
15111         DisplayError(_("You can't revert while pausing"), 0);
15112         return;
15113     }
15114     SendToICS(ics_prefix);
15115     SendToICS("revert\n");
15116 }
15117
15118 void
15119 RetractMoveEvent ()
15120 {
15121     switch (gameMode) {
15122       case MachinePlaysWhite:
15123       case MachinePlaysBlack:
15124         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15125             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15126             return;
15127         }
15128         if (forwardMostMove < 2) return;
15129         currentMove = forwardMostMove = forwardMostMove - 2;
15130         whiteTimeRemaining = timeRemaining[0][currentMove];
15131         blackTimeRemaining = timeRemaining[1][currentMove];
15132         DisplayBothClocks();
15133         DisplayMove(currentMove - 1);
15134         ClearHighlights();/*!! could figure this out*/
15135         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15136         SendToProgram("remove\n", &first);
15137         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15138         break;
15139
15140       case BeginningOfGame:
15141       default:
15142         break;
15143
15144       case IcsPlayingWhite:
15145       case IcsPlayingBlack:
15146         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15147             SendToICS(ics_prefix);
15148             SendToICS("takeback 2\n");
15149         } else {
15150             SendToICS(ics_prefix);
15151             SendToICS("takeback 1\n");
15152         }
15153         break;
15154     }
15155 }
15156
15157 void
15158 MoveNowEvent ()
15159 {
15160     ChessProgramState *cps;
15161
15162     switch (gameMode) {
15163       case MachinePlaysWhite:
15164         if (!WhiteOnMove(forwardMostMove)) {
15165             DisplayError(_("It is your turn"), 0);
15166             return;
15167         }
15168         cps = &first;
15169         break;
15170       case MachinePlaysBlack:
15171         if (WhiteOnMove(forwardMostMove)) {
15172             DisplayError(_("It is your turn"), 0);
15173             return;
15174         }
15175         cps = &first;
15176         break;
15177       case TwoMachinesPlay:
15178         if (WhiteOnMove(forwardMostMove) ==
15179             (first.twoMachinesColor[0] == 'w')) {
15180             cps = &first;
15181         } else {
15182             cps = &second;
15183         }
15184         break;
15185       case BeginningOfGame:
15186       default:
15187         return;
15188     }
15189     SendToProgram("?\n", cps);
15190 }
15191
15192 void
15193 TruncateGameEvent ()
15194 {
15195     EditGameEvent();
15196     if (gameMode != EditGame) return;
15197     TruncateGame();
15198 }
15199
15200 void
15201 TruncateGame ()
15202 {
15203     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15204     if (forwardMostMove > currentMove) {
15205         if (gameInfo.resultDetails != NULL) {
15206             free(gameInfo.resultDetails);
15207             gameInfo.resultDetails = NULL;
15208             gameInfo.result = GameUnfinished;
15209         }
15210         forwardMostMove = currentMove;
15211         HistorySet(parseList, backwardMostMove, forwardMostMove,
15212                    currentMove-1);
15213     }
15214 }
15215
15216 void
15217 HintEvent ()
15218 {
15219     if (appData.noChessProgram) return;
15220     switch (gameMode) {
15221       case MachinePlaysWhite:
15222         if (WhiteOnMove(forwardMostMove)) {
15223             DisplayError(_("Wait until your turn"), 0);
15224             return;
15225         }
15226         break;
15227       case BeginningOfGame:
15228       case MachinePlaysBlack:
15229         if (!WhiteOnMove(forwardMostMove)) {
15230             DisplayError(_("Wait until your turn"), 0);
15231             return;
15232         }
15233         break;
15234       default:
15235         DisplayError(_("No hint available"), 0);
15236         return;
15237     }
15238     SendToProgram("hint\n", &first);
15239     hintRequested = TRUE;
15240 }
15241
15242 void
15243 CreateBookEvent ()
15244 {
15245     ListGame * lg = (ListGame *) gameList.head;
15246     FILE *f;
15247     int nItem;
15248     static int secondTime = FALSE;
15249
15250     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15251         DisplayError(_("Game list not loaded or empty"), 0);
15252         return;
15253     }
15254
15255     if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) {
15256         fclose(f);
15257         secondTime++;
15258         DisplayNote(_("Book file exists! Try again for overwrite."));
15259         return;
15260     }
15261
15262     creatingBook = TRUE;
15263     secondTime = FALSE;
15264
15265     /* Get list size */
15266     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15267         LoadGame(f, nItem, "", TRUE);
15268         AddGameToBook(TRUE);
15269         lg = (ListGame *) lg->node.succ;
15270     }
15271
15272     creatingBook = FALSE;
15273     FlushBook();
15274 }
15275
15276 void
15277 BookEvent ()
15278 {
15279     if (appData.noChessProgram) return;
15280     switch (gameMode) {
15281       case MachinePlaysWhite:
15282         if (WhiteOnMove(forwardMostMove)) {
15283             DisplayError(_("Wait until your turn"), 0);
15284             return;
15285         }
15286         break;
15287       case BeginningOfGame:
15288       case MachinePlaysBlack:
15289         if (!WhiteOnMove(forwardMostMove)) {
15290             DisplayError(_("Wait until your turn"), 0);
15291             return;
15292         }
15293         break;
15294       case EditPosition:
15295         EditPositionDone(TRUE);
15296         break;
15297       case TwoMachinesPlay:
15298         return;
15299       default:
15300         break;
15301     }
15302     SendToProgram("bk\n", &first);
15303     bookOutput[0] = NULLCHAR;
15304     bookRequested = TRUE;
15305 }
15306
15307 void
15308 AboutGameEvent ()
15309 {
15310     char *tags = PGNTags(&gameInfo);
15311     TagsPopUp(tags, CmailMsg());
15312     free(tags);
15313 }
15314
15315 /* end button procedures */
15316
15317 void
15318 PrintPosition (FILE *fp, int move)
15319 {
15320     int i, j;
15321
15322     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15323         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15324             char c = PieceToChar(boards[move][i][j]);
15325             fputc(c == 'x' ? '.' : c, fp);
15326             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15327         }
15328     }
15329     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15330       fprintf(fp, "white to play\n");
15331     else
15332       fprintf(fp, "black to play\n");
15333 }
15334
15335 void
15336 PrintOpponents (FILE *fp)
15337 {
15338     if (gameInfo.white != NULL) {
15339         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15340     } else {
15341         fprintf(fp, "\n");
15342     }
15343 }
15344
15345 /* Find last component of program's own name, using some heuristics */
15346 void
15347 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15348 {
15349     char *p, *q, c;
15350     int local = (strcmp(host, "localhost") == 0);
15351     while (!local && (p = strchr(prog, ';')) != NULL) {
15352         p++;
15353         while (*p == ' ') p++;
15354         prog = p;
15355     }
15356     if (*prog == '"' || *prog == '\'') {
15357         q = strchr(prog + 1, *prog);
15358     } else {
15359         q = strchr(prog, ' ');
15360     }
15361     if (q == NULL) q = prog + strlen(prog);
15362     p = q;
15363     while (p >= prog && *p != '/' && *p != '\\') p--;
15364     p++;
15365     if(p == prog && *p == '"') p++;
15366     c = *q; *q = 0;
15367     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15368     memcpy(buf, p, q - p);
15369     buf[q - p] = NULLCHAR;
15370     if (!local) {
15371         strcat(buf, "@");
15372         strcat(buf, host);
15373     }
15374 }
15375
15376 char *
15377 TimeControlTagValue ()
15378 {
15379     char buf[MSG_SIZ];
15380     if (!appData.clockMode) {
15381       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15382     } else if (movesPerSession > 0) {
15383       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15384     } else if (timeIncrement == 0) {
15385       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15386     } else {
15387       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15388     }
15389     return StrSave(buf);
15390 }
15391
15392 void
15393 SetGameInfo ()
15394 {
15395     /* This routine is used only for certain modes */
15396     VariantClass v = gameInfo.variant;
15397     ChessMove r = GameUnfinished;
15398     char *p = NULL;
15399
15400     if(keepInfo) return;
15401
15402     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15403         r = gameInfo.result;
15404         p = gameInfo.resultDetails;
15405         gameInfo.resultDetails = NULL;
15406     }
15407     ClearGameInfo(&gameInfo);
15408     gameInfo.variant = v;
15409
15410     switch (gameMode) {
15411       case MachinePlaysWhite:
15412         gameInfo.event = StrSave( appData.pgnEventHeader );
15413         gameInfo.site = StrSave(HostName());
15414         gameInfo.date = PGNDate();
15415         gameInfo.round = StrSave("-");
15416         gameInfo.white = StrSave(first.tidy);
15417         gameInfo.black = StrSave(UserName());
15418         gameInfo.timeControl = TimeControlTagValue();
15419         break;
15420
15421       case MachinePlaysBlack:
15422         gameInfo.event = StrSave( appData.pgnEventHeader );
15423         gameInfo.site = StrSave(HostName());
15424         gameInfo.date = PGNDate();
15425         gameInfo.round = StrSave("-");
15426         gameInfo.white = StrSave(UserName());
15427         gameInfo.black = StrSave(first.tidy);
15428         gameInfo.timeControl = TimeControlTagValue();
15429         break;
15430
15431       case TwoMachinesPlay:
15432         gameInfo.event = StrSave( appData.pgnEventHeader );
15433         gameInfo.site = StrSave(HostName());
15434         gameInfo.date = PGNDate();
15435         if (roundNr > 0) {
15436             char buf[MSG_SIZ];
15437             snprintf(buf, MSG_SIZ, "%d", roundNr);
15438             gameInfo.round = StrSave(buf);
15439         } else {
15440             gameInfo.round = StrSave("-");
15441         }
15442         if (first.twoMachinesColor[0] == 'w') {
15443             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15444             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15445         } else {
15446             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15447             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15448         }
15449         gameInfo.timeControl = TimeControlTagValue();
15450         break;
15451
15452       case EditGame:
15453         gameInfo.event = StrSave("Edited game");
15454         gameInfo.site = StrSave(HostName());
15455         gameInfo.date = PGNDate();
15456         gameInfo.round = StrSave("-");
15457         gameInfo.white = StrSave("-");
15458         gameInfo.black = StrSave("-");
15459         gameInfo.result = r;
15460         gameInfo.resultDetails = p;
15461         break;
15462
15463       case EditPosition:
15464         gameInfo.event = StrSave("Edited position");
15465         gameInfo.site = StrSave(HostName());
15466         gameInfo.date = PGNDate();
15467         gameInfo.round = StrSave("-");
15468         gameInfo.white = StrSave("-");
15469         gameInfo.black = StrSave("-");
15470         break;
15471
15472       case IcsPlayingWhite:
15473       case IcsPlayingBlack:
15474       case IcsObserving:
15475       case IcsExamining:
15476         break;
15477
15478       case PlayFromGameFile:
15479         gameInfo.event = StrSave("Game from non-PGN file");
15480         gameInfo.site = StrSave(HostName());
15481         gameInfo.date = PGNDate();
15482         gameInfo.round = StrSave("-");
15483         gameInfo.white = StrSave("?");
15484         gameInfo.black = StrSave("?");
15485         break;
15486
15487       default:
15488         break;
15489     }
15490 }
15491
15492 void
15493 ReplaceComment (int index, char *text)
15494 {
15495     int len;
15496     char *p;
15497     float score;
15498
15499     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15500        pvInfoList[index-1].depth == len &&
15501        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15502        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15503     while (*text == '\n') text++;
15504     len = strlen(text);
15505     while (len > 0 && text[len - 1] == '\n') len--;
15506
15507     if (commentList[index] != NULL)
15508       free(commentList[index]);
15509
15510     if (len == 0) {
15511         commentList[index] = NULL;
15512         return;
15513     }
15514   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15515       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15516       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15517     commentList[index] = (char *) malloc(len + 2);
15518     strncpy(commentList[index], text, len);
15519     commentList[index][len] = '\n';
15520     commentList[index][len + 1] = NULLCHAR;
15521   } else {
15522     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15523     char *p;
15524     commentList[index] = (char *) malloc(len + 7);
15525     safeStrCpy(commentList[index], "{\n", 3);
15526     safeStrCpy(commentList[index]+2, text, len+1);
15527     commentList[index][len+2] = NULLCHAR;
15528     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15529     strcat(commentList[index], "\n}\n");
15530   }
15531 }
15532
15533 void
15534 CrushCRs (char *text)
15535 {
15536   char *p = text;
15537   char *q = text;
15538   char ch;
15539
15540   do {
15541     ch = *p++;
15542     if (ch == '\r') continue;
15543     *q++ = ch;
15544   } while (ch != '\0');
15545 }
15546
15547 void
15548 AppendComment (int index, char *text, Boolean addBraces)
15549 /* addBraces  tells if we should add {} */
15550 {
15551     int oldlen, len;
15552     char *old;
15553
15554 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15555     if(addBraces == 3) addBraces = 0; else // force appending literally
15556     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15557
15558     CrushCRs(text);
15559     while (*text == '\n') text++;
15560     len = strlen(text);
15561     while (len > 0 && text[len - 1] == '\n') len--;
15562     text[len] = NULLCHAR;
15563
15564     if (len == 0) return;
15565
15566     if (commentList[index] != NULL) {
15567       Boolean addClosingBrace = addBraces;
15568         old = commentList[index];
15569         oldlen = strlen(old);
15570         while(commentList[index][oldlen-1] ==  '\n')
15571           commentList[index][--oldlen] = NULLCHAR;
15572         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15573         safeStrCpy(commentList[index], old, oldlen + len + 6);
15574         free(old);
15575         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15576         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15577           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15578           while (*text == '\n') { text++; len--; }
15579           commentList[index][--oldlen] = NULLCHAR;
15580       }
15581         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15582         else          strcat(commentList[index], "\n");
15583         strcat(commentList[index], text);
15584         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15585         else          strcat(commentList[index], "\n");
15586     } else {
15587         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15588         if(addBraces)
15589           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15590         else commentList[index][0] = NULLCHAR;
15591         strcat(commentList[index], text);
15592         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15593         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15594     }
15595 }
15596
15597 static char *
15598 FindStr (char * text, char * sub_text)
15599 {
15600     char * result = strstr( text, sub_text );
15601
15602     if( result != NULL ) {
15603         result += strlen( sub_text );
15604     }
15605
15606     return result;
15607 }
15608
15609 /* [AS] Try to extract PV info from PGN comment */
15610 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15611 char *
15612 GetInfoFromComment (int index, char * text)
15613 {
15614     char * sep = text, *p;
15615
15616     if( text != NULL && index > 0 ) {
15617         int score = 0;
15618         int depth = 0;
15619         int time = -1, sec = 0, deci;
15620         char * s_eval = FindStr( text, "[%eval " );
15621         char * s_emt = FindStr( text, "[%emt " );
15622 #if 0
15623         if( s_eval != NULL || s_emt != NULL ) {
15624 #else
15625         if(0) { // [HGM] this code is not finished, and could actually be detrimental
15626 #endif
15627             /* New style */
15628             char delim;
15629
15630             if( s_eval != NULL ) {
15631                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15632                     return text;
15633                 }
15634
15635                 if( delim != ']' ) {
15636                     return text;
15637                 }
15638             }
15639
15640             if( s_emt != NULL ) {
15641             }
15642                 return text;
15643         }
15644         else {
15645             /* We expect something like: [+|-]nnn.nn/dd */
15646             int score_lo = 0;
15647
15648             if(*text != '{') return text; // [HGM] braces: must be normal comment
15649
15650             sep = strchr( text, '/' );
15651             if( sep == NULL || sep < (text+4) ) {
15652                 return text;
15653             }
15654
15655             p = text;
15656             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15657             if(p[1] == '(') { // comment starts with PV
15658                p = strchr(p, ')'); // locate end of PV
15659                if(p == NULL || sep < p+5) return text;
15660                // at this point we have something like "{(.*) +0.23/6 ..."
15661                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15662                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15663                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15664             }
15665             time = -1; sec = -1; deci = -1;
15666             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15667                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15668                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15669                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15670                 return text;
15671             }
15672
15673             if( score_lo < 0 || score_lo >= 100 ) {
15674                 return text;
15675             }
15676
15677             if(sec >= 0) time = 600*time + 10*sec; else
15678             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15679
15680             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
15681
15682             /* [HGM] PV time: now locate end of PV info */
15683             while( *++sep >= '0' && *sep <= '9'); // strip depth
15684             if(time >= 0)
15685             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15686             if(sec >= 0)
15687             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15688             if(deci >= 0)
15689             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15690             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15691         }
15692
15693         if( depth <= 0 ) {
15694             return text;
15695         }
15696
15697         if( time < 0 ) {
15698             time = -1;
15699         }
15700
15701         pvInfoList[index-1].depth = depth;
15702         pvInfoList[index-1].score = score;
15703         pvInfoList[index-1].time  = 10*time; // centi-sec
15704         if(*sep == '}') *sep = 0; else *--sep = '{';
15705         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15706     }
15707     return sep;
15708 }
15709
15710 void
15711 SendToProgram (char *message, ChessProgramState *cps)
15712 {
15713     int count, outCount, error;
15714     char buf[MSG_SIZ];
15715
15716     if (cps->pr == NoProc) return;
15717     Attention(cps);
15718
15719     if (appData.debugMode) {
15720         TimeMark now;
15721         GetTimeMark(&now);
15722         fprintf(debugFP, "%ld >%-6s: %s",
15723                 SubtractTimeMarks(&now, &programStartTime),
15724                 cps->which, message);
15725         if(serverFP)
15726             fprintf(serverFP, "%ld >%-6s: %s",
15727                 SubtractTimeMarks(&now, &programStartTime),
15728                 cps->which, message), fflush(serverFP);
15729     }
15730
15731     count = strlen(message);
15732     outCount = OutputToProcess(cps->pr, message, count, &error);
15733     if (outCount < count && !exiting
15734                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15735       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15736       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15737         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15738             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15739                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15740                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15741                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15742             } else {
15743                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15744                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15745                 gameInfo.result = res;
15746             }
15747             gameInfo.resultDetails = StrSave(buf);
15748         }
15749         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15750         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15751     }
15752 }
15753
15754 void
15755 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15756 {
15757     char *end_str;
15758     char buf[MSG_SIZ];
15759     ChessProgramState *cps = (ChessProgramState *)closure;
15760
15761     if (isr != cps->isr) return; /* Killed intentionally */
15762     if (count <= 0) {
15763         if (count == 0) {
15764             RemoveInputSource(cps->isr);
15765             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15766                     _(cps->which), cps->program);
15767             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15768             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15769                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15770                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15771                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15772                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15773                 } else {
15774                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15775                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15776                     gameInfo.result = res;
15777                 }
15778                 gameInfo.resultDetails = StrSave(buf);
15779             }
15780             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15781             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15782         } else {
15783             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15784                     _(cps->which), cps->program);
15785             RemoveInputSource(cps->isr);
15786
15787             /* [AS] Program is misbehaving badly... kill it */
15788             if( count == -2 ) {
15789                 DestroyChildProcess( cps->pr, 9 );
15790                 cps->pr = NoProc;
15791             }
15792
15793             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15794         }
15795         return;
15796     }
15797
15798     if ((end_str = strchr(message, '\r')) != NULL)
15799       *end_str = NULLCHAR;
15800     if ((end_str = strchr(message, '\n')) != NULL)
15801       *end_str = NULLCHAR;
15802
15803     if (appData.debugMode) {
15804         TimeMark now; int print = 1;
15805         char *quote = ""; char c; int i;
15806
15807         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15808                 char start = message[0];
15809                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15810                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15811                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15812                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15813                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15814                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15815                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15816                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15817                    sscanf(message, "hint: %c", &c)!=1 &&
15818                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15819                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15820                     print = (appData.engineComments >= 2);
15821                 }
15822                 message[0] = start; // restore original message
15823         }
15824         if(print) {
15825                 GetTimeMark(&now);
15826                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15827                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15828                         quote,
15829                         message);
15830                 if(serverFP)
15831                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15832                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15833                         quote,
15834                         message), fflush(serverFP);
15835         }
15836     }
15837
15838     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15839     if (appData.icsEngineAnalyze) {
15840         if (strstr(message, "whisper") != NULL ||
15841              strstr(message, "kibitz") != NULL ||
15842             strstr(message, "tellics") != NULL) return;
15843     }
15844
15845     HandleMachineMove(message, cps);
15846 }
15847
15848
15849 void
15850 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15851 {
15852     char buf[MSG_SIZ];
15853     int seconds;
15854
15855     if( timeControl_2 > 0 ) {
15856         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15857             tc = timeControl_2;
15858         }
15859     }
15860     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15861     inc /= cps->timeOdds;
15862     st  /= cps->timeOdds;
15863
15864     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15865
15866     if (st > 0) {
15867       /* Set exact time per move, normally using st command */
15868       if (cps->stKludge) {
15869         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15870         seconds = st % 60;
15871         if (seconds == 0) {
15872           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15873         } else {
15874           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15875         }
15876       } else {
15877         snprintf(buf, MSG_SIZ, "st %d\n", st);
15878       }
15879     } else {
15880       /* Set conventional or incremental time control, using level command */
15881       if (seconds == 0) {
15882         /* Note old gnuchess bug -- minutes:seconds used to not work.
15883            Fixed in later versions, but still avoid :seconds
15884            when seconds is 0. */
15885         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15886       } else {
15887         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15888                  seconds, inc/1000.);
15889       }
15890     }
15891     SendToProgram(buf, cps);
15892
15893     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15894     /* Orthogonally, limit search to given depth */
15895     if (sd > 0) {
15896       if (cps->sdKludge) {
15897         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15898       } else {
15899         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15900       }
15901       SendToProgram(buf, cps);
15902     }
15903
15904     if(cps->nps >= 0) { /* [HGM] nps */
15905         if(cps->supportsNPS == FALSE)
15906           cps->nps = -1; // don't use if engine explicitly says not supported!
15907         else {
15908           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15909           SendToProgram(buf, cps);
15910         }
15911     }
15912 }
15913
15914 ChessProgramState *
15915 WhitePlayer ()
15916 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15917 {
15918     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15919        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15920         return &second;
15921     return &first;
15922 }
15923
15924 void
15925 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15926 {
15927     char message[MSG_SIZ];
15928     long time, otime;
15929
15930     /* Note: this routine must be called when the clocks are stopped
15931        or when they have *just* been set or switched; otherwise
15932        it will be off by the time since the current tick started.
15933     */
15934     if (machineWhite) {
15935         time = whiteTimeRemaining / 10;
15936         otime = blackTimeRemaining / 10;
15937     } else {
15938         time = blackTimeRemaining / 10;
15939         otime = whiteTimeRemaining / 10;
15940     }
15941     /* [HGM] translate opponent's time by time-odds factor */
15942     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15943
15944     if (time <= 0) time = 1;
15945     if (otime <= 0) otime = 1;
15946
15947     snprintf(message, MSG_SIZ, "time %ld\n", time);
15948     SendToProgram(message, cps);
15949
15950     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15951     SendToProgram(message, cps);
15952 }
15953
15954 int
15955 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15956 {
15957   char buf[MSG_SIZ];
15958   int len = strlen(name);
15959   int val;
15960
15961   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15962     (*p) += len + 1;
15963     sscanf(*p, "%d", &val);
15964     *loc = (val != 0);
15965     while (**p && **p != ' ')
15966       (*p)++;
15967     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15968     SendToProgram(buf, cps);
15969     return TRUE;
15970   }
15971   return FALSE;
15972 }
15973
15974 int
15975 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15976 {
15977   char buf[MSG_SIZ];
15978   int len = strlen(name);
15979   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15980     (*p) += len + 1;
15981     sscanf(*p, "%d", loc);
15982     while (**p && **p != ' ') (*p)++;
15983     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15984     SendToProgram(buf, cps);
15985     return TRUE;
15986   }
15987   return FALSE;
15988 }
15989
15990 int
15991 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
15992 {
15993   char buf[MSG_SIZ];
15994   int len = strlen(name);
15995   if (strncmp((*p), name, len) == 0
15996       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15997     (*p) += len + 2;
15998     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
15999     sscanf(*p, "%[^\"]", *loc);
16000     while (**p && **p != '\"') (*p)++;
16001     if (**p == '\"') (*p)++;
16002     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16003     SendToProgram(buf, cps);
16004     return TRUE;
16005   }
16006   return FALSE;
16007 }
16008
16009 int
16010 ParseOption (Option *opt, ChessProgramState *cps)
16011 // [HGM] options: process the string that defines an engine option, and determine
16012 // name, type, default value, and allowed value range
16013 {
16014         char *p, *q, buf[MSG_SIZ];
16015         int n, min = (-1)<<31, max = 1<<31, def;
16016
16017         if(p = strstr(opt->name, " -spin ")) {
16018             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16019             if(max < min) max = min; // enforce consistency
16020             if(def < min) def = min;
16021             if(def > max) def = max;
16022             opt->value = def;
16023             opt->min = min;
16024             opt->max = max;
16025             opt->type = Spin;
16026         } else if((p = strstr(opt->name, " -slider "))) {
16027             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16028             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16029             if(max < min) max = min; // enforce consistency
16030             if(def < min) def = min;
16031             if(def > max) def = max;
16032             opt->value = def;
16033             opt->min = min;
16034             opt->max = max;
16035             opt->type = Spin; // Slider;
16036         } else if((p = strstr(opt->name, " -string "))) {
16037             opt->textValue = p+9;
16038             opt->type = TextBox;
16039         } else if((p = strstr(opt->name, " -file "))) {
16040             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16041             opt->textValue = p+7;
16042             opt->type = FileName; // FileName;
16043         } else if((p = strstr(opt->name, " -path "))) {
16044             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16045             opt->textValue = p+7;
16046             opt->type = PathName; // PathName;
16047         } else if(p = strstr(opt->name, " -check ")) {
16048             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16049             opt->value = (def != 0);
16050             opt->type = CheckBox;
16051         } else if(p = strstr(opt->name, " -combo ")) {
16052             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16053             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16054             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16055             opt->value = n = 0;
16056             while(q = StrStr(q, " /// ")) {
16057                 n++; *q = 0;    // count choices, and null-terminate each of them
16058                 q += 5;
16059                 if(*q == '*') { // remember default, which is marked with * prefix
16060                     q++;
16061                     opt->value = n;
16062                 }
16063                 cps->comboList[cps->comboCnt++] = q;
16064             }
16065             cps->comboList[cps->comboCnt++] = NULL;
16066             opt->max = n + 1;
16067             opt->type = ComboBox;
16068         } else if(p = strstr(opt->name, " -button")) {
16069             opt->type = Button;
16070         } else if(p = strstr(opt->name, " -save")) {
16071             opt->type = SaveButton;
16072         } else return FALSE;
16073         *p = 0; // terminate option name
16074         // now look if the command-line options define a setting for this engine option.
16075         if(cps->optionSettings && cps->optionSettings[0])
16076             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16077         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16078           snprintf(buf, MSG_SIZ, "option %s", p);
16079                 if(p = strstr(buf, ",")) *p = 0;
16080                 if(q = strchr(buf, '=')) switch(opt->type) {
16081                     case ComboBox:
16082                         for(n=0; n<opt->max; n++)
16083                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16084                         break;
16085                     case TextBox:
16086                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16087                         break;
16088                     case Spin:
16089                     case CheckBox:
16090                         opt->value = atoi(q+1);
16091                     default:
16092                         break;
16093                 }
16094                 strcat(buf, "\n");
16095                 SendToProgram(buf, cps);
16096         }
16097         return TRUE;
16098 }
16099
16100 void
16101 FeatureDone (ChessProgramState *cps, int val)
16102 {
16103   DelayedEventCallback cb = GetDelayedEvent();
16104   if ((cb == InitBackEnd3 && cps == &first) ||
16105       (cb == SettingsMenuIfReady && cps == &second) ||
16106       (cb == LoadEngine) ||
16107       (cb == TwoMachinesEventIfReady)) {
16108     CancelDelayedEvent();
16109     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16110   }
16111   cps->initDone = val;
16112   if(val) cps->reload = FALSE;
16113 }
16114
16115 /* Parse feature command from engine */
16116 void
16117 ParseFeatures (char *args, ChessProgramState *cps)
16118 {
16119   char *p = args;
16120   char *q = NULL;
16121   int val;
16122   char buf[MSG_SIZ];
16123
16124   for (;;) {
16125     while (*p == ' ') p++;
16126     if (*p == NULLCHAR) return;
16127
16128     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16129     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16130     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16131     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16132     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16133     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16134     if (BoolFeature(&p, "reuse", &val, cps)) {
16135       /* Engine can disable reuse, but can't enable it if user said no */
16136       if (!val) cps->reuse = FALSE;
16137       continue;
16138     }
16139     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16140     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16141       if (gameMode == TwoMachinesPlay) {
16142         DisplayTwoMachinesTitle();
16143       } else {
16144         DisplayTitle("");
16145       }
16146       continue;
16147     }
16148     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16149     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16150     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16151     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16152     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16153     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16154     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16155     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16156     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16157     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16158     if (IntFeature(&p, "done", &val, cps)) {
16159       FeatureDone(cps, val);
16160       continue;
16161     }
16162     /* Added by Tord: */
16163     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16164     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16165     /* End of additions by Tord */
16166
16167     /* [HGM] added features: */
16168     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16169     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16170     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16171     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16172     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16173     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16174     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16175         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16176         FREE(cps->option[cps->nrOptions].name);
16177         cps->option[cps->nrOptions].name = q; q = NULL;
16178         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16179           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16180             SendToProgram(buf, cps);
16181             continue;
16182         }
16183         if(cps->nrOptions >= MAX_OPTIONS) {
16184             cps->nrOptions--;
16185             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16186             DisplayError(buf, 0);
16187         }
16188         continue;
16189     }
16190     /* End of additions by HGM */
16191
16192     /* unknown feature: complain and skip */
16193     q = p;
16194     while (*q && *q != '=') q++;
16195     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16196     SendToProgram(buf, cps);
16197     p = q;
16198     if (*p == '=') {
16199       p++;
16200       if (*p == '\"') {
16201         p++;
16202         while (*p && *p != '\"') p++;
16203         if (*p == '\"') p++;
16204       } else {
16205         while (*p && *p != ' ') p++;
16206       }
16207     }
16208   }
16209
16210 }
16211
16212 void
16213 PeriodicUpdatesEvent (int newState)
16214 {
16215     if (newState == appData.periodicUpdates)
16216       return;
16217
16218     appData.periodicUpdates=newState;
16219
16220     /* Display type changes, so update it now */
16221 //    DisplayAnalysis();
16222
16223     /* Get the ball rolling again... */
16224     if (newState) {
16225         AnalysisPeriodicEvent(1);
16226         StartAnalysisClock();
16227     }
16228 }
16229
16230 void
16231 PonderNextMoveEvent (int newState)
16232 {
16233     if (newState == appData.ponderNextMove) return;
16234     if (gameMode == EditPosition) EditPositionDone(TRUE);
16235     if (newState) {
16236         SendToProgram("hard\n", &first);
16237         if (gameMode == TwoMachinesPlay) {
16238             SendToProgram("hard\n", &second);
16239         }
16240     } else {
16241         SendToProgram("easy\n", &first);
16242         thinkOutput[0] = NULLCHAR;
16243         if (gameMode == TwoMachinesPlay) {
16244             SendToProgram("easy\n", &second);
16245         }
16246     }
16247     appData.ponderNextMove = newState;
16248 }
16249
16250 void
16251 NewSettingEvent (int option, int *feature, char *command, int value)
16252 {
16253     char buf[MSG_SIZ];
16254
16255     if (gameMode == EditPosition) EditPositionDone(TRUE);
16256     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16257     if(feature == NULL || *feature) SendToProgram(buf, &first);
16258     if (gameMode == TwoMachinesPlay) {
16259         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16260     }
16261 }
16262
16263 void
16264 ShowThinkingEvent ()
16265 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16266 {
16267     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16268     int newState = appData.showThinking
16269         // [HGM] thinking: other features now need thinking output as well
16270         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16271
16272     if (oldState == newState) return;
16273     oldState = newState;
16274     if (gameMode == EditPosition) EditPositionDone(TRUE);
16275     if (oldState) {
16276         SendToProgram("post\n", &first);
16277         if (gameMode == TwoMachinesPlay) {
16278             SendToProgram("post\n", &second);
16279         }
16280     } else {
16281         SendToProgram("nopost\n", &first);
16282         thinkOutput[0] = NULLCHAR;
16283         if (gameMode == TwoMachinesPlay) {
16284             SendToProgram("nopost\n", &second);
16285         }
16286     }
16287 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16288 }
16289
16290 void
16291 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16292 {
16293   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16294   if (pr == NoProc) return;
16295   AskQuestion(title, question, replyPrefix, pr);
16296 }
16297
16298 void
16299 TypeInEvent (char firstChar)
16300 {
16301     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16302         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16303         gameMode == AnalyzeMode || gameMode == EditGame ||
16304         gameMode == EditPosition || gameMode == IcsExamining ||
16305         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16306         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16307                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16308                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16309         gameMode == Training) PopUpMoveDialog(firstChar);
16310 }
16311
16312 void
16313 TypeInDoneEvent (char *move)
16314 {
16315         Board board;
16316         int n, fromX, fromY, toX, toY;
16317         char promoChar;
16318         ChessMove moveType;
16319
16320         // [HGM] FENedit
16321         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16322                 EditPositionPasteFEN(move);
16323                 return;
16324         }
16325         // [HGM] movenum: allow move number to be typed in any mode
16326         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16327           ToNrEvent(2*n-1);
16328           return;
16329         }
16330         // undocumented kludge: allow command-line option to be typed in!
16331         // (potentially fatal, and does not implement the effect of the option.)
16332         // should only be used for options that are values on which future decisions will be made,
16333         // and definitely not on options that would be used during initialization.
16334         if(strstr(move, "!!! -") == move) {
16335             ParseArgsFromString(move+4);
16336             return;
16337         }
16338
16339       if (gameMode != EditGame && currentMove != forwardMostMove &&
16340         gameMode != Training) {
16341         DisplayMoveError(_("Displayed move is not current"));
16342       } else {
16343         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16344           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16345         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16346         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16347           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16348           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16349         } else {
16350           DisplayMoveError(_("Could not parse move"));
16351         }
16352       }
16353 }
16354
16355 void
16356 DisplayMove (int moveNumber)
16357 {
16358     char message[MSG_SIZ];
16359     char res[MSG_SIZ];
16360     char cpThinkOutput[MSG_SIZ];
16361
16362     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16363
16364     if (moveNumber == forwardMostMove - 1 ||
16365         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16366
16367         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16368
16369         if (strchr(cpThinkOutput, '\n')) {
16370             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16371         }
16372     } else {
16373         *cpThinkOutput = NULLCHAR;
16374     }
16375
16376     /* [AS] Hide thinking from human user */
16377     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16378         *cpThinkOutput = NULLCHAR;
16379         if( thinkOutput[0] != NULLCHAR ) {
16380             int i;
16381
16382             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16383                 cpThinkOutput[i] = '.';
16384             }
16385             cpThinkOutput[i] = NULLCHAR;
16386             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16387         }
16388     }
16389
16390     if (moveNumber == forwardMostMove - 1 &&
16391         gameInfo.resultDetails != NULL) {
16392         if (gameInfo.resultDetails[0] == NULLCHAR) {
16393           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16394         } else {
16395           snprintf(res, MSG_SIZ, " {%s} %s",
16396                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16397         }
16398     } else {
16399         res[0] = NULLCHAR;
16400     }
16401
16402     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16403         DisplayMessage(res, cpThinkOutput);
16404     } else {
16405       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16406                 WhiteOnMove(moveNumber) ? " " : ".. ",
16407                 parseList[moveNumber], res);
16408         DisplayMessage(message, cpThinkOutput);
16409     }
16410 }
16411
16412 void
16413 DisplayComment (int moveNumber, char *text)
16414 {
16415     char title[MSG_SIZ];
16416
16417     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16418       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16419     } else {
16420       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16421               WhiteOnMove(moveNumber) ? " " : ".. ",
16422               parseList[moveNumber]);
16423     }
16424     if (text != NULL && (appData.autoDisplayComment || commentUp))
16425         CommentPopUp(title, text);
16426 }
16427
16428 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16429  * might be busy thinking or pondering.  It can be omitted if your
16430  * gnuchess is configured to stop thinking immediately on any user
16431  * input.  However, that gnuchess feature depends on the FIONREAD
16432  * ioctl, which does not work properly on some flavors of Unix.
16433  */
16434 void
16435 Attention (ChessProgramState *cps)
16436 {
16437 #if ATTENTION
16438     if (!cps->useSigint) return;
16439     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16440     switch (gameMode) {
16441       case MachinePlaysWhite:
16442       case MachinePlaysBlack:
16443       case TwoMachinesPlay:
16444       case IcsPlayingWhite:
16445       case IcsPlayingBlack:
16446       case AnalyzeMode:
16447       case AnalyzeFile:
16448         /* Skip if we know it isn't thinking */
16449         if (!cps->maybeThinking) return;
16450         if (appData.debugMode)
16451           fprintf(debugFP, "Interrupting %s\n", cps->which);
16452         InterruptChildProcess(cps->pr);
16453         cps->maybeThinking = FALSE;
16454         break;
16455       default:
16456         break;
16457     }
16458 #endif /*ATTENTION*/
16459 }
16460
16461 int
16462 CheckFlags ()
16463 {
16464     if (whiteTimeRemaining <= 0) {
16465         if (!whiteFlag) {
16466             whiteFlag = TRUE;
16467             if (appData.icsActive) {
16468                 if (appData.autoCallFlag &&
16469                     gameMode == IcsPlayingBlack && !blackFlag) {
16470                   SendToICS(ics_prefix);
16471                   SendToICS("flag\n");
16472                 }
16473             } else {
16474                 if (blackFlag) {
16475                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16476                 } else {
16477                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16478                     if (appData.autoCallFlag) {
16479                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16480                         return TRUE;
16481                     }
16482                 }
16483             }
16484         }
16485     }
16486     if (blackTimeRemaining <= 0) {
16487         if (!blackFlag) {
16488             blackFlag = TRUE;
16489             if (appData.icsActive) {
16490                 if (appData.autoCallFlag &&
16491                     gameMode == IcsPlayingWhite && !whiteFlag) {
16492                   SendToICS(ics_prefix);
16493                   SendToICS("flag\n");
16494                 }
16495             } else {
16496                 if (whiteFlag) {
16497                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16498                 } else {
16499                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16500                     if (appData.autoCallFlag) {
16501                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16502                         return TRUE;
16503                     }
16504                 }
16505             }
16506         }
16507     }
16508     return FALSE;
16509 }
16510
16511 void
16512 CheckTimeControl ()
16513 {
16514     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16515         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16516
16517     /*
16518      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16519      */
16520     if ( !WhiteOnMove(forwardMostMove) ) {
16521         /* White made time control */
16522         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16523         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16524         /* [HGM] time odds: correct new time quota for time odds! */
16525                                             / WhitePlayer()->timeOdds;
16526         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16527     } else {
16528         lastBlack -= blackTimeRemaining;
16529         /* Black made time control */
16530         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16531                                             / WhitePlayer()->other->timeOdds;
16532         lastWhite = whiteTimeRemaining;
16533     }
16534 }
16535
16536 void
16537 DisplayBothClocks ()
16538 {
16539     int wom = gameMode == EditPosition ?
16540       !blackPlaysFirst : WhiteOnMove(currentMove);
16541     DisplayWhiteClock(whiteTimeRemaining, wom);
16542     DisplayBlackClock(blackTimeRemaining, !wom);
16543 }
16544
16545
16546 /* Timekeeping seems to be a portability nightmare.  I think everyone
16547    has ftime(), but I'm really not sure, so I'm including some ifdefs
16548    to use other calls if you don't.  Clocks will be less accurate if
16549    you have neither ftime nor gettimeofday.
16550 */
16551
16552 /* VS 2008 requires the #include outside of the function */
16553 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16554 #include <sys/timeb.h>
16555 #endif
16556
16557 /* Get the current time as a TimeMark */
16558 void
16559 GetTimeMark (TimeMark *tm)
16560 {
16561 #if HAVE_GETTIMEOFDAY
16562
16563     struct timeval timeVal;
16564     struct timezone timeZone;
16565
16566     gettimeofday(&timeVal, &timeZone);
16567     tm->sec = (long) timeVal.tv_sec;
16568     tm->ms = (int) (timeVal.tv_usec / 1000L);
16569
16570 #else /*!HAVE_GETTIMEOFDAY*/
16571 #if HAVE_FTIME
16572
16573 // include <sys/timeb.h> / moved to just above start of function
16574     struct timeb timeB;
16575
16576     ftime(&timeB);
16577     tm->sec = (long) timeB.time;
16578     tm->ms = (int) timeB.millitm;
16579
16580 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16581     tm->sec = (long) time(NULL);
16582     tm->ms = 0;
16583 #endif
16584 #endif
16585 }
16586
16587 /* Return the difference in milliseconds between two
16588    time marks.  We assume the difference will fit in a long!
16589 */
16590 long
16591 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16592 {
16593     return 1000L*(tm2->sec - tm1->sec) +
16594            (long) (tm2->ms - tm1->ms);
16595 }
16596
16597
16598 /*
16599  * Code to manage the game clocks.
16600  *
16601  * In tournament play, black starts the clock and then white makes a move.
16602  * We give the human user a slight advantage if he is playing white---the
16603  * clocks don't run until he makes his first move, so it takes zero time.
16604  * Also, we don't account for network lag, so we could get out of sync
16605  * with GNU Chess's clock -- but then, referees are always right.
16606  */
16607
16608 static TimeMark tickStartTM;
16609 static long intendedTickLength;
16610
16611 long
16612 NextTickLength (long timeRemaining)
16613 {
16614     long nominalTickLength, nextTickLength;
16615
16616     if (timeRemaining > 0L && timeRemaining <= 10000L)
16617       nominalTickLength = 100L;
16618     else
16619       nominalTickLength = 1000L;
16620     nextTickLength = timeRemaining % nominalTickLength;
16621     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16622
16623     return nextTickLength;
16624 }
16625
16626 /* Adjust clock one minute up or down */
16627 void
16628 AdjustClock (Boolean which, int dir)
16629 {
16630     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16631     if(which) blackTimeRemaining += 60000*dir;
16632     else      whiteTimeRemaining += 60000*dir;
16633     DisplayBothClocks();
16634     adjustedClock = TRUE;
16635 }
16636
16637 /* Stop clocks and reset to a fresh time control */
16638 void
16639 ResetClocks ()
16640 {
16641     (void) StopClockTimer();
16642     if (appData.icsActive) {
16643         whiteTimeRemaining = blackTimeRemaining = 0;
16644     } else if (searchTime) {
16645         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16646         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16647     } else { /* [HGM] correct new time quote for time odds */
16648         whiteTC = blackTC = fullTimeControlString;
16649         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16650         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16651     }
16652     if (whiteFlag || blackFlag) {
16653         DisplayTitle("");
16654         whiteFlag = blackFlag = FALSE;
16655     }
16656     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16657     DisplayBothClocks();
16658     adjustedClock = FALSE;
16659 }
16660
16661 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16662
16663 /* Decrement running clock by amount of time that has passed */
16664 void
16665 DecrementClocks ()
16666 {
16667     long timeRemaining;
16668     long lastTickLength, fudge;
16669     TimeMark now;
16670
16671     if (!appData.clockMode) return;
16672     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16673
16674     GetTimeMark(&now);
16675
16676     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16677
16678     /* Fudge if we woke up a little too soon */
16679     fudge = intendedTickLength - lastTickLength;
16680     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16681
16682     if (WhiteOnMove(forwardMostMove)) {
16683         if(whiteNPS >= 0) lastTickLength = 0;
16684         timeRemaining = whiteTimeRemaining -= lastTickLength;
16685         if(timeRemaining < 0 && !appData.icsActive) {
16686             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16687             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16688                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16689                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16690             }
16691         }
16692         DisplayWhiteClock(whiteTimeRemaining - fudge,
16693                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16694     } else {
16695         if(blackNPS >= 0) lastTickLength = 0;
16696         timeRemaining = blackTimeRemaining -= lastTickLength;
16697         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16698             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16699             if(suddenDeath) {
16700                 blackStartMove = forwardMostMove;
16701                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16702             }
16703         }
16704         DisplayBlackClock(blackTimeRemaining - fudge,
16705                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16706     }
16707     if (CheckFlags()) return;
16708
16709     if(twoBoards) { // count down secondary board's clocks as well
16710         activePartnerTime -= lastTickLength;
16711         partnerUp = 1;
16712         if(activePartner == 'W')
16713             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16714         else
16715             DisplayBlackClock(activePartnerTime, TRUE);
16716         partnerUp = 0;
16717     }
16718
16719     tickStartTM = now;
16720     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16721     StartClockTimer(intendedTickLength);
16722
16723     /* if the time remaining has fallen below the alarm threshold, sound the
16724      * alarm. if the alarm has sounded and (due to a takeback or time control
16725      * with increment) the time remaining has increased to a level above the
16726      * threshold, reset the alarm so it can sound again.
16727      */
16728
16729     if (appData.icsActive && appData.icsAlarm) {
16730
16731         /* make sure we are dealing with the user's clock */
16732         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16733                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16734            )) return;
16735
16736         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16737             alarmSounded = FALSE;
16738         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16739             PlayAlarmSound();
16740             alarmSounded = TRUE;
16741         }
16742     }
16743 }
16744
16745
16746 /* A player has just moved, so stop the previously running
16747    clock and (if in clock mode) start the other one.
16748    We redisplay both clocks in case we're in ICS mode, because
16749    ICS gives us an update to both clocks after every move.
16750    Note that this routine is called *after* forwardMostMove
16751    is updated, so the last fractional tick must be subtracted
16752    from the color that is *not* on move now.
16753 */
16754 void
16755 SwitchClocks (int newMoveNr)
16756 {
16757     long lastTickLength;
16758     TimeMark now;
16759     int flagged = FALSE;
16760
16761     GetTimeMark(&now);
16762
16763     if (StopClockTimer() && appData.clockMode) {
16764         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16765         if (!WhiteOnMove(forwardMostMove)) {
16766             if(blackNPS >= 0) lastTickLength = 0;
16767             blackTimeRemaining -= lastTickLength;
16768            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16769 //         if(pvInfoList[forwardMostMove].time == -1)
16770                  pvInfoList[forwardMostMove].time =               // use GUI time
16771                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16772         } else {
16773            if(whiteNPS >= 0) lastTickLength = 0;
16774            whiteTimeRemaining -= lastTickLength;
16775            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16776 //         if(pvInfoList[forwardMostMove].time == -1)
16777                  pvInfoList[forwardMostMove].time =
16778                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16779         }
16780         flagged = CheckFlags();
16781     }
16782     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16783     CheckTimeControl();
16784
16785     if (flagged || !appData.clockMode) return;
16786
16787     switch (gameMode) {
16788       case MachinePlaysBlack:
16789       case MachinePlaysWhite:
16790       case BeginningOfGame:
16791         if (pausing) return;
16792         break;
16793
16794       case EditGame:
16795       case PlayFromGameFile:
16796       case IcsExamining:
16797         return;
16798
16799       default:
16800         break;
16801     }
16802
16803     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16804         if(WhiteOnMove(forwardMostMove))
16805              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16806         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16807     }
16808
16809     tickStartTM = now;
16810     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16811       whiteTimeRemaining : blackTimeRemaining);
16812     StartClockTimer(intendedTickLength);
16813 }
16814
16815
16816 /* Stop both clocks */
16817 void
16818 StopClocks ()
16819 {
16820     long lastTickLength;
16821     TimeMark now;
16822
16823     if (!StopClockTimer()) return;
16824     if (!appData.clockMode) return;
16825
16826     GetTimeMark(&now);
16827
16828     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16829     if (WhiteOnMove(forwardMostMove)) {
16830         if(whiteNPS >= 0) lastTickLength = 0;
16831         whiteTimeRemaining -= lastTickLength;
16832         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16833     } else {
16834         if(blackNPS >= 0) lastTickLength = 0;
16835         blackTimeRemaining -= lastTickLength;
16836         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16837     }
16838     CheckFlags();
16839 }
16840
16841 /* Start clock of player on move.  Time may have been reset, so
16842    if clock is already running, stop and restart it. */
16843 void
16844 StartClocks ()
16845 {
16846     (void) StopClockTimer(); /* in case it was running already */
16847     DisplayBothClocks();
16848     if (CheckFlags()) return;
16849
16850     if (!appData.clockMode) return;
16851     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16852
16853     GetTimeMark(&tickStartTM);
16854     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16855       whiteTimeRemaining : blackTimeRemaining);
16856
16857    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16858     whiteNPS = blackNPS = -1;
16859     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16860        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16861         whiteNPS = first.nps;
16862     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16863        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16864         blackNPS = first.nps;
16865     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16866         whiteNPS = second.nps;
16867     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16868         blackNPS = second.nps;
16869     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16870
16871     StartClockTimer(intendedTickLength);
16872 }
16873
16874 char *
16875 TimeString (long ms)
16876 {
16877     long second, minute, hour, day;
16878     char *sign = "";
16879     static char buf[32];
16880
16881     if (ms > 0 && ms <= 9900) {
16882       /* convert milliseconds to tenths, rounding up */
16883       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16884
16885       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16886       return buf;
16887     }
16888
16889     /* convert milliseconds to seconds, rounding up */
16890     /* use floating point to avoid strangeness of integer division
16891        with negative dividends on many machines */
16892     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16893
16894     if (second < 0) {
16895         sign = "-";
16896         second = -second;
16897     }
16898
16899     day = second / (60 * 60 * 24);
16900     second = second % (60 * 60 * 24);
16901     hour = second / (60 * 60);
16902     second = second % (60 * 60);
16903     minute = second / 60;
16904     second = second % 60;
16905
16906     if (day > 0)
16907       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16908               sign, day, hour, minute, second);
16909     else if (hour > 0)
16910       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16911     else
16912       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16913
16914     return buf;
16915 }
16916
16917
16918 /*
16919  * This is necessary because some C libraries aren't ANSI C compliant yet.
16920  */
16921 char *
16922 StrStr (char *string, char *match)
16923 {
16924     int i, length;
16925
16926     length = strlen(match);
16927
16928     for (i = strlen(string) - length; i >= 0; i--, string++)
16929       if (!strncmp(match, string, length))
16930         return string;
16931
16932     return NULL;
16933 }
16934
16935 char *
16936 StrCaseStr (char *string, char *match)
16937 {
16938     int i, j, length;
16939
16940     length = strlen(match);
16941
16942     for (i = strlen(string) - length; i >= 0; i--, string++) {
16943         for (j = 0; j < length; j++) {
16944             if (ToLower(match[j]) != ToLower(string[j]))
16945               break;
16946         }
16947         if (j == length) return string;
16948     }
16949
16950     return NULL;
16951 }
16952
16953 #ifndef _amigados
16954 int
16955 StrCaseCmp (char *s1, char *s2)
16956 {
16957     char c1, c2;
16958
16959     for (;;) {
16960         c1 = ToLower(*s1++);
16961         c2 = ToLower(*s2++);
16962         if (c1 > c2) return 1;
16963         if (c1 < c2) return -1;
16964         if (c1 == NULLCHAR) return 0;
16965     }
16966 }
16967
16968
16969 int
16970 ToLower (int c)
16971 {
16972     return isupper(c) ? tolower(c) : c;
16973 }
16974
16975
16976 int
16977 ToUpper (int c)
16978 {
16979     return islower(c) ? toupper(c) : c;
16980 }
16981 #endif /* !_amigados    */
16982
16983 char *
16984 StrSave (char *s)
16985 {
16986   char *ret;
16987
16988   if ((ret = (char *) malloc(strlen(s) + 1)))
16989     {
16990       safeStrCpy(ret, s, strlen(s)+1);
16991     }
16992   return ret;
16993 }
16994
16995 char *
16996 StrSavePtr (char *s, char **savePtr)
16997 {
16998     if (*savePtr) {
16999         free(*savePtr);
17000     }
17001     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17002       safeStrCpy(*savePtr, s, strlen(s)+1);
17003     }
17004     return(*savePtr);
17005 }
17006
17007 char *
17008 PGNDate ()
17009 {
17010     time_t clock;
17011     struct tm *tm;
17012     char buf[MSG_SIZ];
17013
17014     clock = time((time_t *)NULL);
17015     tm = localtime(&clock);
17016     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17017             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17018     return StrSave(buf);
17019 }
17020
17021
17022 char *
17023 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17024 {
17025     int i, j, fromX, fromY, toX, toY;
17026     int whiteToPlay;
17027     char buf[MSG_SIZ];
17028     char *p, *q;
17029     int emptycount;
17030     ChessSquare piece;
17031
17032     whiteToPlay = (gameMode == EditPosition) ?
17033       !blackPlaysFirst : (move % 2 == 0);
17034     p = buf;
17035
17036     /* Piece placement data */
17037     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17038         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17039         emptycount = 0;
17040         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17041             if (boards[move][i][j] == EmptySquare) {
17042                 emptycount++;
17043             } else { ChessSquare piece = boards[move][i][j];
17044                 if (emptycount > 0) {
17045                     if(emptycount<10) /* [HGM] can be >= 10 */
17046                         *p++ = '0' + emptycount;
17047                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17048                     emptycount = 0;
17049                 }
17050                 if(PieceToChar(piece) == '+') {
17051                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17052                     *p++ = '+';
17053                     piece = (ChessSquare)(DEMOTED piece);
17054                 }
17055                 *p++ = PieceToChar(piece);
17056                 if(p[-1] == '~') {
17057                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17058                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17059                     *p++ = '~';
17060                 }
17061             }
17062         }
17063         if (emptycount > 0) {
17064             if(emptycount<10) /* [HGM] can be >= 10 */
17065                 *p++ = '0' + emptycount;
17066             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17067             emptycount = 0;
17068         }
17069         *p++ = '/';
17070     }
17071     *(p - 1) = ' ';
17072
17073     /* [HGM] print Crazyhouse or Shogi holdings */
17074     if( gameInfo.holdingsWidth ) {
17075         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17076         q = p;
17077         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17078             piece = boards[move][i][BOARD_WIDTH-1];
17079             if( piece != EmptySquare )
17080               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17081                   *p++ = PieceToChar(piece);
17082         }
17083         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17084             piece = boards[move][BOARD_HEIGHT-i-1][0];
17085             if( piece != EmptySquare )
17086               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17087                   *p++ = PieceToChar(piece);
17088         }
17089
17090         if( q == p ) *p++ = '-';
17091         *p++ = ']';
17092         *p++ = ' ';
17093     }
17094
17095     /* Active color */
17096     *p++ = whiteToPlay ? 'w' : 'b';
17097     *p++ = ' ';
17098
17099   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17100     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17101   } else {
17102   if(nrCastlingRights) {
17103      q = p;
17104      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17105        /* [HGM] write directly from rights */
17106            if(boards[move][CASTLING][2] != NoRights &&
17107               boards[move][CASTLING][0] != NoRights   )
17108                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17109            if(boards[move][CASTLING][2] != NoRights &&
17110               boards[move][CASTLING][1] != NoRights   )
17111                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17112            if(boards[move][CASTLING][5] != NoRights &&
17113               boards[move][CASTLING][3] != NoRights   )
17114                 *p++ = boards[move][CASTLING][3] + AAA;
17115            if(boards[move][CASTLING][5] != NoRights &&
17116               boards[move][CASTLING][4] != NoRights   )
17117                 *p++ = boards[move][CASTLING][4] + AAA;
17118      } else {
17119
17120         /* [HGM] write true castling rights */
17121         if( nrCastlingRights == 6 ) {
17122             int q, k=0;
17123             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17124                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17125             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17126                  boards[move][CASTLING][2] != NoRights  );
17127             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17128                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17129                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17130                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17131                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17132             }
17133             if(q) *p++ = 'Q';
17134             k = 0;
17135             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17136                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17137             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17138                  boards[move][CASTLING][5] != NoRights  );
17139             if(gameInfo.variant == VariantSChess) {
17140                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17141                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17142                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17143                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17144             }
17145             if(q) *p++ = 'q';
17146         }
17147      }
17148      if (q == p) *p++ = '-'; /* No castling rights */
17149      *p++ = ' ';
17150   }
17151
17152   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17153      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17154      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17155     /* En passant target square */
17156     if (move > backwardMostMove) {
17157         fromX = moveList[move - 1][0] - AAA;
17158         fromY = moveList[move - 1][1] - ONE;
17159         toX = moveList[move - 1][2] - AAA;
17160         toY = moveList[move - 1][3] - ONE;
17161         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17162             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17163             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17164             fromX == toX) {
17165             /* 2-square pawn move just happened */
17166             *p++ = toX + AAA;
17167             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17168         } else {
17169             *p++ = '-';
17170         }
17171     } else if(move == backwardMostMove) {
17172         // [HGM] perhaps we should always do it like this, and forget the above?
17173         if((signed char)boards[move][EP_STATUS] >= 0) {
17174             *p++ = boards[move][EP_STATUS] + AAA;
17175             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17176         } else {
17177             *p++ = '-';
17178         }
17179     } else {
17180         *p++ = '-';
17181     }
17182     *p++ = ' ';
17183   }
17184   }
17185
17186     if(moveCounts)
17187     {   int i = 0, j=move;
17188
17189         /* [HGM] find reversible plies */
17190         if (appData.debugMode) { int k;
17191             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17192             for(k=backwardMostMove; k<=forwardMostMove; k++)
17193                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17194
17195         }
17196
17197         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17198         if( j == backwardMostMove ) i += initialRulePlies;
17199         sprintf(p, "%d ", i);
17200         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17201
17202         /* Fullmove number */
17203         sprintf(p, "%d", (move / 2) + 1);
17204     } else *--p = NULLCHAR;
17205
17206     return StrSave(buf);
17207 }
17208
17209 Boolean
17210 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17211 {
17212     int i, j;
17213     char *p, c;
17214     int emptycount, virgin[BOARD_FILES];
17215     ChessSquare piece;
17216
17217     p = fen;
17218
17219     /* [HGM] by default clear Crazyhouse holdings, if present */
17220     if(gameInfo.holdingsWidth) {
17221        for(i=0; i<BOARD_HEIGHT; i++) {
17222            board[i][0]             = EmptySquare; /* black holdings */
17223            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17224            board[i][1]             = (ChessSquare) 0; /* black counts */
17225            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17226        }
17227     }
17228
17229     /* Piece placement data */
17230     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17231         j = 0;
17232         for (;;) {
17233             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17234                 if (*p == '/') p++;
17235                 emptycount = gameInfo.boardWidth - j;
17236                 while (emptycount--)
17237                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17238                 break;
17239 #if(BOARD_FILES >= 10)
17240             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17241                 p++; emptycount=10;
17242                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17243                 while (emptycount--)
17244                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17245 #endif
17246             } else if (isdigit(*p)) {
17247                 emptycount = *p++ - '0';
17248                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17249                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17250                 while (emptycount--)
17251                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17252             } else if (*p == '+' || isalpha(*p)) {
17253                 if (j >= gameInfo.boardWidth) return FALSE;
17254                 if(*p=='+') {
17255                     piece = CharToPiece(*++p);
17256                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17257                     piece = (ChessSquare) (PROMOTED piece ); p++;
17258                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17259                 } else piece = CharToPiece(*p++);
17260
17261                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17262                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17263                     piece = (ChessSquare) (PROMOTED piece);
17264                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17265                     p++;
17266                 }
17267                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17268             } else {
17269                 return FALSE;
17270             }
17271         }
17272     }
17273     while (*p == '/' || *p == ' ') p++;
17274
17275     /* [HGM] look for Crazyhouse holdings here */
17276     while(*p==' ') p++;
17277     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17278         if(*p == '[') p++;
17279         if(*p == '-' ) p++; /* empty holdings */ else {
17280             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17281             /* if we would allow FEN reading to set board size, we would   */
17282             /* have to add holdings and shift the board read so far here   */
17283             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17284                 p++;
17285                 if((int) piece >= (int) BlackPawn ) {
17286                     i = (int)piece - (int)BlackPawn;
17287                     i = PieceToNumber((ChessSquare)i);
17288                     if( i >= gameInfo.holdingsSize ) return FALSE;
17289                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17290                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17291                 } else {
17292                     i = (int)piece - (int)WhitePawn;
17293                     i = PieceToNumber((ChessSquare)i);
17294                     if( i >= gameInfo.holdingsSize ) return FALSE;
17295                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17296                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17297                 }
17298             }
17299         }
17300         if(*p == ']') p++;
17301     }
17302
17303     while(*p == ' ') p++;
17304
17305     /* Active color */
17306     c = *p++;
17307     if(appData.colorNickNames) {
17308       if( c == appData.colorNickNames[0] ) c = 'w'; else
17309       if( c == appData.colorNickNames[1] ) c = 'b';
17310     }
17311     switch (c) {
17312       case 'w':
17313         *blackPlaysFirst = FALSE;
17314         break;
17315       case 'b':
17316         *blackPlaysFirst = TRUE;
17317         break;
17318       default:
17319         return FALSE;
17320     }
17321
17322     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17323     /* return the extra info in global variiables             */
17324
17325     /* set defaults in case FEN is incomplete */
17326     board[EP_STATUS] = EP_UNKNOWN;
17327     for(i=0; i<nrCastlingRights; i++ ) {
17328         board[CASTLING][i] =
17329             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17330     }   /* assume possible unless obviously impossible */
17331     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17332     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17333     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17334                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17335     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17336     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17337     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17338                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17339     FENrulePlies = 0;
17340
17341     while(*p==' ') p++;
17342     if(nrCastlingRights) {
17343       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17344       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17345           /* castling indicator present, so default becomes no castlings */
17346           for(i=0; i<nrCastlingRights; i++ ) {
17347                  board[CASTLING][i] = NoRights;
17348           }
17349       }
17350       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17351              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17352              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17353              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17354         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17355
17356         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17357             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17358             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17359         }
17360         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17361             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17362         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17363                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17364         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17365                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17366         switch(c) {
17367           case'K':
17368               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17369               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17370               board[CASTLING][2] = whiteKingFile;
17371               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17372               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17373               break;
17374           case'Q':
17375               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17376               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17377               board[CASTLING][2] = whiteKingFile;
17378               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17379               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17380               break;
17381           case'k':
17382               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17383               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17384               board[CASTLING][5] = blackKingFile;
17385               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17386               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17387               break;
17388           case'q':
17389               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17390               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17391               board[CASTLING][5] = blackKingFile;
17392               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17393               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17394           case '-':
17395               break;
17396           default: /* FRC castlings */
17397               if(c >= 'a') { /* black rights */
17398                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17399                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17400                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17401                   if(i == BOARD_RGHT) break;
17402                   board[CASTLING][5] = i;
17403                   c -= AAA;
17404                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17405                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17406                   if(c > i)
17407                       board[CASTLING][3] = c;
17408                   else
17409                       board[CASTLING][4] = c;
17410               } else { /* white rights */
17411                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17412                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17413                     if(board[0][i] == WhiteKing) break;
17414                   if(i == BOARD_RGHT) break;
17415                   board[CASTLING][2] = i;
17416                   c -= AAA - 'a' + 'A';
17417                   if(board[0][c] >= WhiteKing) break;
17418                   if(c > i)
17419                       board[CASTLING][0] = c;
17420                   else
17421                       board[CASTLING][1] = c;
17422               }
17423         }
17424       }
17425       for(i=0; i<nrCastlingRights; i++)
17426         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17427       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17428     if (appData.debugMode) {
17429         fprintf(debugFP, "FEN castling rights:");
17430         for(i=0; i<nrCastlingRights; i++)
17431         fprintf(debugFP, " %d", board[CASTLING][i]);
17432         fprintf(debugFP, "\n");
17433     }
17434
17435       while(*p==' ') p++;
17436     }
17437
17438     /* read e.p. field in games that know e.p. capture */
17439     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17440        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17441        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17442       if(*p=='-') {
17443         p++; board[EP_STATUS] = EP_NONE;
17444       } else {
17445          char c = *p++ - AAA;
17446
17447          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17448          if(*p >= '0' && *p <='9') p++;
17449          board[EP_STATUS] = c;
17450       }
17451     }
17452
17453
17454     if(sscanf(p, "%d", &i) == 1) {
17455         FENrulePlies = i; /* 50-move ply counter */
17456         /* (The move number is still ignored)    */
17457     }
17458
17459     return TRUE;
17460 }
17461
17462 void
17463 EditPositionPasteFEN (char *fen)
17464 {
17465   if (fen != NULL) {
17466     Board initial_position;
17467
17468     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17469       DisplayError(_("Bad FEN position in clipboard"), 0);
17470       return ;
17471     } else {
17472       int savedBlackPlaysFirst = blackPlaysFirst;
17473       EditPositionEvent();
17474       blackPlaysFirst = savedBlackPlaysFirst;
17475       CopyBoard(boards[0], initial_position);
17476       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17477       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17478       DisplayBothClocks();
17479       DrawPosition(FALSE, boards[currentMove]);
17480     }
17481   }
17482 }
17483
17484 static char cseq[12] = "\\   ";
17485
17486 Boolean
17487 set_cont_sequence (char *new_seq)
17488 {
17489     int len;
17490     Boolean ret;
17491
17492     // handle bad attempts to set the sequence
17493         if (!new_seq)
17494                 return 0; // acceptable error - no debug
17495
17496     len = strlen(new_seq);
17497     ret = (len > 0) && (len < sizeof(cseq));
17498     if (ret)
17499       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17500     else if (appData.debugMode)
17501       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17502     return ret;
17503 }
17504
17505 /*
17506     reformat a source message so words don't cross the width boundary.  internal
17507     newlines are not removed.  returns the wrapped size (no null character unless
17508     included in source message).  If dest is NULL, only calculate the size required
17509     for the dest buffer.  lp argument indicats line position upon entry, and it's
17510     passed back upon exit.
17511 */
17512 int
17513 wrap (char *dest, char *src, int count, int width, int *lp)
17514 {
17515     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17516
17517     cseq_len = strlen(cseq);
17518     old_line = line = *lp;
17519     ansi = len = clen = 0;
17520
17521     for (i=0; i < count; i++)
17522     {
17523         if (src[i] == '\033')
17524             ansi = 1;
17525
17526         // if we hit the width, back up
17527         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17528         {
17529             // store i & len in case the word is too long
17530             old_i = i, old_len = len;
17531
17532             // find the end of the last word
17533             while (i && src[i] != ' ' && src[i] != '\n')
17534             {
17535                 i--;
17536                 len--;
17537             }
17538
17539             // word too long?  restore i & len before splitting it
17540             if ((old_i-i+clen) >= width)
17541             {
17542                 i = old_i;
17543                 len = old_len;
17544             }
17545
17546             // extra space?
17547             if (i && src[i-1] == ' ')
17548                 len--;
17549
17550             if (src[i] != ' ' && src[i] != '\n')
17551             {
17552                 i--;
17553                 if (len)
17554                     len--;
17555             }
17556
17557             // now append the newline and continuation sequence
17558             if (dest)
17559                 dest[len] = '\n';
17560             len++;
17561             if (dest)
17562                 strncpy(dest+len, cseq, cseq_len);
17563             len += cseq_len;
17564             line = cseq_len;
17565             clen = cseq_len;
17566             continue;
17567         }
17568
17569         if (dest)
17570             dest[len] = src[i];
17571         len++;
17572         if (!ansi)
17573             line++;
17574         if (src[i] == '\n')
17575             line = 0;
17576         if (src[i] == 'm')
17577             ansi = 0;
17578     }
17579     if (dest && appData.debugMode)
17580     {
17581         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17582             count, width, line, len, *lp);
17583         show_bytes(debugFP, src, count);
17584         fprintf(debugFP, "\ndest: ");
17585         show_bytes(debugFP, dest, len);
17586         fprintf(debugFP, "\n");
17587     }
17588     *lp = dest ? line : old_line;
17589
17590     return len;
17591 }
17592
17593 // [HGM] vari: routines for shelving variations
17594 Boolean modeRestore = FALSE;
17595
17596 void
17597 PushInner (int firstMove, int lastMove)
17598 {
17599         int i, j, nrMoves = lastMove - firstMove;
17600
17601         // push current tail of game on stack
17602         savedResult[storedGames] = gameInfo.result;
17603         savedDetails[storedGames] = gameInfo.resultDetails;
17604         gameInfo.resultDetails = NULL;
17605         savedFirst[storedGames] = firstMove;
17606         savedLast [storedGames] = lastMove;
17607         savedFramePtr[storedGames] = framePtr;
17608         framePtr -= nrMoves; // reserve space for the boards
17609         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17610             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17611             for(j=0; j<MOVE_LEN; j++)
17612                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17613             for(j=0; j<2*MOVE_LEN; j++)
17614                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17615             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17616             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17617             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17618             pvInfoList[firstMove+i-1].depth = 0;
17619             commentList[framePtr+i] = commentList[firstMove+i];
17620             commentList[firstMove+i] = NULL;
17621         }
17622
17623         storedGames++;
17624         forwardMostMove = firstMove; // truncate game so we can start variation
17625 }
17626
17627 void
17628 PushTail (int firstMove, int lastMove)
17629 {
17630         if(appData.icsActive) { // only in local mode
17631                 forwardMostMove = currentMove; // mimic old ICS behavior
17632                 return;
17633         }
17634         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17635
17636         PushInner(firstMove, lastMove);
17637         if(storedGames == 1) GreyRevert(FALSE);
17638         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17639 }
17640
17641 void
17642 PopInner (Boolean annotate)
17643 {
17644         int i, j, nrMoves;
17645         char buf[8000], moveBuf[20];
17646
17647         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17648         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17649         nrMoves = savedLast[storedGames] - currentMove;
17650         if(annotate) {
17651                 int cnt = 10;
17652                 if(!WhiteOnMove(currentMove))
17653                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17654                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17655                 for(i=currentMove; i<forwardMostMove; i++) {
17656                         if(WhiteOnMove(i))
17657                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17658                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17659                         strcat(buf, moveBuf);
17660                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17661                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17662                 }
17663                 strcat(buf, ")");
17664         }
17665         for(i=1; i<=nrMoves; i++) { // copy last variation back
17666             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17667             for(j=0; j<MOVE_LEN; j++)
17668                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17669             for(j=0; j<2*MOVE_LEN; j++)
17670                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17671             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17672             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17673             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17674             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17675             commentList[currentMove+i] = commentList[framePtr+i];
17676             commentList[framePtr+i] = NULL;
17677         }
17678         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17679         framePtr = savedFramePtr[storedGames];
17680         gameInfo.result = savedResult[storedGames];
17681         if(gameInfo.resultDetails != NULL) {
17682             free(gameInfo.resultDetails);
17683       }
17684         gameInfo.resultDetails = savedDetails[storedGames];
17685         forwardMostMove = currentMove + nrMoves;
17686 }
17687
17688 Boolean
17689 PopTail (Boolean annotate)
17690 {
17691         if(appData.icsActive) return FALSE; // only in local mode
17692         if(!storedGames) return FALSE; // sanity
17693         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17694
17695         PopInner(annotate);
17696         if(currentMove < forwardMostMove) ForwardEvent(); else
17697         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17698
17699         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17700         return TRUE;
17701 }
17702
17703 void
17704 CleanupTail ()
17705 {       // remove all shelved variations
17706         int i;
17707         for(i=0; i<storedGames; i++) {
17708             if(savedDetails[i])
17709                 free(savedDetails[i]);
17710             savedDetails[i] = NULL;
17711         }
17712         for(i=framePtr; i<MAX_MOVES; i++) {
17713                 if(commentList[i]) free(commentList[i]);
17714                 commentList[i] = NULL;
17715         }
17716         framePtr = MAX_MOVES-1;
17717         storedGames = 0;
17718 }
17719
17720 void
17721 LoadVariation (int index, char *text)
17722 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17723         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17724         int level = 0, move;
17725
17726         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17727         // first find outermost bracketing variation
17728         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17729             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17730                 if(*p == '{') wait = '}'; else
17731                 if(*p == '[') wait = ']'; else
17732                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17733                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17734             }
17735             if(*p == wait) wait = NULLCHAR; // closing ]} found
17736             p++;
17737         }
17738         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17739         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17740         end[1] = NULLCHAR; // clip off comment beyond variation
17741         ToNrEvent(currentMove-1);
17742         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17743         // kludge: use ParsePV() to append variation to game
17744         move = currentMove;
17745         ParsePV(start, TRUE, TRUE);
17746         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17747         ClearPremoveHighlights();
17748         CommentPopDown();
17749         ToNrEvent(currentMove+1);
17750 }
17751
17752 void
17753 LoadTheme ()
17754 {
17755     char *p, *q, buf[MSG_SIZ];
17756     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17757         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17758         ParseArgsFromString(buf);
17759         ActivateTheme(TRUE); // also redo colors
17760         return;
17761     }
17762     p = nickName;
17763     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17764     {
17765         int len;
17766         q = appData.themeNames;
17767         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17768       if(appData.useBitmaps) {
17769         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17770                 appData.liteBackTextureFile, appData.darkBackTextureFile,
17771                 appData.liteBackTextureMode,
17772                 appData.darkBackTextureMode );
17773       } else {
17774         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17775                 Col2Text(2),   // lightSquareColor
17776                 Col2Text(3) ); // darkSquareColor
17777       }
17778       if(appData.useBorder) {
17779         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17780                 appData.border);
17781       } else {
17782         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17783       }
17784       if(appData.useFont) {
17785         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17786                 appData.renderPiecesWithFont,
17787                 appData.fontToPieceTable,
17788                 Col2Text(9),    // appData.fontBackColorWhite
17789                 Col2Text(10) ); // appData.fontForeColorBlack
17790       } else {
17791         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17792                 appData.pieceDirectory);
17793         if(!appData.pieceDirectory[0])
17794           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17795                 Col2Text(0),   // whitePieceColor
17796                 Col2Text(1) ); // blackPieceColor
17797       }
17798       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17799                 Col2Text(4),   // highlightSquareColor
17800                 Col2Text(5) ); // premoveHighlightColor
17801         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17802         if(insert != q) insert[-1] = NULLCHAR;
17803         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17804         if(q)   free(q);
17805     }
17806     ActivateTheme(FALSE);
17807 }