Implement board-marker protocol
[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     cps->highlight = 0;
829
830     if (appData.protocolVersion[n] > PROTOVER
831         || appData.protocolVersion[n] < 1)
832       {
833         char buf[MSG_SIZ];
834         int len;
835
836         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
837                        appData.protocolVersion[n]);
838         if( (len >= MSG_SIZ) && appData.debugMode )
839           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
840
841         DisplayFatalError(buf, 0, 2);
842       }
843     else
844       {
845         cps->protocolVersion = appData.protocolVersion[n];
846       }
847
848     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
849     ParseFeatures(appData.featureDefaults, cps);
850 }
851
852 ChessProgramState *savCps;
853
854 GameMode oldMode;
855
856 void
857 LoadEngine ()
858 {
859     int i;
860     if(WaitForEngine(savCps, LoadEngine)) return;
861     CommonEngineInit(); // recalculate time odds
862     if(gameInfo.variant != StringToVariant(appData.variant)) {
863         // we changed variant when loading the engine; this forces us to reset
864         Reset(TRUE, savCps != &first);
865         oldMode = BeginningOfGame; // to prevent restoring old mode
866     }
867     InitChessProgram(savCps, FALSE);
868     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
869     DisplayMessage("", "");
870     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
871     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
872     ThawUI();
873     SetGNUMode();
874     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
875 }
876
877 void
878 ReplaceEngine (ChessProgramState *cps, int n)
879 {
880     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
881     keepInfo = 1;
882     if(oldMode != BeginningOfGame) EditGameEvent();
883     keepInfo = 0;
884     UnloadEngine(cps);
885     appData.noChessProgram = FALSE;
886     appData.clockMode = TRUE;
887     InitEngine(cps, n);
888     UpdateLogos(TRUE);
889     if(n) return; // only startup first engine immediately; second can wait
890     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
891     LoadEngine();
892 }
893
894 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
895 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
896
897 static char resetOptions[] =
898         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
899         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
900         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
901         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
902
903 void
904 FloatToFront(char **list, char *engineLine)
905 {
906     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
907     int i=0;
908     if(appData.recentEngines <= 0) return;
909     TidyProgramName(engineLine, "localhost", tidy+1);
910     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
911     strncpy(buf+1, *list, MSG_SIZ-50);
912     if(p = strstr(buf, tidy)) { // tidy name appears in list
913         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
914         while(*p++ = *++q); // squeeze out
915     }
916     strcat(tidy, buf+1); // put list behind tidy name
917     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
918     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
919     ASSIGN(*list, tidy+1);
920 }
921
922 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
923
924 void
925 Load (ChessProgramState *cps, int i)
926 {
927     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
928     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
929         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
930         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
931         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
932         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
933         appData.firstProtocolVersion = PROTOVER;
934         ParseArgsFromString(buf);
935         SwapEngines(i);
936         ReplaceEngine(cps, i);
937         FloatToFront(&appData.recentEngineList, engineLine);
938         return;
939     }
940     p = engineName;
941     while(q = strchr(p, SLASH)) p = q+1;
942     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
943     if(engineDir[0] != NULLCHAR) {
944         ASSIGN(appData.directory[i], engineDir); p = engineName;
945     } else if(p != engineName) { // derive directory from engine path, when not given
946         p[-1] = 0;
947         ASSIGN(appData.directory[i], engineName);
948         p[-1] = SLASH;
949         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
950     } else { ASSIGN(appData.directory[i], "."); }
951     if(params[0]) {
952         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
953         snprintf(command, MSG_SIZ, "%s %s", p, params);
954         p = command;
955     }
956     ASSIGN(appData.chessProgram[i], p);
957     appData.isUCI[i] = isUCI;
958     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
959     appData.hasOwnBookUCI[i] = hasBook;
960     if(!nickName[0]) useNick = FALSE;
961     if(useNick) ASSIGN(appData.pgnName[i], nickName);
962     if(addToList) {
963         int len;
964         char quote;
965         q = firstChessProgramNames;
966         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
967         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
968         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
969                         quote, p, quote, appData.directory[i],
970                         useNick ? " -fn \"" : "",
971                         useNick ? nickName : "",
972                         useNick ? "\"" : "",
973                         v1 ? " -firstProtocolVersion 1" : "",
974                         hasBook ? "" : " -fNoOwnBookUCI",
975                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
976                         storeVariant ? " -variant " : "",
977                         storeVariant ? VariantName(gameInfo.variant) : "");
978         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
979         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
980         if(insert != q) insert[-1] = NULLCHAR;
981         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
982         if(q)   free(q);
983         FloatToFront(&appData.recentEngineList, buf);
984     }
985     ReplaceEngine(cps, i);
986 }
987
988 void
989 InitTimeControls ()
990 {
991     int matched, min, sec;
992     /*
993      * Parse timeControl resource
994      */
995     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
996                           appData.movesPerSession)) {
997         char buf[MSG_SIZ];
998         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
999         DisplayFatalError(buf, 0, 2);
1000     }
1001
1002     /*
1003      * Parse searchTime resource
1004      */
1005     if (*appData.searchTime != NULLCHAR) {
1006         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1007         if (matched == 1) {
1008             searchTime = min * 60;
1009         } else if (matched == 2) {
1010             searchTime = min * 60 + sec;
1011         } else {
1012             char buf[MSG_SIZ];
1013             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1014             DisplayFatalError(buf, 0, 2);
1015         }
1016     }
1017 }
1018
1019 void
1020 InitBackEnd1 ()
1021 {
1022
1023     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1024     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1025
1026     GetTimeMark(&programStartTime);
1027     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1028     appData.seedBase = random() + (random()<<15);
1029     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1030
1031     ClearProgramStats();
1032     programStats.ok_to_send = 1;
1033     programStats.seen_stat = 0;
1034
1035     /*
1036      * Initialize game list
1037      */
1038     ListNew(&gameList);
1039
1040
1041     /*
1042      * Internet chess server status
1043      */
1044     if (appData.icsActive) {
1045         appData.matchMode = FALSE;
1046         appData.matchGames = 0;
1047 #if ZIPPY
1048         appData.noChessProgram = !appData.zippyPlay;
1049 #else
1050         appData.zippyPlay = FALSE;
1051         appData.zippyTalk = FALSE;
1052         appData.noChessProgram = TRUE;
1053 #endif
1054         if (*appData.icsHelper != NULLCHAR) {
1055             appData.useTelnet = TRUE;
1056             appData.telnetProgram = appData.icsHelper;
1057         }
1058     } else {
1059         appData.zippyTalk = appData.zippyPlay = FALSE;
1060     }
1061
1062     /* [AS] Initialize pv info list [HGM] and game state */
1063     {
1064         int i, j;
1065
1066         for( i=0; i<=framePtr; i++ ) {
1067             pvInfoList[i].depth = -1;
1068             boards[i][EP_STATUS] = EP_NONE;
1069             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1070         }
1071     }
1072
1073     InitTimeControls();
1074
1075     /* [AS] Adjudication threshold */
1076     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1077
1078     InitEngine(&first, 0);
1079     InitEngine(&second, 1);
1080     CommonEngineInit();
1081
1082     pairing.which = "pairing"; // pairing engine
1083     pairing.pr = NoProc;
1084     pairing.isr = NULL;
1085     pairing.program = appData.pairingEngine;
1086     pairing.host = "localhost";
1087     pairing.dir = ".";
1088
1089     if (appData.icsActive) {
1090         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1091     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1092         appData.clockMode = FALSE;
1093         first.sendTime = second.sendTime = 0;
1094     }
1095
1096 #if ZIPPY
1097     /* Override some settings from environment variables, for backward
1098        compatibility.  Unfortunately it's not feasible to have the env
1099        vars just set defaults, at least in xboard.  Ugh.
1100     */
1101     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1102       ZippyInit();
1103     }
1104 #endif
1105
1106     if (!appData.icsActive) {
1107       char buf[MSG_SIZ];
1108       int len;
1109
1110       /* Check for variants that are supported only in ICS mode,
1111          or not at all.  Some that are accepted here nevertheless
1112          have bugs; see comments below.
1113       */
1114       VariantClass variant = StringToVariant(appData.variant);
1115       switch (variant) {
1116       case VariantBughouse:     /* need four players and two boards */
1117       case VariantKriegspiel:   /* need to hide pieces and move details */
1118         /* case VariantFischeRandom: (Fabien: moved below) */
1119         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1120         if( (len >= MSG_SIZ) && appData.debugMode )
1121           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1122
1123         DisplayFatalError(buf, 0, 2);
1124         return;
1125
1126       case VariantUnknown:
1127       case VariantLoadable:
1128       case Variant29:
1129       case Variant30:
1130       case Variant31:
1131       case Variant32:
1132       case Variant33:
1133       case Variant34:
1134       case Variant35:
1135       case Variant36:
1136       default:
1137         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1138         if( (len >= MSG_SIZ) && appData.debugMode )
1139           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1140
1141         DisplayFatalError(buf, 0, 2);
1142         return;
1143
1144       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1145       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1146       case VariantGothic:     /* [HGM] should work */
1147       case VariantCapablanca: /* [HGM] should work */
1148       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1149       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1150       case VariantKnightmate: /* [HGM] should work */
1151       case VariantCylinder:   /* [HGM] untested */
1152       case VariantFalcon:     /* [HGM] untested */
1153       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1154                                  offboard interposition not understood */
1155       case VariantNormal:     /* definitely works! */
1156       case VariantWildCastle: /* pieces not automatically shuffled */
1157       case VariantNoCastle:   /* pieces not automatically shuffled */
1158       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1159       case VariantLosers:     /* should work except for win condition,
1160                                  and doesn't know captures are mandatory */
1161       case VariantSuicide:    /* should work except for win condition,
1162                                  and doesn't know captures are mandatory */
1163       case VariantGiveaway:   /* should work except for win condition,
1164                                  and doesn't know captures are mandatory */
1165       case VariantTwoKings:   /* should work */
1166       case VariantAtomic:     /* should work except for win condition */
1167       case Variant3Check:     /* should work except for win condition */
1168       case VariantShatranj:   /* should work except for all win conditions */
1169       case VariantMakruk:     /* should work except for draw countdown */
1170       case VariantASEAN :     /* should work except for draw countdown */
1171       case VariantBerolina:   /* might work if TestLegality is off */
1172       case VariantCapaRandom: /* should work */
1173       case VariantJanus:      /* should work */
1174       case VariantSuper:      /* experimental */
1175       case VariantGreat:      /* experimental, requires legality testing to be off */
1176       case VariantSChess:     /* S-Chess, should work */
1177       case VariantGrand:      /* should work */
1178       case VariantSpartan:    /* should work */
1179         break;
1180       }
1181     }
1182
1183 }
1184
1185 int
1186 NextIntegerFromString (char ** str, long * value)
1187 {
1188     int result = -1;
1189     char * s = *str;
1190
1191     while( *s == ' ' || *s == '\t' ) {
1192         s++;
1193     }
1194
1195     *value = 0;
1196
1197     if( *s >= '0' && *s <= '9' ) {
1198         while( *s >= '0' && *s <= '9' ) {
1199             *value = *value * 10 + (*s - '0');
1200             s++;
1201         }
1202
1203         result = 0;
1204     }
1205
1206     *str = s;
1207
1208     return result;
1209 }
1210
1211 int
1212 NextTimeControlFromString (char ** str, long * value)
1213 {
1214     long temp;
1215     int result = NextIntegerFromString( str, &temp );
1216
1217     if( result == 0 ) {
1218         *value = temp * 60; /* Minutes */
1219         if( **str == ':' ) {
1220             (*str)++;
1221             result = NextIntegerFromString( str, &temp );
1222             *value += temp; /* Seconds */
1223         }
1224     }
1225
1226     return result;
1227 }
1228
1229 int
1230 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1231 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1232     int result = -1, type = 0; long temp, temp2;
1233
1234     if(**str != ':') return -1; // old params remain in force!
1235     (*str)++;
1236     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1237     if( NextIntegerFromString( str, &temp ) ) return -1;
1238     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1239
1240     if(**str != '/') {
1241         /* time only: incremental or sudden-death time control */
1242         if(**str == '+') { /* increment follows; read it */
1243             (*str)++;
1244             if(**str == '!') type = *(*str)++; // Bronstein TC
1245             if(result = NextIntegerFromString( str, &temp2)) return -1;
1246             *inc = temp2 * 1000;
1247             if(**str == '.') { // read fraction of increment
1248                 char *start = ++(*str);
1249                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1250                 temp2 *= 1000;
1251                 while(start++ < *str) temp2 /= 10;
1252                 *inc += temp2;
1253             }
1254         } else *inc = 0;
1255         *moves = 0; *tc = temp * 1000; *incType = type;
1256         return 0;
1257     }
1258
1259     (*str)++; /* classical time control */
1260     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1261
1262     if(result == 0) {
1263         *moves = temp;
1264         *tc    = temp2 * 1000;
1265         *inc   = 0;
1266         *incType = type;
1267     }
1268     return result;
1269 }
1270
1271 int
1272 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1273 {   /* [HGM] get time to add from the multi-session time-control string */
1274     int incType, moves=1; /* kludge to force reading of first session */
1275     long time, increment;
1276     char *s = tcString;
1277
1278     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1279     do {
1280         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1281         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1282         if(movenr == -1) return time;    /* last move before new session     */
1283         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1284         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1285         if(!moves) return increment;     /* current session is incremental   */
1286         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1287     } while(movenr >= -1);               /* try again for next session       */
1288
1289     return 0; // no new time quota on this move
1290 }
1291
1292 int
1293 ParseTimeControl (char *tc, float ti, int mps)
1294 {
1295   long tc1;
1296   long tc2;
1297   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1298   int min, sec=0;
1299
1300   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1301   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1302       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1303   if(ti > 0) {
1304
1305     if(mps)
1306       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1307     else
1308       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1309   } else {
1310     if(mps)
1311       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1312     else
1313       snprintf(buf, MSG_SIZ, ":%s", mytc);
1314   }
1315   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1316
1317   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1318     return FALSE;
1319   }
1320
1321   if( *tc == '/' ) {
1322     /* Parse second time control */
1323     tc++;
1324
1325     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1326       return FALSE;
1327     }
1328
1329     if( tc2 == 0 ) {
1330       return FALSE;
1331     }
1332
1333     timeControl_2 = tc2 * 1000;
1334   }
1335   else {
1336     timeControl_2 = 0;
1337   }
1338
1339   if( tc1 == 0 ) {
1340     return FALSE;
1341   }
1342
1343   timeControl = tc1 * 1000;
1344
1345   if (ti >= 0) {
1346     timeIncrement = ti * 1000;  /* convert to ms */
1347     movesPerSession = 0;
1348   } else {
1349     timeIncrement = 0;
1350     movesPerSession = mps;
1351   }
1352   return TRUE;
1353 }
1354
1355 void
1356 InitBackEnd2 ()
1357 {
1358     if (appData.debugMode) {
1359 #    ifdef __GIT_VERSION
1360       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1361 #    else
1362       fprintf(debugFP, "Version: %s\n", programVersion);
1363 #    endif
1364     }
1365     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1366
1367     set_cont_sequence(appData.wrapContSeq);
1368     if (appData.matchGames > 0) {
1369         appData.matchMode = TRUE;
1370     } else if (appData.matchMode) {
1371         appData.matchGames = 1;
1372     }
1373     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1374         appData.matchGames = appData.sameColorGames;
1375     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1376         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1377         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1378     }
1379     Reset(TRUE, FALSE);
1380     if (appData.noChessProgram || first.protocolVersion == 1) {
1381       InitBackEnd3();
1382     } else {
1383       /* kludge: allow timeout for initial "feature" commands */
1384       FreezeUI();
1385       DisplayMessage("", _("Starting chess program"));
1386       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1387     }
1388 }
1389
1390 int
1391 CalculateIndex (int index, int gameNr)
1392 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1393     int res;
1394     if(index > 0) return index; // fixed nmber
1395     if(index == 0) return 1;
1396     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1397     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1398     return res;
1399 }
1400
1401 int
1402 LoadGameOrPosition (int gameNr)
1403 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1404     if (*appData.loadGameFile != NULLCHAR) {
1405         if (!LoadGameFromFile(appData.loadGameFile,
1406                 CalculateIndex(appData.loadGameIndex, gameNr),
1407                               appData.loadGameFile, FALSE)) {
1408             DisplayFatalError(_("Bad game file"), 0, 1);
1409             return 0;
1410         }
1411     } else if (*appData.loadPositionFile != NULLCHAR) {
1412         if (!LoadPositionFromFile(appData.loadPositionFile,
1413                 CalculateIndex(appData.loadPositionIndex, gameNr),
1414                                   appData.loadPositionFile)) {
1415             DisplayFatalError(_("Bad position file"), 0, 1);
1416             return 0;
1417         }
1418     }
1419     return 1;
1420 }
1421
1422 void
1423 ReserveGame (int gameNr, char resChar)
1424 {
1425     FILE *tf = fopen(appData.tourneyFile, "r+");
1426     char *p, *q, c, buf[MSG_SIZ];
1427     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1428     safeStrCpy(buf, lastMsg, MSG_SIZ);
1429     DisplayMessage(_("Pick new game"), "");
1430     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1431     ParseArgsFromFile(tf);
1432     p = q = appData.results;
1433     if(appData.debugMode) {
1434       char *r = appData.participants;
1435       fprintf(debugFP, "results = '%s'\n", p);
1436       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1437       fprintf(debugFP, "\n");
1438     }
1439     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1440     nextGame = q - p;
1441     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1442     safeStrCpy(q, p, strlen(p) + 2);
1443     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1444     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1445     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1446         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1447         q[nextGame] = '*';
1448     }
1449     fseek(tf, -(strlen(p)+4), SEEK_END);
1450     c = fgetc(tf);
1451     if(c != '"') // depending on DOS or Unix line endings we can be one off
1452          fseek(tf, -(strlen(p)+2), SEEK_END);
1453     else fseek(tf, -(strlen(p)+3), SEEK_END);
1454     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1455     DisplayMessage(buf, "");
1456     free(p); appData.results = q;
1457     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1458        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1459       int round = appData.defaultMatchGames * appData.tourneyType;
1460       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1461          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1462         UnloadEngine(&first);  // next game belongs to other pairing;
1463         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1464     }
1465     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1466 }
1467
1468 void
1469 MatchEvent (int mode)
1470 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1471         int dummy;
1472         if(matchMode) { // already in match mode: switch it off
1473             abortMatch = TRUE;
1474             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1475             return;
1476         }
1477 //      if(gameMode != BeginningOfGame) {
1478 //          DisplayError(_("You can only start a match from the initial position."), 0);
1479 //          return;
1480 //      }
1481         abortMatch = FALSE;
1482         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1483         /* Set up machine vs. machine match */
1484         nextGame = 0;
1485         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1486         if(appData.tourneyFile[0]) {
1487             ReserveGame(-1, 0);
1488             if(nextGame > appData.matchGames) {
1489                 char buf[MSG_SIZ];
1490                 if(strchr(appData.results, '*') == NULL) {
1491                     FILE *f;
1492                     appData.tourneyCycles++;
1493                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1494                         fclose(f);
1495                         NextTourneyGame(-1, &dummy);
1496                         ReserveGame(-1, 0);
1497                         if(nextGame <= appData.matchGames) {
1498                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1499                             matchMode = mode;
1500                             ScheduleDelayedEvent(NextMatchGame, 10000);
1501                             return;
1502                         }
1503                     }
1504                 }
1505                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1506                 DisplayError(buf, 0);
1507                 appData.tourneyFile[0] = 0;
1508                 return;
1509             }
1510         } else
1511         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1512             DisplayFatalError(_("Can't have a match with no chess programs"),
1513                               0, 2);
1514             return;
1515         }
1516         matchMode = mode;
1517         matchGame = roundNr = 1;
1518         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1519         NextMatchGame();
1520 }
1521
1522 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1523
1524 void
1525 InitBackEnd3 P((void))
1526 {
1527     GameMode initialMode;
1528     char buf[MSG_SIZ];
1529     int err, len;
1530
1531     InitChessProgram(&first, startedFromSetupPosition);
1532
1533     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1534         free(programVersion);
1535         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1536         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1537         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1538     }
1539
1540     if (appData.icsActive) {
1541 #ifdef WIN32
1542         /* [DM] Make a console window if needed [HGM] merged ifs */
1543         ConsoleCreate();
1544 #endif
1545         err = establish();
1546         if (err != 0)
1547           {
1548             if (*appData.icsCommPort != NULLCHAR)
1549               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1550                              appData.icsCommPort);
1551             else
1552               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1553                         appData.icsHost, appData.icsPort);
1554
1555             if( (len >= MSG_SIZ) && appData.debugMode )
1556               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1557
1558             DisplayFatalError(buf, err, 1);
1559             return;
1560         }
1561         SetICSMode();
1562         telnetISR =
1563           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1564         fromUserISR =
1565           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1566         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1567             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1568     } else if (appData.noChessProgram) {
1569         SetNCPMode();
1570     } else {
1571         SetGNUMode();
1572     }
1573
1574     if (*appData.cmailGameName != NULLCHAR) {
1575         SetCmailMode();
1576         OpenLoopback(&cmailPR);
1577         cmailISR =
1578           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1579     }
1580
1581     ThawUI();
1582     DisplayMessage("", "");
1583     if (StrCaseCmp(appData.initialMode, "") == 0) {
1584       initialMode = BeginningOfGame;
1585       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1586         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1587         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1588         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1589         ModeHighlight();
1590       }
1591     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1592       initialMode = TwoMachinesPlay;
1593     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1594       initialMode = AnalyzeFile;
1595     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1596       initialMode = AnalyzeMode;
1597     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1598       initialMode = MachinePlaysWhite;
1599     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1600       initialMode = MachinePlaysBlack;
1601     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1602       initialMode = EditGame;
1603     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1604       initialMode = EditPosition;
1605     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1606       initialMode = Training;
1607     } else {
1608       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1609       if( (len >= MSG_SIZ) && appData.debugMode )
1610         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1611
1612       DisplayFatalError(buf, 0, 2);
1613       return;
1614     }
1615
1616     if (appData.matchMode) {
1617         if(appData.tourneyFile[0]) { // start tourney from command line
1618             FILE *f;
1619             if(f = fopen(appData.tourneyFile, "r")) {
1620                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1621                 fclose(f);
1622                 appData.clockMode = TRUE;
1623                 SetGNUMode();
1624             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1625         }
1626         MatchEvent(TRUE);
1627     } else if (*appData.cmailGameName != NULLCHAR) {
1628         /* Set up cmail mode */
1629         ReloadCmailMsgEvent(TRUE);
1630     } else {
1631         /* Set up other modes */
1632         if (initialMode == AnalyzeFile) {
1633           if (*appData.loadGameFile == NULLCHAR) {
1634             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1635             return;
1636           }
1637         }
1638         if (*appData.loadGameFile != NULLCHAR) {
1639             (void) LoadGameFromFile(appData.loadGameFile,
1640                                     appData.loadGameIndex,
1641                                     appData.loadGameFile, TRUE);
1642         } else if (*appData.loadPositionFile != NULLCHAR) {
1643             (void) LoadPositionFromFile(appData.loadPositionFile,
1644                                         appData.loadPositionIndex,
1645                                         appData.loadPositionFile);
1646             /* [HGM] try to make self-starting even after FEN load */
1647             /* to allow automatic setup of fairy variants with wtm */
1648             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1649                 gameMode = BeginningOfGame;
1650                 setboardSpoiledMachineBlack = 1;
1651             }
1652             /* [HGM] loadPos: make that every new game uses the setup */
1653             /* from file as long as we do not switch variant          */
1654             if(!blackPlaysFirst) {
1655                 startedFromPositionFile = TRUE;
1656                 CopyBoard(filePosition, boards[0]);
1657             }
1658         }
1659         if (initialMode == AnalyzeMode) {
1660           if (appData.noChessProgram) {
1661             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1662             return;
1663           }
1664           if (appData.icsActive) {
1665             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1666             return;
1667           }
1668           AnalyzeModeEvent();
1669         } else if (initialMode == AnalyzeFile) {
1670           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1671           ShowThinkingEvent();
1672           AnalyzeFileEvent();
1673           AnalysisPeriodicEvent(1);
1674         } else if (initialMode == MachinePlaysWhite) {
1675           if (appData.noChessProgram) {
1676             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1677                               0, 2);
1678             return;
1679           }
1680           if (appData.icsActive) {
1681             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1682                               0, 2);
1683             return;
1684           }
1685           MachineWhiteEvent();
1686         } else if (initialMode == MachinePlaysBlack) {
1687           if (appData.noChessProgram) {
1688             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1689                               0, 2);
1690             return;
1691           }
1692           if (appData.icsActive) {
1693             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1694                               0, 2);
1695             return;
1696           }
1697           MachineBlackEvent();
1698         } else if (initialMode == TwoMachinesPlay) {
1699           if (appData.noChessProgram) {
1700             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1701                               0, 2);
1702             return;
1703           }
1704           if (appData.icsActive) {
1705             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1706                               0, 2);
1707             return;
1708           }
1709           TwoMachinesEvent();
1710         } else if (initialMode == EditGame) {
1711           EditGameEvent();
1712         } else if (initialMode == EditPosition) {
1713           EditPositionEvent();
1714         } else if (initialMode == Training) {
1715           if (*appData.loadGameFile == NULLCHAR) {
1716             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1717             return;
1718           }
1719           TrainingEvent();
1720         }
1721     }
1722 }
1723
1724 void
1725 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1726 {
1727     DisplayBook(current+1);
1728
1729     MoveHistorySet( movelist, first, last, current, pvInfoList );
1730
1731     EvalGraphSet( first, last, current, pvInfoList );
1732
1733     MakeEngineOutputTitle();
1734 }
1735
1736 /*
1737  * Establish will establish a contact to a remote host.port.
1738  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1739  *  used to talk to the host.
1740  * Returns 0 if okay, error code if not.
1741  */
1742 int
1743 establish ()
1744 {
1745     char buf[MSG_SIZ];
1746
1747     if (*appData.icsCommPort != NULLCHAR) {
1748         /* Talk to the host through a serial comm port */
1749         return OpenCommPort(appData.icsCommPort, &icsPR);
1750
1751     } else if (*appData.gateway != NULLCHAR) {
1752         if (*appData.remoteShell == NULLCHAR) {
1753             /* Use the rcmd protocol to run telnet program on a gateway host */
1754             snprintf(buf, sizeof(buf), "%s %s %s",
1755                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1756             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1757
1758         } else {
1759             /* Use the rsh program to run telnet program on a gateway host */
1760             if (*appData.remoteUser == NULLCHAR) {
1761                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1762                         appData.gateway, appData.telnetProgram,
1763                         appData.icsHost, appData.icsPort);
1764             } else {
1765                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1766                         appData.remoteShell, appData.gateway,
1767                         appData.remoteUser, appData.telnetProgram,
1768                         appData.icsHost, appData.icsPort);
1769             }
1770             return StartChildProcess(buf, "", &icsPR);
1771
1772         }
1773     } else if (appData.useTelnet) {
1774         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1775
1776     } else {
1777         /* TCP socket interface differs somewhat between
1778            Unix and NT; handle details in the front end.
1779            */
1780         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1781     }
1782 }
1783
1784 void
1785 EscapeExpand (char *p, char *q)
1786 {       // [HGM] initstring: routine to shape up string arguments
1787         while(*p++ = *q++) if(p[-1] == '\\')
1788             switch(*q++) {
1789                 case 'n': p[-1] = '\n'; break;
1790                 case 'r': p[-1] = '\r'; break;
1791                 case 't': p[-1] = '\t'; break;
1792                 case '\\': p[-1] = '\\'; break;
1793                 case 0: *p = 0; return;
1794                 default: p[-1] = q[-1]; break;
1795             }
1796 }
1797
1798 void
1799 show_bytes (FILE *fp, char *buf, int count)
1800 {
1801     while (count--) {
1802         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1803             fprintf(fp, "\\%03o", *buf & 0xff);
1804         } else {
1805             putc(*buf, fp);
1806         }
1807         buf++;
1808     }
1809     fflush(fp);
1810 }
1811
1812 /* Returns an errno value */
1813 int
1814 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1815 {
1816     char buf[8192], *p, *q, *buflim;
1817     int left, newcount, outcount;
1818
1819     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1820         *appData.gateway != NULLCHAR) {
1821         if (appData.debugMode) {
1822             fprintf(debugFP, ">ICS: ");
1823             show_bytes(debugFP, message, count);
1824             fprintf(debugFP, "\n");
1825         }
1826         return OutputToProcess(pr, message, count, outError);
1827     }
1828
1829     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1830     p = message;
1831     q = buf;
1832     left = count;
1833     newcount = 0;
1834     while (left) {
1835         if (q >= buflim) {
1836             if (appData.debugMode) {
1837                 fprintf(debugFP, ">ICS: ");
1838                 show_bytes(debugFP, buf, newcount);
1839                 fprintf(debugFP, "\n");
1840             }
1841             outcount = OutputToProcess(pr, buf, newcount, outError);
1842             if (outcount < newcount) return -1; /* to be sure */
1843             q = buf;
1844             newcount = 0;
1845         }
1846         if (*p == '\n') {
1847             *q++ = '\r';
1848             newcount++;
1849         } else if (((unsigned char) *p) == TN_IAC) {
1850             *q++ = (char) TN_IAC;
1851             newcount ++;
1852         }
1853         *q++ = *p++;
1854         newcount++;
1855         left--;
1856     }
1857     if (appData.debugMode) {
1858         fprintf(debugFP, ">ICS: ");
1859         show_bytes(debugFP, buf, newcount);
1860         fprintf(debugFP, "\n");
1861     }
1862     outcount = OutputToProcess(pr, buf, newcount, outError);
1863     if (outcount < newcount) return -1; /* to be sure */
1864     return count;
1865 }
1866
1867 void
1868 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1869 {
1870     int outError, outCount;
1871     static int gotEof = 0;
1872     static FILE *ini;
1873
1874     /* Pass data read from player on to ICS */
1875     if (count > 0) {
1876         gotEof = 0;
1877         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1878         if (outCount < count) {
1879             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1880         }
1881         if(have_sent_ICS_logon == 2) {
1882           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1883             fprintf(ini, "%s", message);
1884             have_sent_ICS_logon = 3;
1885           } else
1886             have_sent_ICS_logon = 1;
1887         } else if(have_sent_ICS_logon == 3) {
1888             fprintf(ini, "%s", message);
1889             fclose(ini);
1890           have_sent_ICS_logon = 1;
1891         }
1892     } else if (count < 0) {
1893         RemoveInputSource(isr);
1894         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1895     } else if (gotEof++ > 0) {
1896         RemoveInputSource(isr);
1897         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1898     }
1899 }
1900
1901 void
1902 KeepAlive ()
1903 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1904     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1905     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1906     SendToICS("date\n");
1907     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1908 }
1909
1910 /* added routine for printf style output to ics */
1911 void
1912 ics_printf (char *format, ...)
1913 {
1914     char buffer[MSG_SIZ];
1915     va_list args;
1916
1917     va_start(args, format);
1918     vsnprintf(buffer, sizeof(buffer), format, args);
1919     buffer[sizeof(buffer)-1] = '\0';
1920     SendToICS(buffer);
1921     va_end(args);
1922 }
1923
1924 void
1925 SendToICS (char *s)
1926 {
1927     int count, outCount, outError;
1928
1929     if (icsPR == NoProc) return;
1930
1931     count = strlen(s);
1932     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1933     if (outCount < count) {
1934         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1935     }
1936 }
1937
1938 /* This is used for sending logon scripts to the ICS. Sending
1939    without a delay causes problems when using timestamp on ICC
1940    (at least on my machine). */
1941 void
1942 SendToICSDelayed (char *s, long msdelay)
1943 {
1944     int count, outCount, outError;
1945
1946     if (icsPR == NoProc) return;
1947
1948     count = strlen(s);
1949     if (appData.debugMode) {
1950         fprintf(debugFP, ">ICS: ");
1951         show_bytes(debugFP, s, count);
1952         fprintf(debugFP, "\n");
1953     }
1954     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1955                                       msdelay);
1956     if (outCount < count) {
1957         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1958     }
1959 }
1960
1961
1962 /* Remove all highlighting escape sequences in s
1963    Also deletes any suffix starting with '('
1964    */
1965 char *
1966 StripHighlightAndTitle (char *s)
1967 {
1968     static char retbuf[MSG_SIZ];
1969     char *p = retbuf;
1970
1971     while (*s != NULLCHAR) {
1972         while (*s == '\033') {
1973             while (*s != NULLCHAR && !isalpha(*s)) s++;
1974             if (*s != NULLCHAR) s++;
1975         }
1976         while (*s != NULLCHAR && *s != '\033') {
1977             if (*s == '(' || *s == '[') {
1978                 *p = NULLCHAR;
1979                 return retbuf;
1980             }
1981             *p++ = *s++;
1982         }
1983     }
1984     *p = NULLCHAR;
1985     return retbuf;
1986 }
1987
1988 /* Remove all highlighting escape sequences in s */
1989 char *
1990 StripHighlight (char *s)
1991 {
1992     static char retbuf[MSG_SIZ];
1993     char *p = retbuf;
1994
1995     while (*s != NULLCHAR) {
1996         while (*s == '\033') {
1997             while (*s != NULLCHAR && !isalpha(*s)) s++;
1998             if (*s != NULLCHAR) s++;
1999         }
2000         while (*s != NULLCHAR && *s != '\033') {
2001             *p++ = *s++;
2002         }
2003     }
2004     *p = NULLCHAR;
2005     return retbuf;
2006 }
2007
2008 char *variantNames[] = VARIANT_NAMES;
2009 char *
2010 VariantName (VariantClass v)
2011 {
2012     return variantNames[v];
2013 }
2014
2015
2016 /* Identify a variant from the strings the chess servers use or the
2017    PGN Variant tag names we use. */
2018 VariantClass
2019 StringToVariant (char *e)
2020 {
2021     char *p;
2022     int wnum = -1;
2023     VariantClass v = VariantNormal;
2024     int i, found = FALSE;
2025     char buf[MSG_SIZ];
2026     int len;
2027
2028     if (!e) return v;
2029
2030     /* [HGM] skip over optional board-size prefixes */
2031     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2032         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2033         while( *e++ != '_');
2034     }
2035
2036     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2037         v = VariantNormal;
2038         found = TRUE;
2039     } else
2040     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2041       if (StrCaseStr(e, variantNames[i])) {
2042         v = (VariantClass) i;
2043         found = TRUE;
2044         break;
2045       }
2046     }
2047
2048     if (!found) {
2049       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2050           || StrCaseStr(e, "wild/fr")
2051           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2052         v = VariantFischeRandom;
2053       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2054                  (i = 1, p = StrCaseStr(e, "w"))) {
2055         p += i;
2056         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2057         if (isdigit(*p)) {
2058           wnum = atoi(p);
2059         } else {
2060           wnum = -1;
2061         }
2062         switch (wnum) {
2063         case 0: /* FICS only, actually */
2064         case 1:
2065           /* Castling legal even if K starts on d-file */
2066           v = VariantWildCastle;
2067           break;
2068         case 2:
2069         case 3:
2070         case 4:
2071           /* Castling illegal even if K & R happen to start in
2072              normal positions. */
2073           v = VariantNoCastle;
2074           break;
2075         case 5:
2076         case 7:
2077         case 8:
2078         case 10:
2079         case 11:
2080         case 12:
2081         case 13:
2082         case 14:
2083         case 15:
2084         case 18:
2085         case 19:
2086           /* Castling legal iff K & R start in normal positions */
2087           v = VariantNormal;
2088           break;
2089         case 6:
2090         case 20:
2091         case 21:
2092           /* Special wilds for position setup; unclear what to do here */
2093           v = VariantLoadable;
2094           break;
2095         case 9:
2096           /* Bizarre ICC game */
2097           v = VariantTwoKings;
2098           break;
2099         case 16:
2100           v = VariantKriegspiel;
2101           break;
2102         case 17:
2103           v = VariantLosers;
2104           break;
2105         case 22:
2106           v = VariantFischeRandom;
2107           break;
2108         case 23:
2109           v = VariantCrazyhouse;
2110           break;
2111         case 24:
2112           v = VariantBughouse;
2113           break;
2114         case 25:
2115           v = Variant3Check;
2116           break;
2117         case 26:
2118           /* Not quite the same as FICS suicide! */
2119           v = VariantGiveaway;
2120           break;
2121         case 27:
2122           v = VariantAtomic;
2123           break;
2124         case 28:
2125           v = VariantShatranj;
2126           break;
2127
2128         /* Temporary names for future ICC types.  The name *will* change in
2129            the next xboard/WinBoard release after ICC defines it. */
2130         case 29:
2131           v = Variant29;
2132           break;
2133         case 30:
2134           v = Variant30;
2135           break;
2136         case 31:
2137           v = Variant31;
2138           break;
2139         case 32:
2140           v = Variant32;
2141           break;
2142         case 33:
2143           v = Variant33;
2144           break;
2145         case 34:
2146           v = Variant34;
2147           break;
2148         case 35:
2149           v = Variant35;
2150           break;
2151         case 36:
2152           v = Variant36;
2153           break;
2154         case 37:
2155           v = VariantShogi;
2156           break;
2157         case 38:
2158           v = VariantXiangqi;
2159           break;
2160         case 39:
2161           v = VariantCourier;
2162           break;
2163         case 40:
2164           v = VariantGothic;
2165           break;
2166         case 41:
2167           v = VariantCapablanca;
2168           break;
2169         case 42:
2170           v = VariantKnightmate;
2171           break;
2172         case 43:
2173           v = VariantFairy;
2174           break;
2175         case 44:
2176           v = VariantCylinder;
2177           break;
2178         case 45:
2179           v = VariantFalcon;
2180           break;
2181         case 46:
2182           v = VariantCapaRandom;
2183           break;
2184         case 47:
2185           v = VariantBerolina;
2186           break;
2187         case 48:
2188           v = VariantJanus;
2189           break;
2190         case 49:
2191           v = VariantSuper;
2192           break;
2193         case 50:
2194           v = VariantGreat;
2195           break;
2196         case -1:
2197           /* Found "wild" or "w" in the string but no number;
2198              must assume it's normal chess. */
2199           v = VariantNormal;
2200           break;
2201         default:
2202           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2203           if( (len >= MSG_SIZ) && appData.debugMode )
2204             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2205
2206           DisplayError(buf, 0);
2207           v = VariantUnknown;
2208           break;
2209         }
2210       }
2211     }
2212     if (appData.debugMode) {
2213       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2214               e, wnum, VariantName(v));
2215     }
2216     return v;
2217 }
2218
2219 static int leftover_start = 0, leftover_len = 0;
2220 char star_match[STAR_MATCH_N][MSG_SIZ];
2221
2222 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2223    advance *index beyond it, and set leftover_start to the new value of
2224    *index; else return FALSE.  If pattern contains the character '*', it
2225    matches any sequence of characters not containing '\r', '\n', or the
2226    character following the '*' (if any), and the matched sequence(s) are
2227    copied into star_match.
2228    */
2229 int
2230 looking_at ( char *buf, int *index, char *pattern)
2231 {
2232     char *bufp = &buf[*index], *patternp = pattern;
2233     int star_count = 0;
2234     char *matchp = star_match[0];
2235
2236     for (;;) {
2237         if (*patternp == NULLCHAR) {
2238             *index = leftover_start = bufp - buf;
2239             *matchp = NULLCHAR;
2240             return TRUE;
2241         }
2242         if (*bufp == NULLCHAR) return FALSE;
2243         if (*patternp == '*') {
2244             if (*bufp == *(patternp + 1)) {
2245                 *matchp = NULLCHAR;
2246                 matchp = star_match[++star_count];
2247                 patternp += 2;
2248                 bufp++;
2249                 continue;
2250             } else if (*bufp == '\n' || *bufp == '\r') {
2251                 patternp++;
2252                 if (*patternp == NULLCHAR)
2253                   continue;
2254                 else
2255                   return FALSE;
2256             } else {
2257                 *matchp++ = *bufp++;
2258                 continue;
2259             }
2260         }
2261         if (*patternp != *bufp) return FALSE;
2262         patternp++;
2263         bufp++;
2264     }
2265 }
2266
2267 void
2268 SendToPlayer (char *data, int length)
2269 {
2270     int error, outCount;
2271     outCount = OutputToProcess(NoProc, data, length, &error);
2272     if (outCount < length) {
2273         DisplayFatalError(_("Error writing to display"), error, 1);
2274     }
2275 }
2276
2277 void
2278 PackHolding (char packed[], char *holding)
2279 {
2280     char *p = holding;
2281     char *q = packed;
2282     int runlength = 0;
2283     int curr = 9999;
2284     do {
2285         if (*p == curr) {
2286             runlength++;
2287         } else {
2288             switch (runlength) {
2289               case 0:
2290                 break;
2291               case 1:
2292                 *q++ = curr;
2293                 break;
2294               case 2:
2295                 *q++ = curr;
2296                 *q++ = curr;
2297                 break;
2298               default:
2299                 sprintf(q, "%d", runlength);
2300                 while (*q) q++;
2301                 *q++ = curr;
2302                 break;
2303             }
2304             runlength = 1;
2305             curr = *p;
2306         }
2307     } while (*p++);
2308     *q = NULLCHAR;
2309 }
2310
2311 /* Telnet protocol requests from the front end */
2312 void
2313 TelnetRequest (unsigned char ddww, unsigned char option)
2314 {
2315     unsigned char msg[3];
2316     int outCount, outError;
2317
2318     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2319
2320     if (appData.debugMode) {
2321         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2322         switch (ddww) {
2323           case TN_DO:
2324             ddwwStr = "DO";
2325             break;
2326           case TN_DONT:
2327             ddwwStr = "DONT";
2328             break;
2329           case TN_WILL:
2330             ddwwStr = "WILL";
2331             break;
2332           case TN_WONT:
2333             ddwwStr = "WONT";
2334             break;
2335           default:
2336             ddwwStr = buf1;
2337             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2338             break;
2339         }
2340         switch (option) {
2341           case TN_ECHO:
2342             optionStr = "ECHO";
2343             break;
2344           default:
2345             optionStr = buf2;
2346             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2347             break;
2348         }
2349         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2350     }
2351     msg[0] = TN_IAC;
2352     msg[1] = ddww;
2353     msg[2] = option;
2354     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2355     if (outCount < 3) {
2356         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2357     }
2358 }
2359
2360 void
2361 DoEcho ()
2362 {
2363     if (!appData.icsActive) return;
2364     TelnetRequest(TN_DO, TN_ECHO);
2365 }
2366
2367 void
2368 DontEcho ()
2369 {
2370     if (!appData.icsActive) return;
2371     TelnetRequest(TN_DONT, TN_ECHO);
2372 }
2373
2374 void
2375 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2376 {
2377     /* put the holdings sent to us by the server on the board holdings area */
2378     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2379     char p;
2380     ChessSquare piece;
2381
2382     if(gameInfo.holdingsWidth < 2)  return;
2383     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2384         return; // prevent overwriting by pre-board holdings
2385
2386     if( (int)lowestPiece >= BlackPawn ) {
2387         holdingsColumn = 0;
2388         countsColumn = 1;
2389         holdingsStartRow = BOARD_HEIGHT-1;
2390         direction = -1;
2391     } else {
2392         holdingsColumn = BOARD_WIDTH-1;
2393         countsColumn = BOARD_WIDTH-2;
2394         holdingsStartRow = 0;
2395         direction = 1;
2396     }
2397
2398     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2399         board[i][holdingsColumn] = EmptySquare;
2400         board[i][countsColumn]   = (ChessSquare) 0;
2401     }
2402     while( (p=*holdings++) != NULLCHAR ) {
2403         piece = CharToPiece( ToUpper(p) );
2404         if(piece == EmptySquare) continue;
2405         /*j = (int) piece - (int) WhitePawn;*/
2406         j = PieceToNumber(piece);
2407         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2408         if(j < 0) continue;               /* should not happen */
2409         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2410         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2411         board[holdingsStartRow+j*direction][countsColumn]++;
2412     }
2413 }
2414
2415
2416 void
2417 VariantSwitch (Board board, VariantClass newVariant)
2418 {
2419    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2420    static Board oldBoard;
2421
2422    startedFromPositionFile = FALSE;
2423    if(gameInfo.variant == newVariant) return;
2424
2425    /* [HGM] This routine is called each time an assignment is made to
2426     * gameInfo.variant during a game, to make sure the board sizes
2427     * are set to match the new variant. If that means adding or deleting
2428     * holdings, we shift the playing board accordingly
2429     * This kludge is needed because in ICS observe mode, we get boards
2430     * of an ongoing game without knowing the variant, and learn about the
2431     * latter only later. This can be because of the move list we requested,
2432     * in which case the game history is refilled from the beginning anyway,
2433     * but also when receiving holdings of a crazyhouse game. In the latter
2434     * case we want to add those holdings to the already received position.
2435     */
2436
2437
2438    if (appData.debugMode) {
2439      fprintf(debugFP, "Switch board from %s to %s\n",
2440              VariantName(gameInfo.variant), VariantName(newVariant));
2441      setbuf(debugFP, NULL);
2442    }
2443    shuffleOpenings = 0;       /* [HGM] shuffle */
2444    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2445    switch(newVariant)
2446      {
2447      case VariantShogi:
2448        newWidth = 9;  newHeight = 9;
2449        gameInfo.holdingsSize = 7;
2450      case VariantBughouse:
2451      case VariantCrazyhouse:
2452        newHoldingsWidth = 2; break;
2453      case VariantGreat:
2454        newWidth = 10;
2455      case VariantSuper:
2456        newHoldingsWidth = 2;
2457        gameInfo.holdingsSize = 8;
2458        break;
2459      case VariantGothic:
2460      case VariantCapablanca:
2461      case VariantCapaRandom:
2462        newWidth = 10;
2463      default:
2464        newHoldingsWidth = gameInfo.holdingsSize = 0;
2465      };
2466
2467    if(newWidth  != gameInfo.boardWidth  ||
2468       newHeight != gameInfo.boardHeight ||
2469       newHoldingsWidth != gameInfo.holdingsWidth ) {
2470
2471      /* shift position to new playing area, if needed */
2472      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2473        for(i=0; i<BOARD_HEIGHT; i++)
2474          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2475            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2476              board[i][j];
2477        for(i=0; i<newHeight; i++) {
2478          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2479          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2480        }
2481      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2482        for(i=0; i<BOARD_HEIGHT; i++)
2483          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2484            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2485              board[i][j];
2486      }
2487      board[HOLDINGS_SET] = 0;
2488      gameInfo.boardWidth  = newWidth;
2489      gameInfo.boardHeight = newHeight;
2490      gameInfo.holdingsWidth = newHoldingsWidth;
2491      gameInfo.variant = newVariant;
2492      InitDrawingSizes(-2, 0);
2493    } else gameInfo.variant = newVariant;
2494    CopyBoard(oldBoard, board);   // remember correctly formatted board
2495      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2496    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2497 }
2498
2499 static int loggedOn = FALSE;
2500
2501 /*-- Game start info cache: --*/
2502 int gs_gamenum;
2503 char gs_kind[MSG_SIZ];
2504 static char player1Name[128] = "";
2505 static char player2Name[128] = "";
2506 static char cont_seq[] = "\n\\   ";
2507 static int player1Rating = -1;
2508 static int player2Rating = -1;
2509 /*----------------------------*/
2510
2511 ColorClass curColor = ColorNormal;
2512 int suppressKibitz = 0;
2513
2514 // [HGM] seekgraph
2515 Boolean soughtPending = FALSE;
2516 Boolean seekGraphUp;
2517 #define MAX_SEEK_ADS 200
2518 #define SQUARE 0x80
2519 char *seekAdList[MAX_SEEK_ADS];
2520 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2521 float tcList[MAX_SEEK_ADS];
2522 char colorList[MAX_SEEK_ADS];
2523 int nrOfSeekAds = 0;
2524 int minRating = 1010, maxRating = 2800;
2525 int hMargin = 10, vMargin = 20, h, w;
2526 extern int squareSize, lineGap;
2527
2528 void
2529 PlotSeekAd (int i)
2530 {
2531         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2532         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2533         if(r < minRating+100 && r >=0 ) r = minRating+100;
2534         if(r > maxRating) r = maxRating;
2535         if(tc < 1.f) tc = 1.f;
2536         if(tc > 95.f) tc = 95.f;
2537         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2538         y = ((double)r - minRating)/(maxRating - minRating)
2539             * (h-vMargin-squareSize/8-1) + vMargin;
2540         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2541         if(strstr(seekAdList[i], " u ")) color = 1;
2542         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2543            !strstr(seekAdList[i], "bullet") &&
2544            !strstr(seekAdList[i], "blitz") &&
2545            !strstr(seekAdList[i], "standard") ) color = 2;
2546         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2547         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2548 }
2549
2550 void
2551 PlotSingleSeekAd (int i)
2552 {
2553         PlotSeekAd(i);
2554 }
2555
2556 void
2557 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2558 {
2559         char buf[MSG_SIZ], *ext = "";
2560         VariantClass v = StringToVariant(type);
2561         if(strstr(type, "wild")) {
2562             ext = type + 4; // append wild number
2563             if(v == VariantFischeRandom) type = "chess960"; else
2564             if(v == VariantLoadable) type = "setup"; else
2565             type = VariantName(v);
2566         }
2567         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2568         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2569             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2570             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2571             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2572             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2573             seekNrList[nrOfSeekAds] = nr;
2574             zList[nrOfSeekAds] = 0;
2575             seekAdList[nrOfSeekAds++] = StrSave(buf);
2576             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2577         }
2578 }
2579
2580 void
2581 EraseSeekDot (int i)
2582 {
2583     int x = xList[i], y = yList[i], d=squareSize/4, k;
2584     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2585     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2586     // now replot every dot that overlapped
2587     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2588         int xx = xList[k], yy = yList[k];
2589         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2590             DrawSeekDot(xx, yy, colorList[k]);
2591     }
2592 }
2593
2594 void
2595 RemoveSeekAd (int nr)
2596 {
2597         int i;
2598         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2599             EraseSeekDot(i);
2600             if(seekAdList[i]) free(seekAdList[i]);
2601             seekAdList[i] = seekAdList[--nrOfSeekAds];
2602             seekNrList[i] = seekNrList[nrOfSeekAds];
2603             ratingList[i] = ratingList[nrOfSeekAds];
2604             colorList[i]  = colorList[nrOfSeekAds];
2605             tcList[i] = tcList[nrOfSeekAds];
2606             xList[i]  = xList[nrOfSeekAds];
2607             yList[i]  = yList[nrOfSeekAds];
2608             zList[i]  = zList[nrOfSeekAds];
2609             seekAdList[nrOfSeekAds] = NULL;
2610             break;
2611         }
2612 }
2613
2614 Boolean
2615 MatchSoughtLine (char *line)
2616 {
2617     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2618     int nr, base, inc, u=0; char dummy;
2619
2620     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2621        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2622        (u=1) &&
2623        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2624         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2625         // match: compact and save the line
2626         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2627         return TRUE;
2628     }
2629     return FALSE;
2630 }
2631
2632 int
2633 DrawSeekGraph ()
2634 {
2635     int i;
2636     if(!seekGraphUp) return FALSE;
2637     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2638     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2639
2640     DrawSeekBackground(0, 0, w, h);
2641     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2642     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2643     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2644         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2645         yy = h-1-yy;
2646         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2647         if(i%500 == 0) {
2648             char buf[MSG_SIZ];
2649             snprintf(buf, MSG_SIZ, "%d", i);
2650             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2651         }
2652     }
2653     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2654     for(i=1; i<100; i+=(i<10?1:5)) {
2655         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2656         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2657         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2658             char buf[MSG_SIZ];
2659             snprintf(buf, MSG_SIZ, "%d", i);
2660             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2661         }
2662     }
2663     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2664     return TRUE;
2665 }
2666
2667 int
2668 SeekGraphClick (ClickType click, int x, int y, int moving)
2669 {
2670     static int lastDown = 0, displayed = 0, lastSecond;
2671     if(y < 0) return FALSE;
2672     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2673         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2674         if(!seekGraphUp) return FALSE;
2675         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2676         DrawPosition(TRUE, NULL);
2677         return TRUE;
2678     }
2679     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2680         if(click == Release || moving) return FALSE;
2681         nrOfSeekAds = 0;
2682         soughtPending = TRUE;
2683         SendToICS(ics_prefix);
2684         SendToICS("sought\n"); // should this be "sought all"?
2685     } else { // issue challenge based on clicked ad
2686         int dist = 10000; int i, closest = 0, second = 0;
2687         for(i=0; i<nrOfSeekAds; i++) {
2688             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2689             if(d < dist) { dist = d; closest = i; }
2690             second += (d - zList[i] < 120); // count in-range ads
2691             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2692         }
2693         if(dist < 120) {
2694             char buf[MSG_SIZ];
2695             second = (second > 1);
2696             if(displayed != closest || second != lastSecond) {
2697                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2698                 lastSecond = second; displayed = closest;
2699             }
2700             if(click == Press) {
2701                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2702                 lastDown = closest;
2703                 return TRUE;
2704             } // on press 'hit', only show info
2705             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2706             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2707             SendToICS(ics_prefix);
2708             SendToICS(buf);
2709             return TRUE; // let incoming board of started game pop down the graph
2710         } else if(click == Release) { // release 'miss' is ignored
2711             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2712             if(moving == 2) { // right up-click
2713                 nrOfSeekAds = 0; // refresh graph
2714                 soughtPending = TRUE;
2715                 SendToICS(ics_prefix);
2716                 SendToICS("sought\n"); // should this be "sought all"?
2717             }
2718             return TRUE;
2719         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2720         // press miss or release hit 'pop down' seek graph
2721         seekGraphUp = FALSE;
2722         DrawPosition(TRUE, NULL);
2723     }
2724     return TRUE;
2725 }
2726
2727 void
2728 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2729 {
2730 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2731 #define STARTED_NONE 0
2732 #define STARTED_MOVES 1
2733 #define STARTED_BOARD 2
2734 #define STARTED_OBSERVE 3
2735 #define STARTED_HOLDINGS 4
2736 #define STARTED_CHATTER 5
2737 #define STARTED_COMMENT 6
2738 #define STARTED_MOVES_NOHIDE 7
2739
2740     static int started = STARTED_NONE;
2741     static char parse[20000];
2742     static int parse_pos = 0;
2743     static char buf[BUF_SIZE + 1];
2744     static int firstTime = TRUE, intfSet = FALSE;
2745     static ColorClass prevColor = ColorNormal;
2746     static int savingComment = FALSE;
2747     static int cmatch = 0; // continuation sequence match
2748     char *bp;
2749     char str[MSG_SIZ];
2750     int i, oldi;
2751     int buf_len;
2752     int next_out;
2753     int tkind;
2754     int backup;    /* [DM] For zippy color lines */
2755     char *p;
2756     char talker[MSG_SIZ]; // [HGM] chat
2757     int channel;
2758
2759     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2760
2761     if (appData.debugMode) {
2762       if (!error) {
2763         fprintf(debugFP, "<ICS: ");
2764         show_bytes(debugFP, data, count);
2765         fprintf(debugFP, "\n");
2766       }
2767     }
2768
2769     if (appData.debugMode) { int f = forwardMostMove;
2770         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2771                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2772                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2773     }
2774     if (count > 0) {
2775         /* If last read ended with a partial line that we couldn't parse,
2776            prepend it to the new read and try again. */
2777         if (leftover_len > 0) {
2778             for (i=0; i<leftover_len; i++)
2779               buf[i] = buf[leftover_start + i];
2780         }
2781
2782     /* copy new characters into the buffer */
2783     bp = buf + leftover_len;
2784     buf_len=leftover_len;
2785     for (i=0; i<count; i++)
2786     {
2787         // ignore these
2788         if (data[i] == '\r')
2789             continue;
2790
2791         // join lines split by ICS?
2792         if (!appData.noJoin)
2793         {
2794             /*
2795                 Joining just consists of finding matches against the
2796                 continuation sequence, and discarding that sequence
2797                 if found instead of copying it.  So, until a match
2798                 fails, there's nothing to do since it might be the
2799                 complete sequence, and thus, something we don't want
2800                 copied.
2801             */
2802             if (data[i] == cont_seq[cmatch])
2803             {
2804                 cmatch++;
2805                 if (cmatch == strlen(cont_seq))
2806                 {
2807                     cmatch = 0; // complete match.  just reset the counter
2808
2809                     /*
2810                         it's possible for the ICS to not include the space
2811                         at the end of the last word, making our [correct]
2812                         join operation fuse two separate words.  the server
2813                         does this when the space occurs at the width setting.
2814                     */
2815                     if (!buf_len || buf[buf_len-1] != ' ')
2816                     {
2817                         *bp++ = ' ';
2818                         buf_len++;
2819                     }
2820                 }
2821                 continue;
2822             }
2823             else if (cmatch)
2824             {
2825                 /*
2826                     match failed, so we have to copy what matched before
2827                     falling through and copying this character.  In reality,
2828                     this will only ever be just the newline character, but
2829                     it doesn't hurt to be precise.
2830                 */
2831                 strncpy(bp, cont_seq, cmatch);
2832                 bp += cmatch;
2833                 buf_len += cmatch;
2834                 cmatch = 0;
2835             }
2836         }
2837
2838         // copy this char
2839         *bp++ = data[i];
2840         buf_len++;
2841     }
2842
2843         buf[buf_len] = NULLCHAR;
2844 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2845         next_out = 0;
2846         leftover_start = 0;
2847
2848         i = 0;
2849         while (i < buf_len) {
2850             /* Deal with part of the TELNET option negotiation
2851                protocol.  We refuse to do anything beyond the
2852                defaults, except that we allow the WILL ECHO option,
2853                which ICS uses to turn off password echoing when we are
2854                directly connected to it.  We reject this option
2855                if localLineEditing mode is on (always on in xboard)
2856                and we are talking to port 23, which might be a real
2857                telnet server that will try to keep WILL ECHO on permanently.
2858              */
2859             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2860                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2861                 unsigned char option;
2862                 oldi = i;
2863                 switch ((unsigned char) buf[++i]) {
2864                   case TN_WILL:
2865                     if (appData.debugMode)
2866                       fprintf(debugFP, "\n<WILL ");
2867                     switch (option = (unsigned char) buf[++i]) {
2868                       case TN_ECHO:
2869                         if (appData.debugMode)
2870                           fprintf(debugFP, "ECHO ");
2871                         /* Reply only if this is a change, according
2872                            to the protocol rules. */
2873                         if (remoteEchoOption) break;
2874                         if (appData.localLineEditing &&
2875                             atoi(appData.icsPort) == TN_PORT) {
2876                             TelnetRequest(TN_DONT, TN_ECHO);
2877                         } else {
2878                             EchoOff();
2879                             TelnetRequest(TN_DO, TN_ECHO);
2880                             remoteEchoOption = TRUE;
2881                         }
2882                         break;
2883                       default:
2884                         if (appData.debugMode)
2885                           fprintf(debugFP, "%d ", option);
2886                         /* Whatever this is, we don't want it. */
2887                         TelnetRequest(TN_DONT, option);
2888                         break;
2889                     }
2890                     break;
2891                   case TN_WONT:
2892                     if (appData.debugMode)
2893                       fprintf(debugFP, "\n<WONT ");
2894                     switch (option = (unsigned char) buf[++i]) {
2895                       case TN_ECHO:
2896                         if (appData.debugMode)
2897                           fprintf(debugFP, "ECHO ");
2898                         /* Reply only if this is a change, according
2899                            to the protocol rules. */
2900                         if (!remoteEchoOption) break;
2901                         EchoOn();
2902                         TelnetRequest(TN_DONT, TN_ECHO);
2903                         remoteEchoOption = FALSE;
2904                         break;
2905                       default:
2906                         if (appData.debugMode)
2907                           fprintf(debugFP, "%d ", (unsigned char) option);
2908                         /* Whatever this is, it must already be turned
2909                            off, because we never agree to turn on
2910                            anything non-default, so according to the
2911                            protocol rules, we don't reply. */
2912                         break;
2913                     }
2914                     break;
2915                   case TN_DO:
2916                     if (appData.debugMode)
2917                       fprintf(debugFP, "\n<DO ");
2918                     switch (option = (unsigned char) buf[++i]) {
2919                       default:
2920                         /* Whatever this is, we refuse to do it. */
2921                         if (appData.debugMode)
2922                           fprintf(debugFP, "%d ", option);
2923                         TelnetRequest(TN_WONT, option);
2924                         break;
2925                     }
2926                     break;
2927                   case TN_DONT:
2928                     if (appData.debugMode)
2929                       fprintf(debugFP, "\n<DONT ");
2930                     switch (option = (unsigned char) buf[++i]) {
2931                       default:
2932                         if (appData.debugMode)
2933                           fprintf(debugFP, "%d ", option);
2934                         /* Whatever this is, we are already not doing
2935                            it, because we never agree to do anything
2936                            non-default, so according to the protocol
2937                            rules, we don't reply. */
2938                         break;
2939                     }
2940                     break;
2941                   case TN_IAC:
2942                     if (appData.debugMode)
2943                       fprintf(debugFP, "\n<IAC ");
2944                     /* Doubled IAC; pass it through */
2945                     i--;
2946                     break;
2947                   default:
2948                     if (appData.debugMode)
2949                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2950                     /* Drop all other telnet commands on the floor */
2951                     break;
2952                 }
2953                 if (oldi > next_out)
2954                   SendToPlayer(&buf[next_out], oldi - next_out);
2955                 if (++i > next_out)
2956                   next_out = i;
2957                 continue;
2958             }
2959
2960             /* OK, this at least will *usually* work */
2961             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2962                 loggedOn = TRUE;
2963             }
2964
2965             if (loggedOn && !intfSet) {
2966                 if (ics_type == ICS_ICC) {
2967                   snprintf(str, MSG_SIZ,
2968                           "/set-quietly interface %s\n/set-quietly style 12\n",
2969                           programVersion);
2970                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2971                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2972                 } else if (ics_type == ICS_CHESSNET) {
2973                   snprintf(str, MSG_SIZ, "/style 12\n");
2974                 } else {
2975                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2976                   strcat(str, programVersion);
2977                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2978                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2979                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2980 #ifdef WIN32
2981                   strcat(str, "$iset nohighlight 1\n");
2982 #endif
2983                   strcat(str, "$iset lock 1\n$style 12\n");
2984                 }
2985                 SendToICS(str);
2986                 NotifyFrontendLogin();
2987                 intfSet = TRUE;
2988             }
2989
2990             if (started == STARTED_COMMENT) {
2991                 /* Accumulate characters in comment */
2992                 parse[parse_pos++] = buf[i];
2993                 if (buf[i] == '\n') {
2994                     parse[parse_pos] = NULLCHAR;
2995                     if(chattingPartner>=0) {
2996                         char mess[MSG_SIZ];
2997                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2998                         OutputChatMessage(chattingPartner, mess);
2999                         chattingPartner = -1;
3000                         next_out = i+1; // [HGM] suppress printing in ICS window
3001                     } else
3002                     if(!suppressKibitz) // [HGM] kibitz
3003                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3004                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3005                         int nrDigit = 0, nrAlph = 0, j;
3006                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3007                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3008                         parse[parse_pos] = NULLCHAR;
3009                         // try to be smart: if it does not look like search info, it should go to
3010                         // ICS interaction window after all, not to engine-output window.
3011                         for(j=0; j<parse_pos; j++) { // count letters and digits
3012                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3013                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3014                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3015                         }
3016                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3017                             int depth=0; float score;
3018                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3019                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3020                                 pvInfoList[forwardMostMove-1].depth = depth;
3021                                 pvInfoList[forwardMostMove-1].score = 100*score;
3022                             }
3023                             OutputKibitz(suppressKibitz, parse);
3024                         } else {
3025                             char tmp[MSG_SIZ];
3026                             if(gameMode == IcsObserving) // restore original ICS messages
3027                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3028                             else
3029                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3030                             SendToPlayer(tmp, strlen(tmp));
3031                         }
3032                         next_out = i+1; // [HGM] suppress printing in ICS window
3033                     }
3034                     started = STARTED_NONE;
3035                 } else {
3036                     /* Don't match patterns against characters in comment */
3037                     i++;
3038                     continue;
3039                 }
3040             }
3041             if (started == STARTED_CHATTER) {
3042                 if (buf[i] != '\n') {
3043                     /* Don't match patterns against characters in chatter */
3044                     i++;
3045                     continue;
3046                 }
3047                 started = STARTED_NONE;
3048                 if(suppressKibitz) next_out = i+1;
3049             }
3050
3051             /* Kludge to deal with rcmd protocol */
3052             if (firstTime && looking_at(buf, &i, "\001*")) {
3053                 DisplayFatalError(&buf[1], 0, 1);
3054                 continue;
3055             } else {
3056                 firstTime = FALSE;
3057             }
3058
3059             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3060                 ics_type = ICS_ICC;
3061                 ics_prefix = "/";
3062                 if (appData.debugMode)
3063                   fprintf(debugFP, "ics_type %d\n", ics_type);
3064                 continue;
3065             }
3066             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3067                 ics_type = ICS_FICS;
3068                 ics_prefix = "$";
3069                 if (appData.debugMode)
3070                   fprintf(debugFP, "ics_type %d\n", ics_type);
3071                 continue;
3072             }
3073             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3074                 ics_type = ICS_CHESSNET;
3075                 ics_prefix = "/";
3076                 if (appData.debugMode)
3077                   fprintf(debugFP, "ics_type %d\n", ics_type);
3078                 continue;
3079             }
3080
3081             if (!loggedOn &&
3082                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3083                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3084                  looking_at(buf, &i, "will be \"*\""))) {
3085               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3086               continue;
3087             }
3088
3089             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3090               char buf[MSG_SIZ];
3091               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3092               DisplayIcsInteractionTitle(buf);
3093               have_set_title = TRUE;
3094             }
3095
3096             /* skip finger notes */
3097             if (started == STARTED_NONE &&
3098                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3099                  (buf[i] == '1' && buf[i+1] == '0')) &&
3100                 buf[i+2] == ':' && buf[i+3] == ' ') {
3101               started = STARTED_CHATTER;
3102               i += 3;
3103               continue;
3104             }
3105
3106             oldi = i;
3107             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3108             if(appData.seekGraph) {
3109                 if(soughtPending && MatchSoughtLine(buf+i)) {
3110                     i = strstr(buf+i, "rated") - buf;
3111                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3112                     next_out = leftover_start = i;
3113                     started = STARTED_CHATTER;
3114                     suppressKibitz = TRUE;
3115                     continue;
3116                 }
3117                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3118                         && looking_at(buf, &i, "* ads displayed")) {
3119                     soughtPending = FALSE;
3120                     seekGraphUp = TRUE;
3121                     DrawSeekGraph();
3122                     continue;
3123                 }
3124                 if(appData.autoRefresh) {
3125                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3126                         int s = (ics_type == ICS_ICC); // ICC format differs
3127                         if(seekGraphUp)
3128                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3129                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3130                         looking_at(buf, &i, "*% "); // eat prompt
3131                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3132                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3133                         next_out = i; // suppress
3134                         continue;
3135                     }
3136                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3137                         char *p = star_match[0];
3138                         while(*p) {
3139                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3140                             while(*p && *p++ != ' '); // next
3141                         }
3142                         looking_at(buf, &i, "*% "); // eat prompt
3143                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3144                         next_out = i;
3145                         continue;
3146                     }
3147                 }
3148             }
3149
3150             /* skip formula vars */
3151             if (started == STARTED_NONE &&
3152                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3153               started = STARTED_CHATTER;
3154               i += 3;
3155               continue;
3156             }
3157
3158             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3159             if (appData.autoKibitz && started == STARTED_NONE &&
3160                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3161                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3162                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3163                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3164                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3165                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3166                         suppressKibitz = TRUE;
3167                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3168                         next_out = i;
3169                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3170                                 && (gameMode == IcsPlayingWhite)) ||
3171                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3172                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3173                             started = STARTED_CHATTER; // own kibitz we simply discard
3174                         else {
3175                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3176                             parse_pos = 0; parse[0] = NULLCHAR;
3177                             savingComment = TRUE;
3178                             suppressKibitz = gameMode != IcsObserving ? 2 :
3179                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3180                         }
3181                         continue;
3182                 } else
3183                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3184                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3185                          && atoi(star_match[0])) {
3186                     // suppress the acknowledgements of our own autoKibitz
3187                     char *p;
3188                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3189                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3190                     SendToPlayer(star_match[0], strlen(star_match[0]));
3191                     if(looking_at(buf, &i, "*% ")) // eat prompt
3192                         suppressKibitz = FALSE;
3193                     next_out = i;
3194                     continue;
3195                 }
3196             } // [HGM] kibitz: end of patch
3197
3198             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3199
3200             // [HGM] chat: intercept tells by users for which we have an open chat window
3201             channel = -1;
3202             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3203                                            looking_at(buf, &i, "* whispers:") ||
3204                                            looking_at(buf, &i, "* kibitzes:") ||
3205                                            looking_at(buf, &i, "* shouts:") ||
3206                                            looking_at(buf, &i, "* c-shouts:") ||
3207                                            looking_at(buf, &i, "--> * ") ||
3208                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3209                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3210                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3211                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3212                 int p;
3213                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3214                 chattingPartner = -1;
3215
3216                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3217                 for(p=0; p<MAX_CHAT; p++) {
3218                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3219                     talker[0] = '['; strcat(talker, "] ");
3220                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3221                     chattingPartner = p; break;
3222                     }
3223                 } else
3224                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3225                 for(p=0; p<MAX_CHAT; p++) {
3226                     if(!strcmp("kibitzes", chatPartner[p])) {
3227                         talker[0] = '['; strcat(talker, "] ");
3228                         chattingPartner = p; break;
3229                     }
3230                 } else
3231                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3232                 for(p=0; p<MAX_CHAT; p++) {
3233                     if(!strcmp("whispers", chatPartner[p])) {
3234                         talker[0] = '['; strcat(talker, "] ");
3235                         chattingPartner = p; break;
3236                     }
3237                 } else
3238                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3239                   if(buf[i-8] == '-' && buf[i-3] == 't')
3240                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3241                     if(!strcmp("c-shouts", chatPartner[p])) {
3242                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3243                         chattingPartner = p; break;
3244                     }
3245                   }
3246                   if(chattingPartner < 0)
3247                   for(p=0; p<MAX_CHAT; p++) {
3248                     if(!strcmp("shouts", chatPartner[p])) {
3249                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3250                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3251                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3252                         chattingPartner = p; break;
3253                     }
3254                   }
3255                 }
3256                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3257                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3258                     talker[0] = 0; Colorize(ColorTell, FALSE);
3259                     chattingPartner = p; break;
3260                 }
3261                 if(chattingPartner<0) i = oldi; else {
3262                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3263                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3264                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3265                     started = STARTED_COMMENT;
3266                     parse_pos = 0; parse[0] = NULLCHAR;
3267                     savingComment = 3 + chattingPartner; // counts as TRUE
3268                     suppressKibitz = TRUE;
3269                     continue;
3270                 }
3271             } // [HGM] chat: end of patch
3272
3273           backup = i;
3274             if (appData.zippyTalk || appData.zippyPlay) {
3275                 /* [DM] Backup address for color zippy lines */
3276 #if ZIPPY
3277                if (loggedOn == TRUE)
3278                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3279                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3280 #endif
3281             } // [DM] 'else { ' deleted
3282                 if (
3283                     /* Regular tells and says */
3284                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3285                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3286                     looking_at(buf, &i, "* says: ") ||
3287                     /* Don't color "message" or "messages" output */
3288                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3289                     looking_at(buf, &i, "*. * at *:*: ") ||
3290                     looking_at(buf, &i, "--* (*:*): ") ||
3291                     /* Message notifications (same color as tells) */
3292                     looking_at(buf, &i, "* has left a message ") ||
3293                     looking_at(buf, &i, "* just sent you a message:\n") ||
3294                     /* Whispers and kibitzes */
3295                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3296                     looking_at(buf, &i, "* kibitzes: ") ||
3297                     /* Channel tells */
3298                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3299
3300                   if (tkind == 1 && strchr(star_match[0], ':')) {
3301                       /* Avoid "tells you:" spoofs in channels */
3302                      tkind = 3;
3303                   }
3304                   if (star_match[0][0] == NULLCHAR ||
3305                       strchr(star_match[0], ' ') ||
3306                       (tkind == 3 && strchr(star_match[1], ' '))) {
3307                     /* Reject bogus matches */
3308                     i = oldi;
3309                   } else {
3310                     if (appData.colorize) {
3311                       if (oldi > next_out) {
3312                         SendToPlayer(&buf[next_out], oldi - next_out);
3313                         next_out = oldi;
3314                       }
3315                       switch (tkind) {
3316                       case 1:
3317                         Colorize(ColorTell, FALSE);
3318                         curColor = ColorTell;
3319                         break;
3320                       case 2:
3321                         Colorize(ColorKibitz, FALSE);
3322                         curColor = ColorKibitz;
3323                         break;
3324                       case 3:
3325                         p = strrchr(star_match[1], '(');
3326                         if (p == NULL) {
3327                           p = star_match[1];
3328                         } else {
3329                           p++;
3330                         }
3331                         if (atoi(p) == 1) {
3332                           Colorize(ColorChannel1, FALSE);
3333                           curColor = ColorChannel1;
3334                         } else {
3335                           Colorize(ColorChannel, FALSE);
3336                           curColor = ColorChannel;
3337                         }
3338                         break;
3339                       case 5:
3340                         curColor = ColorNormal;
3341                         break;
3342                       }
3343                     }
3344                     if (started == STARTED_NONE && appData.autoComment &&
3345                         (gameMode == IcsObserving ||
3346                          gameMode == IcsPlayingWhite ||
3347                          gameMode == IcsPlayingBlack)) {
3348                       parse_pos = i - oldi;
3349                       memcpy(parse, &buf[oldi], parse_pos);
3350                       parse[parse_pos] = NULLCHAR;
3351                       started = STARTED_COMMENT;
3352                       savingComment = TRUE;
3353                     } else {
3354                       started = STARTED_CHATTER;
3355                       savingComment = FALSE;
3356                     }
3357                     loggedOn = TRUE;
3358                     continue;
3359                   }
3360                 }
3361
3362                 if (looking_at(buf, &i, "* s-shouts: ") ||
3363                     looking_at(buf, &i, "* c-shouts: ")) {
3364                     if (appData.colorize) {
3365                         if (oldi > next_out) {
3366                             SendToPlayer(&buf[next_out], oldi - next_out);
3367                             next_out = oldi;
3368                         }
3369                         Colorize(ColorSShout, FALSE);
3370                         curColor = ColorSShout;
3371                     }
3372                     loggedOn = TRUE;
3373                     started = STARTED_CHATTER;
3374                     continue;
3375                 }
3376
3377                 if (looking_at(buf, &i, "--->")) {
3378                     loggedOn = TRUE;
3379                     continue;
3380                 }
3381
3382                 if (looking_at(buf, &i, "* shouts: ") ||
3383                     looking_at(buf, &i, "--> ")) {
3384                     if (appData.colorize) {
3385                         if (oldi > next_out) {
3386                             SendToPlayer(&buf[next_out], oldi - next_out);
3387                             next_out = oldi;
3388                         }
3389                         Colorize(ColorShout, FALSE);
3390                         curColor = ColorShout;
3391                     }
3392                     loggedOn = TRUE;
3393                     started = STARTED_CHATTER;
3394                     continue;
3395                 }
3396
3397                 if (looking_at( buf, &i, "Challenge:")) {
3398                     if (appData.colorize) {
3399                         if (oldi > next_out) {
3400                             SendToPlayer(&buf[next_out], oldi - next_out);
3401                             next_out = oldi;
3402                         }
3403                         Colorize(ColorChallenge, FALSE);
3404                         curColor = ColorChallenge;
3405                     }
3406                     loggedOn = TRUE;
3407                     continue;
3408                 }
3409
3410                 if (looking_at(buf, &i, "* offers you") ||
3411                     looking_at(buf, &i, "* offers to be") ||
3412                     looking_at(buf, &i, "* would like to") ||
3413                     looking_at(buf, &i, "* requests to") ||
3414                     looking_at(buf, &i, "Your opponent offers") ||
3415                     looking_at(buf, &i, "Your opponent requests")) {
3416
3417                     if (appData.colorize) {
3418                         if (oldi > next_out) {
3419                             SendToPlayer(&buf[next_out], oldi - next_out);
3420                             next_out = oldi;
3421                         }
3422                         Colorize(ColorRequest, FALSE);
3423                         curColor = ColorRequest;
3424                     }
3425                     continue;
3426                 }
3427
3428                 if (looking_at(buf, &i, "* (*) seeking")) {
3429                     if (appData.colorize) {
3430                         if (oldi > next_out) {
3431                             SendToPlayer(&buf[next_out], oldi - next_out);
3432                             next_out = oldi;
3433                         }
3434                         Colorize(ColorSeek, FALSE);
3435                         curColor = ColorSeek;
3436                     }
3437                     continue;
3438             }
3439
3440           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3441
3442             if (looking_at(buf, &i, "\\   ")) {
3443                 if (prevColor != ColorNormal) {
3444                     if (oldi > next_out) {
3445                         SendToPlayer(&buf[next_out], oldi - next_out);
3446                         next_out = oldi;
3447                     }
3448                     Colorize(prevColor, TRUE);
3449                     curColor = prevColor;
3450                 }
3451                 if (savingComment) {
3452                     parse_pos = i - oldi;
3453                     memcpy(parse, &buf[oldi], parse_pos);
3454                     parse[parse_pos] = NULLCHAR;
3455                     started = STARTED_COMMENT;
3456                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3457                         chattingPartner = savingComment - 3; // kludge to remember the box
3458                 } else {
3459                     started = STARTED_CHATTER;
3460                 }
3461                 continue;
3462             }
3463
3464             if (looking_at(buf, &i, "Black Strength :") ||
3465                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3466                 looking_at(buf, &i, "<10>") ||
3467                 looking_at(buf, &i, "#@#")) {
3468                 /* Wrong board style */
3469                 loggedOn = TRUE;
3470                 SendToICS(ics_prefix);
3471                 SendToICS("set style 12\n");
3472                 SendToICS(ics_prefix);
3473                 SendToICS("refresh\n");
3474                 continue;
3475             }
3476
3477             if (looking_at(buf, &i, "login:")) {
3478               if (!have_sent_ICS_logon) {
3479                 if(ICSInitScript())
3480                   have_sent_ICS_logon = 1;
3481                 else // no init script was found
3482                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3483               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3484                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3485               }
3486                 continue;
3487             }
3488
3489             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3490                 (looking_at(buf, &i, "\n<12> ") ||
3491                  looking_at(buf, &i, "<12> "))) {
3492                 loggedOn = TRUE;
3493                 if (oldi > next_out) {
3494                     SendToPlayer(&buf[next_out], oldi - next_out);
3495                 }
3496                 next_out = i;
3497                 started = STARTED_BOARD;
3498                 parse_pos = 0;
3499                 continue;
3500             }
3501
3502             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3503                 looking_at(buf, &i, "<b1> ")) {
3504                 if (oldi > next_out) {
3505                     SendToPlayer(&buf[next_out], oldi - next_out);
3506                 }
3507                 next_out = i;
3508                 started = STARTED_HOLDINGS;
3509                 parse_pos = 0;
3510                 continue;
3511             }
3512
3513             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3514                 loggedOn = TRUE;
3515                 /* Header for a move list -- first line */
3516
3517                 switch (ics_getting_history) {
3518                   case H_FALSE:
3519                     switch (gameMode) {
3520                       case IcsIdle:
3521                       case BeginningOfGame:
3522                         /* User typed "moves" or "oldmoves" while we
3523                            were idle.  Pretend we asked for these
3524                            moves and soak them up so user can step
3525                            through them and/or save them.
3526                            */
3527                         Reset(FALSE, TRUE);
3528                         gameMode = IcsObserving;
3529                         ModeHighlight();
3530                         ics_gamenum = -1;
3531                         ics_getting_history = H_GOT_UNREQ_HEADER;
3532                         break;
3533                       case EditGame: /*?*/
3534                       case EditPosition: /*?*/
3535                         /* Should above feature work in these modes too? */
3536                         /* For now it doesn't */
3537                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3538                         break;
3539                       default:
3540                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3541                         break;
3542                     }
3543                     break;
3544                   case H_REQUESTED:
3545                     /* Is this the right one? */
3546                     if (gameInfo.white && gameInfo.black &&
3547                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3548                         strcmp(gameInfo.black, star_match[2]) == 0) {
3549                         /* All is well */
3550                         ics_getting_history = H_GOT_REQ_HEADER;
3551                     }
3552                     break;
3553                   case H_GOT_REQ_HEADER:
3554                   case H_GOT_UNREQ_HEADER:
3555                   case H_GOT_UNWANTED_HEADER:
3556                   case H_GETTING_MOVES:
3557                     /* Should not happen */
3558                     DisplayError(_("Error gathering move list: two headers"), 0);
3559                     ics_getting_history = H_FALSE;
3560                     break;
3561                 }
3562
3563                 /* Save player ratings into gameInfo if needed */
3564                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3565                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3566                     (gameInfo.whiteRating == -1 ||
3567                      gameInfo.blackRating == -1)) {
3568
3569                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3570                     gameInfo.blackRating = string_to_rating(star_match[3]);
3571                     if (appData.debugMode)
3572                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3573                               gameInfo.whiteRating, gameInfo.blackRating);
3574                 }
3575                 continue;
3576             }
3577
3578             if (looking_at(buf, &i,
3579               "* * match, initial time: * minute*, increment: * second")) {
3580                 /* Header for a move list -- second line */
3581                 /* Initial board will follow if this is a wild game */
3582                 if (gameInfo.event != NULL) free(gameInfo.event);
3583                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3584                 gameInfo.event = StrSave(str);
3585                 /* [HGM] we switched variant. Translate boards if needed. */
3586                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3587                 continue;
3588             }
3589
3590             if (looking_at(buf, &i, "Move  ")) {
3591                 /* Beginning of a move list */
3592                 switch (ics_getting_history) {
3593                   case H_FALSE:
3594                     /* Normally should not happen */
3595                     /* Maybe user hit reset while we were parsing */
3596                     break;
3597                   case H_REQUESTED:
3598                     /* Happens if we are ignoring a move list that is not
3599                      * the one we just requested.  Common if the user
3600                      * tries to observe two games without turning off
3601                      * getMoveList */
3602                     break;
3603                   case H_GETTING_MOVES:
3604                     /* Should not happen */
3605                     DisplayError(_("Error gathering move list: nested"), 0);
3606                     ics_getting_history = H_FALSE;
3607                     break;
3608                   case H_GOT_REQ_HEADER:
3609                     ics_getting_history = H_GETTING_MOVES;
3610                     started = STARTED_MOVES;
3611                     parse_pos = 0;
3612                     if (oldi > next_out) {
3613                         SendToPlayer(&buf[next_out], oldi - next_out);
3614                     }
3615                     break;
3616                   case H_GOT_UNREQ_HEADER:
3617                     ics_getting_history = H_GETTING_MOVES;
3618                     started = STARTED_MOVES_NOHIDE;
3619                     parse_pos = 0;
3620                     break;
3621                   case H_GOT_UNWANTED_HEADER:
3622                     ics_getting_history = H_FALSE;
3623                     break;
3624                 }
3625                 continue;
3626             }
3627
3628             if (looking_at(buf, &i, "% ") ||
3629                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3630                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3631                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3632                     soughtPending = FALSE;
3633                     seekGraphUp = TRUE;
3634                     DrawSeekGraph();
3635                 }
3636                 if(suppressKibitz) next_out = i;
3637                 savingComment = FALSE;
3638                 suppressKibitz = 0;
3639                 switch (started) {
3640                   case STARTED_MOVES:
3641                   case STARTED_MOVES_NOHIDE:
3642                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3643                     parse[parse_pos + i - oldi] = NULLCHAR;
3644                     ParseGameHistory(parse);
3645 #if ZIPPY
3646                     if (appData.zippyPlay && first.initDone) {
3647                         FeedMovesToProgram(&first, forwardMostMove);
3648                         if (gameMode == IcsPlayingWhite) {
3649                             if (WhiteOnMove(forwardMostMove)) {
3650                                 if (first.sendTime) {
3651                                   if (first.useColors) {
3652                                     SendToProgram("black\n", &first);
3653                                   }
3654                                   SendTimeRemaining(&first, TRUE);
3655                                 }
3656                                 if (first.useColors) {
3657                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3658                                 }
3659                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3660                                 first.maybeThinking = TRUE;
3661                             } else {
3662                                 if (first.usePlayother) {
3663                                   if (first.sendTime) {
3664                                     SendTimeRemaining(&first, TRUE);
3665                                   }
3666                                   SendToProgram("playother\n", &first);
3667                                   firstMove = FALSE;
3668                                 } else {
3669                                   firstMove = TRUE;
3670                                 }
3671                             }
3672                         } else if (gameMode == IcsPlayingBlack) {
3673                             if (!WhiteOnMove(forwardMostMove)) {
3674                                 if (first.sendTime) {
3675                                   if (first.useColors) {
3676                                     SendToProgram("white\n", &first);
3677                                   }
3678                                   SendTimeRemaining(&first, FALSE);
3679                                 }
3680                                 if (first.useColors) {
3681                                   SendToProgram("black\n", &first);
3682                                 }
3683                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3684                                 first.maybeThinking = TRUE;
3685                             } else {
3686                                 if (first.usePlayother) {
3687                                   if (first.sendTime) {
3688                                     SendTimeRemaining(&first, FALSE);
3689                                   }
3690                                   SendToProgram("playother\n", &first);
3691                                   firstMove = FALSE;
3692                                 } else {
3693                                   firstMove = TRUE;
3694                                 }
3695                             }
3696                         }
3697                     }
3698 #endif
3699                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3700                         /* Moves came from oldmoves or moves command
3701                            while we weren't doing anything else.
3702                            */
3703                         currentMove = forwardMostMove;
3704                         ClearHighlights();/*!!could figure this out*/
3705                         flipView = appData.flipView;
3706                         DrawPosition(TRUE, boards[currentMove]);
3707                         DisplayBothClocks();
3708                         snprintf(str, MSG_SIZ, "%s %s %s",
3709                                 gameInfo.white, _("vs."),  gameInfo.black);
3710                         DisplayTitle(str);
3711                         gameMode = IcsIdle;
3712                     } else {
3713                         /* Moves were history of an active game */
3714                         if (gameInfo.resultDetails != NULL) {
3715                             free(gameInfo.resultDetails);
3716                             gameInfo.resultDetails = NULL;
3717                         }
3718                     }
3719                     HistorySet(parseList, backwardMostMove,
3720                                forwardMostMove, currentMove-1);
3721                     DisplayMove(currentMove - 1);
3722                     if (started == STARTED_MOVES) next_out = i;
3723                     started = STARTED_NONE;
3724                     ics_getting_history = H_FALSE;
3725                     break;
3726
3727                   case STARTED_OBSERVE:
3728                     started = STARTED_NONE;
3729                     SendToICS(ics_prefix);
3730                     SendToICS("refresh\n");
3731                     break;
3732
3733                   default:
3734                     break;
3735                 }
3736                 if(bookHit) { // [HGM] book: simulate book reply
3737                     static char bookMove[MSG_SIZ]; // a bit generous?
3738
3739                     programStats.nodes = programStats.depth = programStats.time =
3740                     programStats.score = programStats.got_only_move = 0;
3741                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3742
3743                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3744                     strcat(bookMove, bookHit);
3745                     HandleMachineMove(bookMove, &first);
3746                 }
3747                 continue;
3748             }
3749
3750             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3751                  started == STARTED_HOLDINGS ||
3752                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3753                 /* Accumulate characters in move list or board */
3754                 parse[parse_pos++] = buf[i];
3755             }
3756
3757             /* Start of game messages.  Mostly we detect start of game
3758                when the first board image arrives.  On some versions
3759                of the ICS, though, we need to do a "refresh" after starting
3760                to observe in order to get the current board right away. */
3761             if (looking_at(buf, &i, "Adding game * to observation list")) {
3762                 started = STARTED_OBSERVE;
3763                 continue;
3764             }
3765
3766             /* Handle auto-observe */
3767             if (appData.autoObserve &&
3768                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3769                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3770                 char *player;
3771                 /* Choose the player that was highlighted, if any. */
3772                 if (star_match[0][0] == '\033' ||
3773                     star_match[1][0] != '\033') {
3774                     player = star_match[0];
3775                 } else {
3776                     player = star_match[2];
3777                 }
3778                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3779                         ics_prefix, StripHighlightAndTitle(player));
3780                 SendToICS(str);
3781
3782                 /* Save ratings from notify string */
3783                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3784                 player1Rating = string_to_rating(star_match[1]);
3785                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3786                 player2Rating = string_to_rating(star_match[3]);
3787
3788                 if (appData.debugMode)
3789                   fprintf(debugFP,
3790                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3791                           player1Name, player1Rating,
3792                           player2Name, player2Rating);
3793
3794                 continue;
3795             }
3796
3797             /* Deal with automatic examine mode after a game,
3798                and with IcsObserving -> IcsExamining transition */
3799             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3800                 looking_at(buf, &i, "has made you an examiner of game *")) {
3801
3802                 int gamenum = atoi(star_match[0]);
3803                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3804                     gamenum == ics_gamenum) {
3805                     /* We were already playing or observing this game;
3806                        no need to refetch history */
3807                     gameMode = IcsExamining;
3808                     if (pausing) {
3809                         pauseExamForwardMostMove = forwardMostMove;
3810                     } else if (currentMove < forwardMostMove) {
3811                         ForwardInner(forwardMostMove);
3812                     }
3813                 } else {
3814                     /* I don't think this case really can happen */
3815                     SendToICS(ics_prefix);
3816                     SendToICS("refresh\n");
3817                 }
3818                 continue;
3819             }
3820
3821             /* Error messages */
3822 //          if (ics_user_moved) {
3823             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3824                 if (looking_at(buf, &i, "Illegal move") ||
3825                     looking_at(buf, &i, "Not a legal move") ||
3826                     looking_at(buf, &i, "Your king is in check") ||
3827                     looking_at(buf, &i, "It isn't your turn") ||
3828                     looking_at(buf, &i, "It is not your move")) {
3829                     /* Illegal move */
3830                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3831                         currentMove = forwardMostMove-1;
3832                         DisplayMove(currentMove - 1); /* before DMError */
3833                         DrawPosition(FALSE, boards[currentMove]);
3834                         SwitchClocks(forwardMostMove-1); // [HGM] race
3835                         DisplayBothClocks();
3836                     }
3837                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3838                     ics_user_moved = 0;
3839                     continue;
3840                 }
3841             }
3842
3843             if (looking_at(buf, &i, "still have time") ||
3844                 looking_at(buf, &i, "not out of time") ||
3845                 looking_at(buf, &i, "either player is out of time") ||
3846                 looking_at(buf, &i, "has timeseal; checking")) {
3847                 /* We must have called his flag a little too soon */
3848                 whiteFlag = blackFlag = FALSE;
3849                 continue;
3850             }
3851
3852             if (looking_at(buf, &i, "added * seconds to") ||
3853                 looking_at(buf, &i, "seconds were added to")) {
3854                 /* Update the clocks */
3855                 SendToICS(ics_prefix);
3856                 SendToICS("refresh\n");
3857                 continue;
3858             }
3859
3860             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3861                 ics_clock_paused = TRUE;
3862                 StopClocks();
3863                 continue;
3864             }
3865
3866             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3867                 ics_clock_paused = FALSE;
3868                 StartClocks();
3869                 continue;
3870             }
3871
3872             /* Grab player ratings from the Creating: message.
3873                Note we have to check for the special case when
3874                the ICS inserts things like [white] or [black]. */
3875             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3876                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3877                 /* star_matches:
3878                    0    player 1 name (not necessarily white)
3879                    1    player 1 rating
3880                    2    empty, white, or black (IGNORED)
3881                    3    player 2 name (not necessarily black)
3882                    4    player 2 rating
3883
3884                    The names/ratings are sorted out when the game
3885                    actually starts (below).
3886                 */
3887                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3888                 player1Rating = string_to_rating(star_match[1]);
3889                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3890                 player2Rating = string_to_rating(star_match[4]);
3891
3892                 if (appData.debugMode)
3893                   fprintf(debugFP,
3894                           "Ratings from 'Creating:' %s %d, %s %d\n",
3895                           player1Name, player1Rating,
3896                           player2Name, player2Rating);
3897
3898                 continue;
3899             }
3900
3901             /* Improved generic start/end-of-game messages */
3902             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3903                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3904                 /* If tkind == 0: */
3905                 /* star_match[0] is the game number */
3906                 /*           [1] is the white player's name */
3907                 /*           [2] is the black player's name */
3908                 /* For end-of-game: */
3909                 /*           [3] is the reason for the game end */
3910                 /*           [4] is a PGN end game-token, preceded by " " */
3911                 /* For start-of-game: */
3912                 /*           [3] begins with "Creating" or "Continuing" */
3913                 /*           [4] is " *" or empty (don't care). */
3914                 int gamenum = atoi(star_match[0]);
3915                 char *whitename, *blackname, *why, *endtoken;
3916                 ChessMove endtype = EndOfFile;
3917
3918                 if (tkind == 0) {
3919                   whitename = star_match[1];
3920                   blackname = star_match[2];
3921                   why = star_match[3];
3922                   endtoken = star_match[4];
3923                 } else {
3924                   whitename = star_match[1];
3925                   blackname = star_match[3];
3926                   why = star_match[5];
3927                   endtoken = star_match[6];
3928                 }
3929
3930                 /* Game start messages */
3931                 if (strncmp(why, "Creating ", 9) == 0 ||
3932                     strncmp(why, "Continuing ", 11) == 0) {
3933                     gs_gamenum = gamenum;
3934                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3935                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3936                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3937 #if ZIPPY
3938                     if (appData.zippyPlay) {
3939                         ZippyGameStart(whitename, blackname);
3940                     }
3941 #endif /*ZIPPY*/
3942                     partnerBoardValid = FALSE; // [HGM] bughouse
3943                     continue;
3944                 }
3945
3946                 /* Game end messages */
3947                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3948                     ics_gamenum != gamenum) {
3949                     continue;
3950                 }
3951                 while (endtoken[0] == ' ') endtoken++;
3952                 switch (endtoken[0]) {
3953                   case '*':
3954                   default:
3955                     endtype = GameUnfinished;
3956                     break;
3957                   case '0':
3958                     endtype = BlackWins;
3959                     break;
3960                   case '1':
3961                     if (endtoken[1] == '/')
3962                       endtype = GameIsDrawn;
3963                     else
3964                       endtype = WhiteWins;
3965                     break;
3966                 }
3967                 GameEnds(endtype, why, GE_ICS);
3968 #if ZIPPY
3969                 if (appData.zippyPlay && first.initDone) {
3970                     ZippyGameEnd(endtype, why);
3971                     if (first.pr == NoProc) {
3972                       /* Start the next process early so that we'll
3973                          be ready for the next challenge */
3974                       StartChessProgram(&first);
3975                     }
3976                     /* Send "new" early, in case this command takes
3977                        a long time to finish, so that we'll be ready
3978                        for the next challenge. */
3979                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3980                     Reset(TRUE, TRUE);
3981                 }
3982 #endif /*ZIPPY*/
3983                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3984                 continue;
3985             }
3986
3987             if (looking_at(buf, &i, "Removing game * from observation") ||
3988                 looking_at(buf, &i, "no longer observing game *") ||
3989                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3990                 if (gameMode == IcsObserving &&
3991                     atoi(star_match[0]) == ics_gamenum)
3992                   {
3993                       /* icsEngineAnalyze */
3994                       if (appData.icsEngineAnalyze) {
3995                             ExitAnalyzeMode();
3996                             ModeHighlight();
3997                       }
3998                       StopClocks();
3999                       gameMode = IcsIdle;
4000                       ics_gamenum = -1;
4001                       ics_user_moved = FALSE;
4002                   }
4003                 continue;
4004             }
4005
4006             if (looking_at(buf, &i, "no longer examining game *")) {
4007                 if (gameMode == IcsExamining &&
4008                     atoi(star_match[0]) == ics_gamenum)
4009                   {
4010                       gameMode = IcsIdle;
4011                       ics_gamenum = -1;
4012                       ics_user_moved = FALSE;
4013                   }
4014                 continue;
4015             }
4016
4017             /* Advance leftover_start past any newlines we find,
4018                so only partial lines can get reparsed */
4019             if (looking_at(buf, &i, "\n")) {
4020                 prevColor = curColor;
4021                 if (curColor != ColorNormal) {
4022                     if (oldi > next_out) {
4023                         SendToPlayer(&buf[next_out], oldi - next_out);
4024                         next_out = oldi;
4025                     }
4026                     Colorize(ColorNormal, FALSE);
4027                     curColor = ColorNormal;
4028                 }
4029                 if (started == STARTED_BOARD) {
4030                     started = STARTED_NONE;
4031                     parse[parse_pos] = NULLCHAR;
4032                     ParseBoard12(parse);
4033                     ics_user_moved = 0;
4034
4035                     /* Send premove here */
4036                     if (appData.premove) {
4037                       char str[MSG_SIZ];
4038                       if (currentMove == 0 &&
4039                           gameMode == IcsPlayingWhite &&
4040                           appData.premoveWhite) {
4041                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4042                         if (appData.debugMode)
4043                           fprintf(debugFP, "Sending premove:\n");
4044                         SendToICS(str);
4045                       } else if (currentMove == 1 &&
4046                                  gameMode == IcsPlayingBlack &&
4047                                  appData.premoveBlack) {
4048                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4049                         if (appData.debugMode)
4050                           fprintf(debugFP, "Sending premove:\n");
4051                         SendToICS(str);
4052                       } else if (gotPremove) {
4053                         gotPremove = 0;
4054                         ClearPremoveHighlights();
4055                         if (appData.debugMode)
4056                           fprintf(debugFP, "Sending premove:\n");
4057                           UserMoveEvent(premoveFromX, premoveFromY,
4058                                         premoveToX, premoveToY,
4059                                         premovePromoChar);
4060                       }
4061                     }
4062
4063                     /* Usually suppress following prompt */
4064                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4065                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4066                         if (looking_at(buf, &i, "*% ")) {
4067                             savingComment = FALSE;
4068                             suppressKibitz = 0;
4069                         }
4070                     }
4071                     next_out = i;
4072                 } else if (started == STARTED_HOLDINGS) {
4073                     int gamenum;
4074                     char new_piece[MSG_SIZ];
4075                     started = STARTED_NONE;
4076                     parse[parse_pos] = NULLCHAR;
4077                     if (appData.debugMode)
4078                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4079                                                         parse, currentMove);
4080                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4081                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4082                         if (gameInfo.variant == VariantNormal) {
4083                           /* [HGM] We seem to switch variant during a game!
4084                            * Presumably no holdings were displayed, so we have
4085                            * to move the position two files to the right to
4086                            * create room for them!
4087                            */
4088                           VariantClass newVariant;
4089                           switch(gameInfo.boardWidth) { // base guess on board width
4090                                 case 9:  newVariant = VariantShogi; break;
4091                                 case 10: newVariant = VariantGreat; break;
4092                                 default: newVariant = VariantCrazyhouse; break;
4093                           }
4094                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4095                           /* Get a move list just to see the header, which
4096                              will tell us whether this is really bug or zh */
4097                           if (ics_getting_history == H_FALSE) {
4098                             ics_getting_history = H_REQUESTED;
4099                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4100                             SendToICS(str);
4101                           }
4102                         }
4103                         new_piece[0] = NULLCHAR;
4104                         sscanf(parse, "game %d white [%s black [%s <- %s",
4105                                &gamenum, white_holding, black_holding,
4106                                new_piece);
4107                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4108                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4109                         /* [HGM] copy holdings to board holdings area */
4110                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4111                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4112                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4113 #if ZIPPY
4114                         if (appData.zippyPlay && first.initDone) {
4115                             ZippyHoldings(white_holding, black_holding,
4116                                           new_piece);
4117                         }
4118 #endif /*ZIPPY*/
4119                         if (tinyLayout || smallLayout) {
4120                             char wh[16], bh[16];
4121                             PackHolding(wh, white_holding);
4122                             PackHolding(bh, black_holding);
4123                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4124                                     gameInfo.white, gameInfo.black);
4125                         } else {
4126                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4127                                     gameInfo.white, white_holding, _("vs."),
4128                                     gameInfo.black, black_holding);
4129                         }
4130                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4131                         DrawPosition(FALSE, boards[currentMove]);
4132                         DisplayTitle(str);
4133                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4134                         sscanf(parse, "game %d white [%s black [%s <- %s",
4135                                &gamenum, white_holding, black_holding,
4136                                new_piece);
4137                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4138                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4139                         /* [HGM] copy holdings to partner-board holdings area */
4140                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4141                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4142                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4143                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4144                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4145                       }
4146                     }
4147                     /* Suppress following prompt */
4148                     if (looking_at(buf, &i, "*% ")) {
4149                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4150                         savingComment = FALSE;
4151                         suppressKibitz = 0;
4152                     }
4153                     next_out = i;
4154                 }
4155                 continue;
4156             }
4157
4158             i++;                /* skip unparsed character and loop back */
4159         }
4160
4161         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4162 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4163 //          SendToPlayer(&buf[next_out], i - next_out);
4164             started != STARTED_HOLDINGS && leftover_start > next_out) {
4165             SendToPlayer(&buf[next_out], leftover_start - next_out);
4166             next_out = i;
4167         }
4168
4169         leftover_len = buf_len - leftover_start;
4170         /* if buffer ends with something we couldn't parse,
4171            reparse it after appending the next read */
4172
4173     } else if (count == 0) {
4174         RemoveInputSource(isr);
4175         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4176     } else {
4177         DisplayFatalError(_("Error reading from ICS"), error, 1);
4178     }
4179 }
4180
4181
4182 /* Board style 12 looks like this:
4183
4184    <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
4185
4186  * The "<12> " is stripped before it gets to this routine.  The two
4187  * trailing 0's (flip state and clock ticking) are later addition, and
4188  * some chess servers may not have them, or may have only the first.
4189  * Additional trailing fields may be added in the future.
4190  */
4191
4192 #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"
4193
4194 #define RELATION_OBSERVING_PLAYED    0
4195 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4196 #define RELATION_PLAYING_MYMOVE      1
4197 #define RELATION_PLAYING_NOTMYMOVE  -1
4198 #define RELATION_EXAMINING           2
4199 #define RELATION_ISOLATED_BOARD     -3
4200 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4201
4202 void
4203 ParseBoard12 (char *string)
4204 {
4205 #if ZIPPY
4206     int i, takeback;
4207     char *bookHit = NULL; // [HGM] book
4208 #endif
4209     GameMode newGameMode;
4210     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4211     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4212     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4213     char to_play, board_chars[200];
4214     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4215     char black[32], white[32];
4216     Board board;
4217     int prevMove = currentMove;
4218     int ticking = 2;
4219     ChessMove moveType;
4220     int fromX, fromY, toX, toY;
4221     char promoChar;
4222     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4223     Boolean weird = FALSE, reqFlag = FALSE;
4224
4225     fromX = fromY = toX = toY = -1;
4226
4227     newGame = FALSE;
4228
4229     if (appData.debugMode)
4230       fprintf(debugFP, "Parsing board: %s\n", string);
4231
4232     move_str[0] = NULLCHAR;
4233     elapsed_time[0] = NULLCHAR;
4234     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4235         int  i = 0, j;
4236         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4237             if(string[i] == ' ') { ranks++; files = 0; }
4238             else files++;
4239             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4240             i++;
4241         }
4242         for(j = 0; j <i; j++) board_chars[j] = string[j];
4243         board_chars[i] = '\0';
4244         string += i + 1;
4245     }
4246     n = sscanf(string, PATTERN, &to_play, &double_push,
4247                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4248                &gamenum, white, black, &relation, &basetime, &increment,
4249                &white_stren, &black_stren, &white_time, &black_time,
4250                &moveNum, str, elapsed_time, move_str, &ics_flip,
4251                &ticking);
4252
4253     if (n < 21) {
4254         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4255         DisplayError(str, 0);
4256         return;
4257     }
4258
4259     /* Convert the move number to internal form */
4260     moveNum = (moveNum - 1) * 2;
4261     if (to_play == 'B') moveNum++;
4262     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4263       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4264                         0, 1);
4265       return;
4266     }
4267
4268     switch (relation) {
4269       case RELATION_OBSERVING_PLAYED:
4270       case RELATION_OBSERVING_STATIC:
4271         if (gamenum == -1) {
4272             /* Old ICC buglet */
4273             relation = RELATION_OBSERVING_STATIC;
4274         }
4275         newGameMode = IcsObserving;
4276         break;
4277       case RELATION_PLAYING_MYMOVE:
4278       case RELATION_PLAYING_NOTMYMOVE:
4279         newGameMode =
4280           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4281             IcsPlayingWhite : IcsPlayingBlack;
4282         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4283         break;
4284       case RELATION_EXAMINING:
4285         newGameMode = IcsExamining;
4286         break;
4287       case RELATION_ISOLATED_BOARD:
4288       default:
4289         /* Just display this board.  If user was doing something else,
4290            we will forget about it until the next board comes. */
4291         newGameMode = IcsIdle;
4292         break;
4293       case RELATION_STARTING_POSITION:
4294         newGameMode = gameMode;
4295         break;
4296     }
4297
4298     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4299         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4300          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4301       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4302       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4303       static int lastBgGame = -1;
4304       char *toSqr;
4305       for (k = 0; k < ranks; k++) {
4306         for (j = 0; j < files; j++)
4307           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4308         if(gameInfo.holdingsWidth > 1) {
4309              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4310              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4311         }
4312       }
4313       CopyBoard(partnerBoard, board);
4314       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4315         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4316         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4317       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4318       if(toSqr = strchr(str, '-')) {
4319         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4320         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4321       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4322       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4323       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4324       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4325       if(twoBoards) {
4326           DisplayWhiteClock(white_time*fac, to_play == 'W');
4327           DisplayBlackClock(black_time*fac, to_play != 'W');
4328           activePartner = to_play;
4329           if(gamenum != lastBgGame) {
4330               char buf[MSG_SIZ];
4331               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4332               DisplayTitle(buf);
4333           }
4334           lastBgGame = gamenum;
4335           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4336                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4337       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4338                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4339       if(!twoBoards) DisplayMessage(partnerStatus, "");
4340         partnerBoardValid = TRUE;
4341       return;
4342     }
4343
4344     if(appData.dualBoard && appData.bgObserve) {
4345         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4346             SendToICS(ics_prefix), SendToICS("pobserve\n");
4347         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4348             char buf[MSG_SIZ];
4349             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4350             SendToICS(buf);
4351         }
4352     }
4353
4354     /* Modify behavior for initial board display on move listing
4355        of wild games.
4356        */
4357     switch (ics_getting_history) {
4358       case H_FALSE:
4359       case H_REQUESTED:
4360         break;
4361       case H_GOT_REQ_HEADER:
4362       case H_GOT_UNREQ_HEADER:
4363         /* This is the initial position of the current game */
4364         gamenum = ics_gamenum;
4365         moveNum = 0;            /* old ICS bug workaround */
4366         if (to_play == 'B') {
4367           startedFromSetupPosition = TRUE;
4368           blackPlaysFirst = TRUE;
4369           moveNum = 1;
4370           if (forwardMostMove == 0) forwardMostMove = 1;
4371           if (backwardMostMove == 0) backwardMostMove = 1;
4372           if (currentMove == 0) currentMove = 1;
4373         }
4374         newGameMode = gameMode;
4375         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4376         break;
4377       case H_GOT_UNWANTED_HEADER:
4378         /* This is an initial board that we don't want */
4379         return;
4380       case H_GETTING_MOVES:
4381         /* Should not happen */
4382         DisplayError(_("Error gathering move list: extra board"), 0);
4383         ics_getting_history = H_FALSE;
4384         return;
4385     }
4386
4387    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4388                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4389                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4390      /* [HGM] We seem to have switched variant unexpectedly
4391       * Try to guess new variant from board size
4392       */
4393           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4394           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4395           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4396           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4397           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4398           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4399           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4400           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4401           /* Get a move list just to see the header, which
4402              will tell us whether this is really bug or zh */
4403           if (ics_getting_history == H_FALSE) {
4404             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4405             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4406             SendToICS(str);
4407           }
4408     }
4409
4410     /* Take action if this is the first board of a new game, or of a
4411        different game than is currently being displayed.  */
4412     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4413         relation == RELATION_ISOLATED_BOARD) {
4414
4415         /* Forget the old game and get the history (if any) of the new one */
4416         if (gameMode != BeginningOfGame) {
4417           Reset(TRUE, TRUE);
4418         }
4419         newGame = TRUE;
4420         if (appData.autoRaiseBoard) BoardToTop();
4421         prevMove = -3;
4422         if (gamenum == -1) {
4423             newGameMode = IcsIdle;
4424         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4425                    appData.getMoveList && !reqFlag) {
4426             /* Need to get game history */
4427             ics_getting_history = H_REQUESTED;
4428             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4429             SendToICS(str);
4430         }
4431
4432         /* Initially flip the board to have black on the bottom if playing
4433            black or if the ICS flip flag is set, but let the user change
4434            it with the Flip View button. */
4435         flipView = appData.autoFlipView ?
4436           (newGameMode == IcsPlayingBlack) || ics_flip :
4437           appData.flipView;
4438
4439         /* Done with values from previous mode; copy in new ones */
4440         gameMode = newGameMode;
4441         ModeHighlight();
4442         ics_gamenum = gamenum;
4443         if (gamenum == gs_gamenum) {
4444             int klen = strlen(gs_kind);
4445             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4446             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4447             gameInfo.event = StrSave(str);
4448         } else {
4449             gameInfo.event = StrSave("ICS game");
4450         }
4451         gameInfo.site = StrSave(appData.icsHost);
4452         gameInfo.date = PGNDate();
4453         gameInfo.round = StrSave("-");
4454         gameInfo.white = StrSave(white);
4455         gameInfo.black = StrSave(black);
4456         timeControl = basetime * 60 * 1000;
4457         timeControl_2 = 0;
4458         timeIncrement = increment * 1000;
4459         movesPerSession = 0;
4460         gameInfo.timeControl = TimeControlTagValue();
4461         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4462   if (appData.debugMode) {
4463     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4464     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4465     setbuf(debugFP, NULL);
4466   }
4467
4468         gameInfo.outOfBook = NULL;
4469
4470         /* Do we have the ratings? */
4471         if (strcmp(player1Name, white) == 0 &&
4472             strcmp(player2Name, black) == 0) {
4473             if (appData.debugMode)
4474               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4475                       player1Rating, player2Rating);
4476             gameInfo.whiteRating = player1Rating;
4477             gameInfo.blackRating = player2Rating;
4478         } else if (strcmp(player2Name, white) == 0 &&
4479                    strcmp(player1Name, black) == 0) {
4480             if (appData.debugMode)
4481               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4482                       player2Rating, player1Rating);
4483             gameInfo.whiteRating = player2Rating;
4484             gameInfo.blackRating = player1Rating;
4485         }
4486         player1Name[0] = player2Name[0] = NULLCHAR;
4487
4488         /* Silence shouts if requested */
4489         if (appData.quietPlay &&
4490             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4491             SendToICS(ics_prefix);
4492             SendToICS("set shout 0\n");
4493         }
4494     }
4495
4496     /* Deal with midgame name changes */
4497     if (!newGame) {
4498         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4499             if (gameInfo.white) free(gameInfo.white);
4500             gameInfo.white = StrSave(white);
4501         }
4502         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4503             if (gameInfo.black) free(gameInfo.black);
4504             gameInfo.black = StrSave(black);
4505         }
4506     }
4507
4508     /* Throw away game result if anything actually changes in examine mode */
4509     if (gameMode == IcsExamining && !newGame) {
4510         gameInfo.result = GameUnfinished;
4511         if (gameInfo.resultDetails != NULL) {
4512             free(gameInfo.resultDetails);
4513             gameInfo.resultDetails = NULL;
4514         }
4515     }
4516
4517     /* In pausing && IcsExamining mode, we ignore boards coming
4518        in if they are in a different variation than we are. */
4519     if (pauseExamInvalid) return;
4520     if (pausing && gameMode == IcsExamining) {
4521         if (moveNum <= pauseExamForwardMostMove) {
4522             pauseExamInvalid = TRUE;
4523             forwardMostMove = pauseExamForwardMostMove;
4524             return;
4525         }
4526     }
4527
4528   if (appData.debugMode) {
4529     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4530   }
4531     /* Parse the board */
4532     for (k = 0; k < ranks; k++) {
4533       for (j = 0; j < files; j++)
4534         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4535       if(gameInfo.holdingsWidth > 1) {
4536            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4537            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4538       }
4539     }
4540     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4541       board[5][BOARD_RGHT+1] = WhiteAngel;
4542       board[6][BOARD_RGHT+1] = WhiteMarshall;
4543       board[1][0] = BlackMarshall;
4544       board[2][0] = BlackAngel;
4545       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4546     }
4547     CopyBoard(boards[moveNum], board);
4548     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4549     if (moveNum == 0) {
4550         startedFromSetupPosition =
4551           !CompareBoards(board, initialPosition);
4552         if(startedFromSetupPosition)
4553             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4554     }
4555
4556     /* [HGM] Set castling rights. Take the outermost Rooks,
4557        to make it also work for FRC opening positions. Note that board12
4558        is really defective for later FRC positions, as it has no way to
4559        indicate which Rook can castle if they are on the same side of King.
4560        For the initial position we grant rights to the outermost Rooks,
4561        and remember thos rights, and we then copy them on positions
4562        later in an FRC game. This means WB might not recognize castlings with
4563        Rooks that have moved back to their original position as illegal,
4564        but in ICS mode that is not its job anyway.
4565     */
4566     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4567     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4568
4569         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4570             if(board[0][i] == WhiteRook) j = i;
4571         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4572         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4573             if(board[0][i] == WhiteRook) j = i;
4574         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4575         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4576             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4577         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4578         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4579             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4580         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4581
4582         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4583         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4584         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4585             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4586         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4587             if(board[BOARD_HEIGHT-1][k] == bKing)
4588                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4589         if(gameInfo.variant == VariantTwoKings) {
4590             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4591             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4592             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4593         }
4594     } else { int r;
4595         r = boards[moveNum][CASTLING][0] = initialRights[0];
4596         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4597         r = boards[moveNum][CASTLING][1] = initialRights[1];
4598         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4599         r = boards[moveNum][CASTLING][3] = initialRights[3];
4600         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4601         r = boards[moveNum][CASTLING][4] = initialRights[4];
4602         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4603         /* wildcastle kludge: always assume King has rights */
4604         r = boards[moveNum][CASTLING][2] = initialRights[2];
4605         r = boards[moveNum][CASTLING][5] = initialRights[5];
4606     }
4607     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4608     boards[moveNum][EP_STATUS] = EP_NONE;
4609     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4610     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4611     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4612
4613
4614     if (ics_getting_history == H_GOT_REQ_HEADER ||
4615         ics_getting_history == H_GOT_UNREQ_HEADER) {
4616         /* This was an initial position from a move list, not
4617            the current position */
4618         return;
4619     }
4620
4621     /* Update currentMove and known move number limits */
4622     newMove = newGame || moveNum > forwardMostMove;
4623
4624     if (newGame) {
4625         forwardMostMove = backwardMostMove = currentMove = moveNum;
4626         if (gameMode == IcsExamining && moveNum == 0) {
4627           /* Workaround for ICS limitation: we are not told the wild
4628              type when starting to examine a game.  But if we ask for
4629              the move list, the move list header will tell us */
4630             ics_getting_history = H_REQUESTED;
4631             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4632             SendToICS(str);
4633         }
4634     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4635                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4636 #if ZIPPY
4637         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4638         /* [HGM] applied this also to an engine that is silently watching        */
4639         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4640             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4641             gameInfo.variant == currentlyInitializedVariant) {
4642           takeback = forwardMostMove - moveNum;
4643           for (i = 0; i < takeback; i++) {
4644             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4645             SendToProgram("undo\n", &first);
4646           }
4647         }
4648 #endif
4649
4650         forwardMostMove = moveNum;
4651         if (!pausing || currentMove > forwardMostMove)
4652           currentMove = forwardMostMove;
4653     } else {
4654         /* New part of history that is not contiguous with old part */
4655         if (pausing && gameMode == IcsExamining) {
4656             pauseExamInvalid = TRUE;
4657             forwardMostMove = pauseExamForwardMostMove;
4658             return;
4659         }
4660         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4661 #if ZIPPY
4662             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4663                 // [HGM] when we will receive the move list we now request, it will be
4664                 // fed to the engine from the first move on. So if the engine is not
4665                 // in the initial position now, bring it there.
4666                 InitChessProgram(&first, 0);
4667             }
4668 #endif
4669             ics_getting_history = H_REQUESTED;
4670             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4671             SendToICS(str);
4672         }
4673         forwardMostMove = backwardMostMove = currentMove = moveNum;
4674     }
4675
4676     /* Update the clocks */
4677     if (strchr(elapsed_time, '.')) {
4678       /* Time is in ms */
4679       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4680       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4681     } else {
4682       /* Time is in seconds */
4683       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4684       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4685     }
4686
4687
4688 #if ZIPPY
4689     if (appData.zippyPlay && newGame &&
4690         gameMode != IcsObserving && gameMode != IcsIdle &&
4691         gameMode != IcsExamining)
4692       ZippyFirstBoard(moveNum, basetime, increment);
4693 #endif
4694
4695     /* Put the move on the move list, first converting
4696        to canonical algebraic form. */
4697     if (moveNum > 0) {
4698   if (appData.debugMode) {
4699     int f = forwardMostMove;
4700     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4701             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4702             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4703     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4704     fprintf(debugFP, "moveNum = %d\n", moveNum);
4705     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4706     setbuf(debugFP, NULL);
4707   }
4708         if (moveNum <= backwardMostMove) {
4709             /* We don't know what the board looked like before
4710                this move.  Punt. */
4711           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4712             strcat(parseList[moveNum - 1], " ");
4713             strcat(parseList[moveNum - 1], elapsed_time);
4714             moveList[moveNum - 1][0] = NULLCHAR;
4715         } else if (strcmp(move_str, "none") == 0) {
4716             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4717             /* Again, we don't know what the board looked like;
4718                this is really the start of the game. */
4719             parseList[moveNum - 1][0] = NULLCHAR;
4720             moveList[moveNum - 1][0] = NULLCHAR;
4721             backwardMostMove = moveNum;
4722             startedFromSetupPosition = TRUE;
4723             fromX = fromY = toX = toY = -1;
4724         } else {
4725           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4726           //                 So we parse the long-algebraic move string in stead of the SAN move
4727           int valid; char buf[MSG_SIZ], *prom;
4728
4729           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4730                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4731           // str looks something like "Q/a1-a2"; kill the slash
4732           if(str[1] == '/')
4733             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4734           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4735           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4736                 strcat(buf, prom); // long move lacks promo specification!
4737           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4738                 if(appData.debugMode)
4739                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4740                 safeStrCpy(move_str, buf, MSG_SIZ);
4741           }
4742           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4743                                 &fromX, &fromY, &toX, &toY, &promoChar)
4744                || ParseOneMove(buf, moveNum - 1, &moveType,
4745                                 &fromX, &fromY, &toX, &toY, &promoChar);
4746           // end of long SAN patch
4747           if (valid) {
4748             (void) CoordsToAlgebraic(boards[moveNum - 1],
4749                                      PosFlags(moveNum - 1),
4750                                      fromY, fromX, toY, toX, promoChar,
4751                                      parseList[moveNum-1]);
4752             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4753               case MT_NONE:
4754               case MT_STALEMATE:
4755               default:
4756                 break;
4757               case MT_CHECK:
4758                 if(gameInfo.variant != VariantShogi)
4759                     strcat(parseList[moveNum - 1], "+");
4760                 break;
4761               case MT_CHECKMATE:
4762               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4763                 strcat(parseList[moveNum - 1], "#");
4764                 break;
4765             }
4766             strcat(parseList[moveNum - 1], " ");
4767             strcat(parseList[moveNum - 1], elapsed_time);
4768             /* currentMoveString is set as a side-effect of ParseOneMove */
4769             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4770             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4771             strcat(moveList[moveNum - 1], "\n");
4772
4773             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4774                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4775               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4776                 ChessSquare old, new = boards[moveNum][k][j];
4777                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4778                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4779                   if(old == new) continue;
4780                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4781                   else if(new == WhiteWazir || new == BlackWazir) {
4782                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4783                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4784                       else boards[moveNum][k][j] = old; // preserve type of Gold
4785                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4786                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4787               }
4788           } else {
4789             /* Move from ICS was illegal!?  Punt. */
4790             if (appData.debugMode) {
4791               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4792               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4793             }
4794             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4795             strcat(parseList[moveNum - 1], " ");
4796             strcat(parseList[moveNum - 1], elapsed_time);
4797             moveList[moveNum - 1][0] = NULLCHAR;
4798             fromX = fromY = toX = toY = -1;
4799           }
4800         }
4801   if (appData.debugMode) {
4802     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4803     setbuf(debugFP, NULL);
4804   }
4805
4806 #if ZIPPY
4807         /* Send move to chess program (BEFORE animating it). */
4808         if (appData.zippyPlay && !newGame && newMove &&
4809            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4810
4811             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4812                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4813                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4814                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4815                             move_str);
4816                     DisplayError(str, 0);
4817                 } else {
4818                     if (first.sendTime) {
4819                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4820                     }
4821                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4822                     if (firstMove && !bookHit) {
4823                         firstMove = FALSE;
4824                         if (first.useColors) {
4825                           SendToProgram(gameMode == IcsPlayingWhite ?
4826                                         "white\ngo\n" :
4827                                         "black\ngo\n", &first);
4828                         } else {
4829                           SendToProgram("go\n", &first);
4830                         }
4831                         first.maybeThinking = TRUE;
4832                     }
4833                 }
4834             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4835               if (moveList[moveNum - 1][0] == NULLCHAR) {
4836                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4837                 DisplayError(str, 0);
4838               } else {
4839                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4840                 SendMoveToProgram(moveNum - 1, &first);
4841               }
4842             }
4843         }
4844 #endif
4845     }
4846
4847     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4848         /* If move comes from a remote source, animate it.  If it
4849            isn't remote, it will have already been animated. */
4850         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4851             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4852         }
4853         if (!pausing && appData.highlightLastMove) {
4854             SetHighlights(fromX, fromY, toX, toY);
4855         }
4856     }
4857
4858     /* Start the clocks */
4859     whiteFlag = blackFlag = FALSE;
4860     appData.clockMode = !(basetime == 0 && increment == 0);
4861     if (ticking == 0) {
4862       ics_clock_paused = TRUE;
4863       StopClocks();
4864     } else if (ticking == 1) {
4865       ics_clock_paused = FALSE;
4866     }
4867     if (gameMode == IcsIdle ||
4868         relation == RELATION_OBSERVING_STATIC ||
4869         relation == RELATION_EXAMINING ||
4870         ics_clock_paused)
4871       DisplayBothClocks();
4872     else
4873       StartClocks();
4874
4875     /* Display opponents and material strengths */
4876     if (gameInfo.variant != VariantBughouse &&
4877         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4878         if (tinyLayout || smallLayout) {
4879             if(gameInfo.variant == VariantNormal)
4880               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4881                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4882                     basetime, increment);
4883             else
4884               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4885                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4886                     basetime, increment, (int) gameInfo.variant);
4887         } else {
4888             if(gameInfo.variant == VariantNormal)
4889               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4890                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4891                     basetime, increment);
4892             else
4893               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4894                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4895                     basetime, increment, VariantName(gameInfo.variant));
4896         }
4897         DisplayTitle(str);
4898   if (appData.debugMode) {
4899     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4900   }
4901     }
4902
4903
4904     /* Display the board */
4905     if (!pausing && !appData.noGUI) {
4906
4907       if (appData.premove)
4908           if (!gotPremove ||
4909              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4910              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4911               ClearPremoveHighlights();
4912
4913       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4914         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4915       DrawPosition(j, boards[currentMove]);
4916
4917       DisplayMove(moveNum - 1);
4918       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4919             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4920               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4921         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4922       }
4923     }
4924
4925     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4926 #if ZIPPY
4927     if(bookHit) { // [HGM] book: simulate book reply
4928         static char bookMove[MSG_SIZ]; // a bit generous?
4929
4930         programStats.nodes = programStats.depth = programStats.time =
4931         programStats.score = programStats.got_only_move = 0;
4932         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4933
4934         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4935         strcat(bookMove, bookHit);
4936         HandleMachineMove(bookMove, &first);
4937     }
4938 #endif
4939 }
4940
4941 void
4942 GetMoveListEvent ()
4943 {
4944     char buf[MSG_SIZ];
4945     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4946         ics_getting_history = H_REQUESTED;
4947         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4948         SendToICS(buf);
4949     }
4950 }
4951
4952 void
4953 SendToBoth (char *msg)
4954 {   // to make it easy to keep two engines in step in dual analysis
4955     SendToProgram(msg, &first);
4956     if(second.analyzing) SendToProgram(msg, &second);
4957 }
4958
4959 void
4960 AnalysisPeriodicEvent (int force)
4961 {
4962     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4963          && !force) || !appData.periodicUpdates)
4964       return;
4965
4966     /* Send . command to Crafty to collect stats */
4967     SendToBoth(".\n");
4968
4969     /* Don't send another until we get a response (this makes
4970        us stop sending to old Crafty's which don't understand
4971        the "." command (sending illegal cmds resets node count & time,
4972        which looks bad)) */
4973     programStats.ok_to_send = 0;
4974 }
4975
4976 void
4977 ics_update_width (int new_width)
4978 {
4979         ics_printf("set width %d\n", new_width);
4980 }
4981
4982 void
4983 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4984 {
4985     char buf[MSG_SIZ];
4986
4987     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4988         // null move in variant where engine does not understand it (for analysis purposes)
4989         SendBoard(cps, moveNum + 1); // send position after move in stead.
4990         return;
4991     }
4992     if (cps->useUsermove) {
4993       SendToProgram("usermove ", cps);
4994     }
4995     if (cps->useSAN) {
4996       char *space;
4997       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4998         int len = space - parseList[moveNum];
4999         memcpy(buf, parseList[moveNum], len);
5000         buf[len++] = '\n';
5001         buf[len] = NULLCHAR;
5002       } else {
5003         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5004       }
5005       SendToProgram(buf, cps);
5006     } else {
5007       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5008         AlphaRank(moveList[moveNum], 4);
5009         SendToProgram(moveList[moveNum], cps);
5010         AlphaRank(moveList[moveNum], 4); // and back
5011       } else
5012       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5013        * the engine. It would be nice to have a better way to identify castle
5014        * moves here. */
5015       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5016                                                                          && cps->useOOCastle) {
5017         int fromX = moveList[moveNum][0] - AAA;
5018         int fromY = moveList[moveNum][1] - ONE;
5019         int toX = moveList[moveNum][2] - AAA;
5020         int toY = moveList[moveNum][3] - ONE;
5021         if((boards[moveNum][fromY][fromX] == WhiteKing
5022             && boards[moveNum][toY][toX] == WhiteRook)
5023            || (boards[moveNum][fromY][fromX] == BlackKing
5024                && boards[moveNum][toY][toX] == BlackRook)) {
5025           if(toX > fromX) SendToProgram("O-O\n", cps);
5026           else SendToProgram("O-O-O\n", cps);
5027         }
5028         else SendToProgram(moveList[moveNum], cps);
5029       } else
5030       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5031         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5032           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5033           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5034                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5035         } else
5036           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5037                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5038         SendToProgram(buf, cps);
5039       }
5040       else SendToProgram(moveList[moveNum], cps);
5041       /* End of additions by Tord */
5042     }
5043
5044     /* [HGM] setting up the opening has brought engine in force mode! */
5045     /*       Send 'go' if we are in a mode where machine should play. */
5046     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5047         (gameMode == TwoMachinesPlay   ||
5048 #if ZIPPY
5049          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5050 #endif
5051          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5052         SendToProgram("go\n", cps);
5053   if (appData.debugMode) {
5054     fprintf(debugFP, "(extra)\n");
5055   }
5056     }
5057     setboardSpoiledMachineBlack = 0;
5058 }
5059
5060 void
5061 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5062 {
5063     char user_move[MSG_SIZ];
5064     char suffix[4];
5065
5066     if(gameInfo.variant == VariantSChess && promoChar) {
5067         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5068         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5069     } else suffix[0] = NULLCHAR;
5070
5071     switch (moveType) {
5072       default:
5073         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5074                 (int)moveType, fromX, fromY, toX, toY);
5075         DisplayError(user_move + strlen("say "), 0);
5076         break;
5077       case WhiteKingSideCastle:
5078       case BlackKingSideCastle:
5079       case WhiteQueenSideCastleWild:
5080       case BlackQueenSideCastleWild:
5081       /* PUSH Fabien */
5082       case WhiteHSideCastleFR:
5083       case BlackHSideCastleFR:
5084       /* POP Fabien */
5085         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5086         break;
5087       case WhiteQueenSideCastle:
5088       case BlackQueenSideCastle:
5089       case WhiteKingSideCastleWild:
5090       case BlackKingSideCastleWild:
5091       /* PUSH Fabien */
5092       case WhiteASideCastleFR:
5093       case BlackASideCastleFR:
5094       /* POP Fabien */
5095         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5096         break;
5097       case WhiteNonPromotion:
5098       case BlackNonPromotion:
5099         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5100         break;
5101       case WhitePromotion:
5102       case BlackPromotion:
5103         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5104            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5105           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5106                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5107                 PieceToChar(WhiteFerz));
5108         else if(gameInfo.variant == VariantGreat)
5109           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5110                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5111                 PieceToChar(WhiteMan));
5112         else
5113           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5114                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5115                 promoChar);
5116         break;
5117       case WhiteDrop:
5118       case BlackDrop:
5119       drop:
5120         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5121                  ToUpper(PieceToChar((ChessSquare) fromX)),
5122                  AAA + toX, ONE + toY);
5123         break;
5124       case IllegalMove:  /* could be a variant we don't quite understand */
5125         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5126       case NormalMove:
5127       case WhiteCapturesEnPassant:
5128       case BlackCapturesEnPassant:
5129         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5130                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5131         break;
5132     }
5133     SendToICS(user_move);
5134     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5135         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5136 }
5137
5138 void
5139 UploadGameEvent ()
5140 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5141     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5142     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5143     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5144       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5145       return;
5146     }
5147     if(gameMode != IcsExamining) { // is this ever not the case?
5148         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5149
5150         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5151           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5152         } else { // on FICS we must first go to general examine mode
5153           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5154         }
5155         if(gameInfo.variant != VariantNormal) {
5156             // try figure out wild number, as xboard names are not always valid on ICS
5157             for(i=1; i<=36; i++) {
5158               snprintf(buf, MSG_SIZ, "wild/%d", i);
5159                 if(StringToVariant(buf) == gameInfo.variant) break;
5160             }
5161             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5162             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5163             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5164         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5165         SendToICS(ics_prefix);
5166         SendToICS(buf);
5167         if(startedFromSetupPosition || backwardMostMove != 0) {
5168           fen = PositionToFEN(backwardMostMove, NULL, 1);
5169           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5170             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5171             SendToICS(buf);
5172           } else { // FICS: everything has to set by separate bsetup commands
5173             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5174             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5175             SendToICS(buf);
5176             if(!WhiteOnMove(backwardMostMove)) {
5177                 SendToICS("bsetup tomove black\n");
5178             }
5179             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5180             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5181             SendToICS(buf);
5182             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5183             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5184             SendToICS(buf);
5185             i = boards[backwardMostMove][EP_STATUS];
5186             if(i >= 0) { // set e.p.
5187               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5188                 SendToICS(buf);
5189             }
5190             bsetup++;
5191           }
5192         }
5193       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5194             SendToICS("bsetup done\n"); // switch to normal examining.
5195     }
5196     for(i = backwardMostMove; i<last; i++) {
5197         char buf[20];
5198         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5199         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5200             int len = strlen(moveList[i]);
5201             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5202             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5203         }
5204         SendToICS(buf);
5205     }
5206     SendToICS(ics_prefix);
5207     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5208 }
5209
5210 void
5211 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5212 {
5213     if (rf == DROP_RANK) {
5214       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5215       sprintf(move, "%c@%c%c\n",
5216                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5217     } else {
5218         if (promoChar == 'x' || promoChar == NULLCHAR) {
5219           sprintf(move, "%c%c%c%c\n",
5220                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5221         } else {
5222             sprintf(move, "%c%c%c%c%c\n",
5223                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5224         }
5225     }
5226 }
5227
5228 void
5229 ProcessICSInitScript (FILE *f)
5230 {
5231     char buf[MSG_SIZ];
5232
5233     while (fgets(buf, MSG_SIZ, f)) {
5234         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5235     }
5236
5237     fclose(f);
5238 }
5239
5240
5241 static int lastX, lastY, selectFlag, dragging;
5242
5243 void
5244 Sweep (int step)
5245 {
5246     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5247     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5248     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5249     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5250     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5251     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5252     do {
5253         promoSweep -= step;
5254         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5255         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5256         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5257         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5258         if(!step) step = -1;
5259     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5260             appData.testLegality && (promoSweep == king ||
5261             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5262     if(toX >= 0) {
5263         int victim = boards[currentMove][toY][toX];
5264         boards[currentMove][toY][toX] = promoSweep;
5265         DrawPosition(FALSE, boards[currentMove]);
5266         boards[currentMove][toY][toX] = victim;
5267     } else
5268     ChangeDragPiece(promoSweep);
5269 }
5270
5271 int
5272 PromoScroll (int x, int y)
5273 {
5274   int step = 0;
5275
5276   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5277   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5278   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5279   if(!step) return FALSE;
5280   lastX = x; lastY = y;
5281   if((promoSweep < BlackPawn) == flipView) step = -step;
5282   if(step > 0) selectFlag = 1;
5283   if(!selectFlag) Sweep(step);
5284   return FALSE;
5285 }
5286
5287 void
5288 NextPiece (int step)
5289 {
5290     ChessSquare piece = boards[currentMove][toY][toX];
5291     do {
5292         pieceSweep -= step;
5293         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5294         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5295         if(!step) step = -1;
5296     } while(PieceToChar(pieceSweep) == '.');
5297     boards[currentMove][toY][toX] = pieceSweep;
5298     DrawPosition(FALSE, boards[currentMove]);
5299     boards[currentMove][toY][toX] = piece;
5300 }
5301 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5302 void
5303 AlphaRank (char *move, int n)
5304 {
5305 //    char *p = move, c; int x, y;
5306
5307     if (appData.debugMode) {
5308         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5309     }
5310
5311     if(move[1]=='*' &&
5312        move[2]>='0' && move[2]<='9' &&
5313        move[3]>='a' && move[3]<='x'    ) {
5314         move[1] = '@';
5315         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5316         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5317     } else
5318     if(move[0]>='0' && move[0]<='9' &&
5319        move[1]>='a' && move[1]<='x' &&
5320        move[2]>='0' && move[2]<='9' &&
5321        move[3]>='a' && move[3]<='x'    ) {
5322         /* input move, Shogi -> normal */
5323         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5324         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5325         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5326         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5327     } else
5328     if(move[1]=='@' &&
5329        move[3]>='0' && move[3]<='9' &&
5330        move[2]>='a' && move[2]<='x'    ) {
5331         move[1] = '*';
5332         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5333         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5334     } else
5335     if(
5336        move[0]>='a' && move[0]<='x' &&
5337        move[3]>='0' && move[3]<='9' &&
5338        move[2]>='a' && move[2]<='x'    ) {
5339          /* output move, normal -> Shogi */
5340         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5341         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5342         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5343         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5344         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5345     }
5346     if (appData.debugMode) {
5347         fprintf(debugFP, "   out = '%s'\n", move);
5348     }
5349 }
5350
5351 char yy_textstr[8000];
5352
5353 /* Parser for moves from gnuchess, ICS, or user typein box */
5354 Boolean
5355 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5356 {
5357     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5358
5359     switch (*moveType) {
5360       case WhitePromotion:
5361       case BlackPromotion:
5362       case WhiteNonPromotion:
5363       case BlackNonPromotion:
5364       case NormalMove:
5365       case WhiteCapturesEnPassant:
5366       case BlackCapturesEnPassant:
5367       case WhiteKingSideCastle:
5368       case WhiteQueenSideCastle:
5369       case BlackKingSideCastle:
5370       case BlackQueenSideCastle:
5371       case WhiteKingSideCastleWild:
5372       case WhiteQueenSideCastleWild:
5373       case BlackKingSideCastleWild:
5374       case BlackQueenSideCastleWild:
5375       /* Code added by Tord: */
5376       case WhiteHSideCastleFR:
5377       case WhiteASideCastleFR:
5378       case BlackHSideCastleFR:
5379       case BlackASideCastleFR:
5380       /* End of code added by Tord */
5381       case IllegalMove:         /* bug or odd chess variant */
5382         *fromX = currentMoveString[0] - AAA;
5383         *fromY = currentMoveString[1] - ONE;
5384         *toX = currentMoveString[2] - AAA;
5385         *toY = currentMoveString[3] - ONE;
5386         *promoChar = currentMoveString[4];
5387         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5388             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5389     if (appData.debugMode) {
5390         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5391     }
5392             *fromX = *fromY = *toX = *toY = 0;
5393             return FALSE;
5394         }
5395         if (appData.testLegality) {
5396           return (*moveType != IllegalMove);
5397         } else {
5398           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5399                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5400         }
5401
5402       case WhiteDrop:
5403       case BlackDrop:
5404         *fromX = *moveType == WhiteDrop ?
5405           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5406           (int) CharToPiece(ToLower(currentMoveString[0]));
5407         *fromY = DROP_RANK;
5408         *toX = currentMoveString[2] - AAA;
5409         *toY = currentMoveString[3] - ONE;
5410         *promoChar = NULLCHAR;
5411         return TRUE;
5412
5413       case AmbiguousMove:
5414       case ImpossibleMove:
5415       case EndOfFile:
5416       case ElapsedTime:
5417       case Comment:
5418       case PGNTag:
5419       case NAG:
5420       case WhiteWins:
5421       case BlackWins:
5422       case GameIsDrawn:
5423       default:
5424     if (appData.debugMode) {
5425         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5426     }
5427         /* bug? */
5428         *fromX = *fromY = *toX = *toY = 0;
5429         *promoChar = NULLCHAR;
5430         return FALSE;
5431     }
5432 }
5433
5434 Boolean pushed = FALSE;
5435 char *lastParseAttempt;
5436
5437 void
5438 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5439 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5440   int fromX, fromY, toX, toY; char promoChar;
5441   ChessMove moveType;
5442   Boolean valid;
5443   int nr = 0;
5444
5445   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5446   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5447     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5448     pushed = TRUE;
5449   }
5450   endPV = forwardMostMove;
5451   do {
5452     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5453     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5454     lastParseAttempt = pv;
5455     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5456     if(!valid && nr == 0 &&
5457        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5458         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5459         // Hande case where played move is different from leading PV move
5460         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5461         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5462         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5463         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5464           endPV += 2; // if position different, keep this
5465           moveList[endPV-1][0] = fromX + AAA;
5466           moveList[endPV-1][1] = fromY + ONE;
5467           moveList[endPV-1][2] = toX + AAA;
5468           moveList[endPV-1][3] = toY + ONE;
5469           parseList[endPV-1][0] = NULLCHAR;
5470           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5471         }
5472       }
5473     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5474     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5475     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5476     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5477         valid++; // allow comments in PV
5478         continue;
5479     }
5480     nr++;
5481     if(endPV+1 > framePtr) break; // no space, truncate
5482     if(!valid) break;
5483     endPV++;
5484     CopyBoard(boards[endPV], boards[endPV-1]);
5485     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5486     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5487     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5488     CoordsToAlgebraic(boards[endPV - 1],
5489                              PosFlags(endPV - 1),
5490                              fromY, fromX, toY, toX, promoChar,
5491                              parseList[endPV - 1]);
5492   } while(valid);
5493   if(atEnd == 2) return; // used hidden, for PV conversion
5494   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5495   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5496   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5497                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5498   DrawPosition(TRUE, boards[currentMove]);
5499 }
5500
5501 int
5502 MultiPV (ChessProgramState *cps)
5503 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5504         int i;
5505         for(i=0; i<cps->nrOptions; i++)
5506             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5507                 return i;
5508         return -1;
5509 }
5510
5511 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5512
5513 Boolean
5514 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5515 {
5516         int startPV, multi, lineStart, origIndex = index;
5517         char *p, buf2[MSG_SIZ];
5518         ChessProgramState *cps = (pane ? &second : &first);
5519
5520         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5521         lastX = x; lastY = y;
5522         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5523         lineStart = startPV = index;
5524         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5525         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5526         index = startPV;
5527         do{ while(buf[index] && buf[index] != '\n') index++;
5528         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5529         buf[index] = 0;
5530         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5531                 int n = cps->option[multi].value;
5532                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5533                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5534                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5535                 cps->option[multi].value = n;
5536                 *start = *end = 0;
5537                 return FALSE;
5538         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5539                 ExcludeClick(origIndex - lineStart);
5540                 return FALSE;
5541         }
5542         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5543         *start = startPV; *end = index-1;
5544         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5545         return TRUE;
5546 }
5547
5548 char *
5549 PvToSAN (char *pv)
5550 {
5551         static char buf[10*MSG_SIZ];
5552         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5553         *buf = NULLCHAR;
5554         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5555         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5556         for(i = forwardMostMove; i<endPV; i++){
5557             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5558             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5559             k += strlen(buf+k);
5560         }
5561         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5562         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5563         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5564         endPV = savedEnd;
5565         return buf;
5566 }
5567
5568 Boolean
5569 LoadPV (int x, int y)
5570 { // called on right mouse click to load PV
5571   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5572   lastX = x; lastY = y;
5573   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5574   extendGame = FALSE;
5575   return TRUE;
5576 }
5577
5578 void
5579 UnLoadPV ()
5580 {
5581   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5582   if(endPV < 0) return;
5583   if(appData.autoCopyPV) CopyFENToClipboard();
5584   endPV = -1;
5585   if(extendGame && currentMove > forwardMostMove) {
5586         Boolean saveAnimate = appData.animate;
5587         if(pushed) {
5588             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5589                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5590             } else storedGames--; // abandon shelved tail of original game
5591         }
5592         pushed = FALSE;
5593         forwardMostMove = currentMove;
5594         currentMove = oldFMM;
5595         appData.animate = FALSE;
5596         ToNrEvent(forwardMostMove);
5597         appData.animate = saveAnimate;
5598   }
5599   currentMove = forwardMostMove;
5600   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5601   ClearPremoveHighlights();
5602   DrawPosition(TRUE, boards[currentMove]);
5603 }
5604
5605 void
5606 MovePV (int x, int y, int h)
5607 { // step through PV based on mouse coordinates (called on mouse move)
5608   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5609
5610   // we must somehow check if right button is still down (might be released off board!)
5611   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5612   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5613   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5614   if(!step) return;
5615   lastX = x; lastY = y;
5616
5617   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5618   if(endPV < 0) return;
5619   if(y < margin) step = 1; else
5620   if(y > h - margin) step = -1;
5621   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5622   currentMove += step;
5623   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5624   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5625                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5626   DrawPosition(FALSE, boards[currentMove]);
5627 }
5628
5629
5630 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5631 // All positions will have equal probability, but the current method will not provide a unique
5632 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5633 #define DARK 1
5634 #define LITE 2
5635 #define ANY 3
5636
5637 int squaresLeft[4];
5638 int piecesLeft[(int)BlackPawn];
5639 int seed, nrOfShuffles;
5640
5641 void
5642 GetPositionNumber ()
5643 {       // sets global variable seed
5644         int i;
5645
5646         seed = appData.defaultFrcPosition;
5647         if(seed < 0) { // randomize based on time for negative FRC position numbers
5648                 for(i=0; i<50; i++) seed += random();
5649                 seed = random() ^ random() >> 8 ^ random() << 8;
5650                 if(seed<0) seed = -seed;
5651         }
5652 }
5653
5654 int
5655 put (Board board, int pieceType, int rank, int n, int shade)
5656 // put the piece on the (n-1)-th empty squares of the given shade
5657 {
5658         int i;
5659
5660         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5661                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5662                         board[rank][i] = (ChessSquare) pieceType;
5663                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5664                         squaresLeft[ANY]--;
5665                         piecesLeft[pieceType]--;
5666                         return i;
5667                 }
5668         }
5669         return -1;
5670 }
5671
5672
5673 void
5674 AddOnePiece (Board board, int pieceType, int rank, int shade)
5675 // calculate where the next piece goes, (any empty square), and put it there
5676 {
5677         int i;
5678
5679         i = seed % squaresLeft[shade];
5680         nrOfShuffles *= squaresLeft[shade];
5681         seed /= squaresLeft[shade];
5682         put(board, pieceType, rank, i, shade);
5683 }
5684
5685 void
5686 AddTwoPieces (Board board, int pieceType, int rank)
5687 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5688 {
5689         int i, n=squaresLeft[ANY], j=n-1, k;
5690
5691         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5692         i = seed % k;  // pick one
5693         nrOfShuffles *= k;
5694         seed /= k;
5695         while(i >= j) i -= j--;
5696         j = n - 1 - j; i += j;
5697         put(board, pieceType, rank, j, ANY);
5698         put(board, pieceType, rank, i, ANY);
5699 }
5700
5701 void
5702 SetUpShuffle (Board board, int number)
5703 {
5704         int i, p, first=1;
5705
5706         GetPositionNumber(); nrOfShuffles = 1;
5707
5708         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5709         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5710         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5711
5712         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5713
5714         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5715             p = (int) board[0][i];
5716             if(p < (int) BlackPawn) piecesLeft[p] ++;
5717             board[0][i] = EmptySquare;
5718         }
5719
5720         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5721             // shuffles restricted to allow normal castling put KRR first
5722             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5723                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5724             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5725                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5726             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5727                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5728             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5729                 put(board, WhiteRook, 0, 0, ANY);
5730             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5731         }
5732
5733         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5734             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5735             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5736                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5737                 while(piecesLeft[p] >= 2) {
5738                     AddOnePiece(board, p, 0, LITE);
5739                     AddOnePiece(board, p, 0, DARK);
5740                 }
5741                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5742             }
5743
5744         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5745             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5746             // but we leave King and Rooks for last, to possibly obey FRC restriction
5747             if(p == (int)WhiteRook) continue;
5748             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5749             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5750         }
5751
5752         // now everything is placed, except perhaps King (Unicorn) and Rooks
5753
5754         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5755             // Last King gets castling rights
5756             while(piecesLeft[(int)WhiteUnicorn]) {
5757                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5758                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5759             }
5760
5761             while(piecesLeft[(int)WhiteKing]) {
5762                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5763                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5764             }
5765
5766
5767         } else {
5768             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5769             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5770         }
5771
5772         // Only Rooks can be left; simply place them all
5773         while(piecesLeft[(int)WhiteRook]) {
5774                 i = put(board, WhiteRook, 0, 0, ANY);
5775                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5776                         if(first) {
5777                                 first=0;
5778                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5779                         }
5780                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5781                 }
5782         }
5783         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5784             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5785         }
5786
5787         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5788 }
5789
5790 int
5791 SetCharTable (char *table, const char * map)
5792 /* [HGM] moved here from winboard.c because of its general usefulness */
5793 /*       Basically a safe strcpy that uses the last character as King */
5794 {
5795     int result = FALSE; int NrPieces;
5796
5797     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5798                     && NrPieces >= 12 && !(NrPieces&1)) {
5799         int i; /* [HGM] Accept even length from 12 to 34 */
5800
5801         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5802         for( i=0; i<NrPieces/2-1; i++ ) {
5803             table[i] = map[i];
5804             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5805         }
5806         table[(int) WhiteKing]  = map[NrPieces/2-1];
5807         table[(int) BlackKing]  = map[NrPieces-1];
5808
5809         result = TRUE;
5810     }
5811
5812     return result;
5813 }
5814
5815 void
5816 Prelude (Board board)
5817 {       // [HGM] superchess: random selection of exo-pieces
5818         int i, j, k; ChessSquare p;
5819         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5820
5821         GetPositionNumber(); // use FRC position number
5822
5823         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5824             SetCharTable(pieceToChar, appData.pieceToCharTable);
5825             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5826                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5827         }
5828
5829         j = seed%4;                 seed /= 4;
5830         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5831         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5832         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5833         j = seed%3 + (seed%3 >= j); seed /= 3;
5834         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5835         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5836         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5837         j = seed%3;                 seed /= 3;
5838         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5839         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5840         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5841         j = seed%2 + (seed%2 >= j); seed /= 2;
5842         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5843         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5844         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5845         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5846         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5847         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5848         put(board, exoPieces[0],    0, 0, ANY);
5849         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5850 }
5851
5852 void
5853 InitPosition (int redraw)
5854 {
5855     ChessSquare (* pieces)[BOARD_FILES];
5856     int i, j, pawnRow, overrule,
5857     oldx = gameInfo.boardWidth,
5858     oldy = gameInfo.boardHeight,
5859     oldh = gameInfo.holdingsWidth;
5860     static int oldv;
5861
5862     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5863
5864     /* [AS] Initialize pv info list [HGM] and game status */
5865     {
5866         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5867             pvInfoList[i].depth = 0;
5868             boards[i][EP_STATUS] = EP_NONE;
5869             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5870         }
5871
5872         initialRulePlies = 0; /* 50-move counter start */
5873
5874         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5875         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5876     }
5877
5878
5879     /* [HGM] logic here is completely changed. In stead of full positions */
5880     /* the initialized data only consist of the two backranks. The switch */
5881     /* selects which one we will use, which is than copied to the Board   */
5882     /* initialPosition, which for the rest is initialized by Pawns and    */
5883     /* empty squares. This initial position is then copied to boards[0],  */
5884     /* possibly after shuffling, so that it remains available.            */
5885
5886     gameInfo.holdingsWidth = 0; /* default board sizes */
5887     gameInfo.boardWidth    = 8;
5888     gameInfo.boardHeight   = 8;
5889     gameInfo.holdingsSize  = 0;
5890     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5891     for(i=0; i<BOARD_FILES-2; i++)
5892       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5893     initialPosition[EP_STATUS] = EP_NONE;
5894     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5895     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5896          SetCharTable(pieceNickName, appData.pieceNickNames);
5897     else SetCharTable(pieceNickName, "............");
5898     pieces = FIDEArray;
5899
5900     switch (gameInfo.variant) {
5901     case VariantFischeRandom:
5902       shuffleOpenings = TRUE;
5903     default:
5904       break;
5905     case VariantShatranj:
5906       pieces = ShatranjArray;
5907       nrCastlingRights = 0;
5908       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5909       break;
5910     case VariantMakruk:
5911       pieces = makrukArray;
5912       nrCastlingRights = 0;
5913       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5914       break;
5915     case VariantASEAN:
5916       pieces = aseanArray;
5917       nrCastlingRights = 0;
5918       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5919       break;
5920     case VariantTwoKings:
5921       pieces = twoKingsArray;
5922       break;
5923     case VariantGrand:
5924       pieces = GrandArray;
5925       nrCastlingRights = 0;
5926       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5927       gameInfo.boardWidth = 10;
5928       gameInfo.boardHeight = 10;
5929       gameInfo.holdingsSize = 7;
5930       break;
5931     case VariantCapaRandom:
5932       shuffleOpenings = TRUE;
5933     case VariantCapablanca:
5934       pieces = CapablancaArray;
5935       gameInfo.boardWidth = 10;
5936       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5937       break;
5938     case VariantGothic:
5939       pieces = GothicArray;
5940       gameInfo.boardWidth = 10;
5941       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5942       break;
5943     case VariantSChess:
5944       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5945       gameInfo.holdingsSize = 7;
5946       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5947       break;
5948     case VariantJanus:
5949       pieces = JanusArray;
5950       gameInfo.boardWidth = 10;
5951       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5952       nrCastlingRights = 6;
5953         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5954         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5955         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5956         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5957         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5958         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5959       break;
5960     case VariantFalcon:
5961       pieces = FalconArray;
5962       gameInfo.boardWidth = 10;
5963       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5964       break;
5965     case VariantXiangqi:
5966       pieces = XiangqiArray;
5967       gameInfo.boardWidth  = 9;
5968       gameInfo.boardHeight = 10;
5969       nrCastlingRights = 0;
5970       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5971       break;
5972     case VariantShogi:
5973       pieces = ShogiArray;
5974       gameInfo.boardWidth  = 9;
5975       gameInfo.boardHeight = 9;
5976       gameInfo.holdingsSize = 7;
5977       nrCastlingRights = 0;
5978       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5979       break;
5980     case VariantCourier:
5981       pieces = CourierArray;
5982       gameInfo.boardWidth  = 12;
5983       nrCastlingRights = 0;
5984       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5985       break;
5986     case VariantKnightmate:
5987       pieces = KnightmateArray;
5988       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5989       break;
5990     case VariantSpartan:
5991       pieces = SpartanArray;
5992       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5993       break;
5994     case VariantFairy:
5995       pieces = fairyArray;
5996       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5997       break;
5998     case VariantGreat:
5999       pieces = GreatArray;
6000       gameInfo.boardWidth = 10;
6001       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6002       gameInfo.holdingsSize = 8;
6003       break;
6004     case VariantSuper:
6005       pieces = FIDEArray;
6006       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6007       gameInfo.holdingsSize = 8;
6008       startedFromSetupPosition = TRUE;
6009       break;
6010     case VariantCrazyhouse:
6011     case VariantBughouse:
6012       pieces = FIDEArray;
6013       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6014       gameInfo.holdingsSize = 5;
6015       break;
6016     case VariantWildCastle:
6017       pieces = FIDEArray;
6018       /* !!?shuffle with kings guaranteed to be on d or e file */
6019       shuffleOpenings = 1;
6020       break;
6021     case VariantNoCastle:
6022       pieces = FIDEArray;
6023       nrCastlingRights = 0;
6024       /* !!?unconstrained back-rank shuffle */
6025       shuffleOpenings = 1;
6026       break;
6027     }
6028
6029     overrule = 0;
6030     if(appData.NrFiles >= 0) {
6031         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6032         gameInfo.boardWidth = appData.NrFiles;
6033     }
6034     if(appData.NrRanks >= 0) {
6035         gameInfo.boardHeight = appData.NrRanks;
6036     }
6037     if(appData.holdingsSize >= 0) {
6038         i = appData.holdingsSize;
6039         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6040         gameInfo.holdingsSize = i;
6041     }
6042     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6043     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6044         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6045
6046     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6047     if(pawnRow < 1) pawnRow = 1;
6048     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6049
6050     /* User pieceToChar list overrules defaults */
6051     if(appData.pieceToCharTable != NULL)
6052         SetCharTable(pieceToChar, appData.pieceToCharTable);
6053
6054     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6055
6056         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6057             s = (ChessSquare) 0; /* account holding counts in guard band */
6058         for( i=0; i<BOARD_HEIGHT; i++ )
6059             initialPosition[i][j] = s;
6060
6061         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6062         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6063         initialPosition[pawnRow][j] = WhitePawn;
6064         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6065         if(gameInfo.variant == VariantXiangqi) {
6066             if(j&1) {
6067                 initialPosition[pawnRow][j] =
6068                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6069                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6070                    initialPosition[2][j] = WhiteCannon;
6071                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6072                 }
6073             }
6074         }
6075         if(gameInfo.variant == VariantGrand) {
6076             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6077                initialPosition[0][j] = WhiteRook;
6078                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6079             }
6080         }
6081         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6082     }
6083     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6084
6085             j=BOARD_LEFT+1;
6086             initialPosition[1][j] = WhiteBishop;
6087             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6088             j=BOARD_RGHT-2;
6089             initialPosition[1][j] = WhiteRook;
6090             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6091     }
6092
6093     if( nrCastlingRights == -1) {
6094         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6095         /*       This sets default castling rights from none to normal corners   */
6096         /* Variants with other castling rights must set them themselves above    */
6097         nrCastlingRights = 6;
6098
6099         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6100         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6101         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6102         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6103         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6104         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6105      }
6106
6107      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6108      if(gameInfo.variant == VariantGreat) { // promotion commoners
6109         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6110         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6111         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6112         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6113      }
6114      if( gameInfo.variant == VariantSChess ) {
6115       initialPosition[1][0] = BlackMarshall;
6116       initialPosition[2][0] = BlackAngel;
6117       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6118       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6119       initialPosition[1][1] = initialPosition[2][1] =
6120       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6121      }
6122   if (appData.debugMode) {
6123     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6124   }
6125     if(shuffleOpenings) {
6126         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6127         startedFromSetupPosition = TRUE;
6128     }
6129     if(startedFromPositionFile) {
6130       /* [HGM] loadPos: use PositionFile for every new game */
6131       CopyBoard(initialPosition, filePosition);
6132       for(i=0; i<nrCastlingRights; i++)
6133           initialRights[i] = filePosition[CASTLING][i];
6134       startedFromSetupPosition = TRUE;
6135     }
6136
6137     CopyBoard(boards[0], initialPosition);
6138
6139     if(oldx != gameInfo.boardWidth ||
6140        oldy != gameInfo.boardHeight ||
6141        oldv != gameInfo.variant ||
6142        oldh != gameInfo.holdingsWidth
6143                                          )
6144             InitDrawingSizes(-2 ,0);
6145
6146     oldv = gameInfo.variant;
6147     if (redraw)
6148       DrawPosition(TRUE, boards[currentMove]);
6149 }
6150
6151 void
6152 SendBoard (ChessProgramState *cps, int moveNum)
6153 {
6154     char message[MSG_SIZ];
6155
6156     if (cps->useSetboard) {
6157       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6158       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6159       SendToProgram(message, cps);
6160       free(fen);
6161
6162     } else {
6163       ChessSquare *bp;
6164       int i, j, left=0, right=BOARD_WIDTH;
6165       /* Kludge to set black to move, avoiding the troublesome and now
6166        * deprecated "black" command.
6167        */
6168       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6169         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6170
6171       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6172
6173       SendToProgram("edit\n", cps);
6174       SendToProgram("#\n", cps);
6175       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6176         bp = &boards[moveNum][i][left];
6177         for (j = left; j < right; j++, bp++) {
6178           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6179           if ((int) *bp < (int) BlackPawn) {
6180             if(j == BOARD_RGHT+1)
6181                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6182             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6183             if(message[0] == '+' || message[0] == '~') {
6184               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6185                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6186                         AAA + j, ONE + i);
6187             }
6188             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6189                 message[1] = BOARD_RGHT   - 1 - j + '1';
6190                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6191             }
6192             SendToProgram(message, cps);
6193           }
6194         }
6195       }
6196
6197       SendToProgram("c\n", cps);
6198       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6199         bp = &boards[moveNum][i][left];
6200         for (j = left; j < right; j++, bp++) {
6201           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6202           if (((int) *bp != (int) EmptySquare)
6203               && ((int) *bp >= (int) BlackPawn)) {
6204             if(j == BOARD_LEFT-2)
6205                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6206             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6207                     AAA + j, ONE + i);
6208             if(message[0] == '+' || message[0] == '~') {
6209               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6210                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6211                         AAA + j, ONE + i);
6212             }
6213             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6214                 message[1] = BOARD_RGHT   - 1 - j + '1';
6215                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6216             }
6217             SendToProgram(message, cps);
6218           }
6219         }
6220       }
6221
6222       SendToProgram(".\n", cps);
6223     }
6224     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6225 }
6226
6227 char exclusionHeader[MSG_SIZ];
6228 int exCnt, excludePtr;
6229 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6230 static Exclusion excluTab[200];
6231 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6232
6233 static void
6234 WriteMap (int s)
6235 {
6236     int j;
6237     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6238     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6239 }
6240
6241 static void
6242 ClearMap ()
6243 {
6244     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6245     excludePtr = 24; exCnt = 0;
6246     WriteMap(0);
6247 }
6248
6249 static void
6250 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6251 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6252     char buf[2*MOVE_LEN], *p;
6253     Exclusion *e = excluTab;
6254     int i;
6255     for(i=0; i<exCnt; i++)
6256         if(e[i].ff == fromX && e[i].fr == fromY &&
6257            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6258     if(i == exCnt) { // was not in exclude list; add it
6259         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6260         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6261             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6262             return; // abort
6263         }
6264         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6265         excludePtr++; e[i].mark = excludePtr++;
6266         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6267         exCnt++;
6268     }
6269     exclusionHeader[e[i].mark] = state;
6270 }
6271
6272 static int
6273 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6274 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6275     char buf[MSG_SIZ];
6276     int j, k;
6277     ChessMove moveType;
6278     if((signed char)promoChar == -1) { // kludge to indicate best move
6279         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6280             return 1; // if unparsable, abort
6281     }
6282     // update exclusion map (resolving toggle by consulting existing state)
6283     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6284     j = k%8; k >>= 3;
6285     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6286     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6287          excludeMap[k] |=   1<<j;
6288     else excludeMap[k] &= ~(1<<j);
6289     // update header
6290     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6291     // inform engine
6292     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6293     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6294     SendToBoth(buf);
6295     return (state == '+');
6296 }
6297
6298 static void
6299 ExcludeClick (int index)
6300 {
6301     int i, j;
6302     Exclusion *e = excluTab;
6303     if(index < 25) { // none, best or tail clicked
6304         if(index < 13) { // none: include all
6305             WriteMap(0); // clear map
6306             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6307             SendToBoth("include all\n"); // and inform engine
6308         } else if(index > 18) { // tail
6309             if(exclusionHeader[19] == '-') { // tail was excluded
6310                 SendToBoth("include all\n");
6311                 WriteMap(0); // clear map completely
6312                 // now re-exclude selected moves
6313                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6314                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6315             } else { // tail was included or in mixed state
6316                 SendToBoth("exclude all\n");
6317                 WriteMap(0xFF); // fill map completely
6318                 // now re-include selected moves
6319                 j = 0; // count them
6320                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6321                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6322                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6323             }
6324         } else { // best
6325             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6326         }
6327     } else {
6328         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6329             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6330             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6331             break;
6332         }
6333     }
6334 }
6335
6336 ChessSquare
6337 DefaultPromoChoice (int white)
6338 {
6339     ChessSquare result;
6340     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6341        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6342         result = WhiteFerz; // no choice
6343     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6344         result= WhiteKing; // in Suicide Q is the last thing we want
6345     else if(gameInfo.variant == VariantSpartan)
6346         result = white ? WhiteQueen : WhiteAngel;
6347     else result = WhiteQueen;
6348     if(!white) result = WHITE_TO_BLACK result;
6349     return result;
6350 }
6351
6352 static int autoQueen; // [HGM] oneclick
6353
6354 int
6355 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6356 {
6357     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6358     /* [HGM] add Shogi promotions */
6359     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6360     ChessSquare piece;
6361     ChessMove moveType;
6362     Boolean premove;
6363
6364     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6365     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6366
6367     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6368       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6369         return FALSE;
6370
6371     piece = boards[currentMove][fromY][fromX];
6372     if(gameInfo.variant == VariantShogi) {
6373         promotionZoneSize = BOARD_HEIGHT/3;
6374         highestPromotingPiece = (int)WhiteFerz;
6375     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6376         promotionZoneSize = 3;
6377     }
6378
6379     // Treat Lance as Pawn when it is not representing Amazon
6380     if(gameInfo.variant != VariantSuper) {
6381         if(piece == WhiteLance) piece = WhitePawn; else
6382         if(piece == BlackLance) piece = BlackPawn;
6383     }
6384
6385     // next weed out all moves that do not touch the promotion zone at all
6386     if((int)piece >= BlackPawn) {
6387         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6388              return FALSE;
6389         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6390     } else {
6391         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6392            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6393     }
6394
6395     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6396
6397     // weed out mandatory Shogi promotions
6398     if(gameInfo.variant == VariantShogi) {
6399         if(piece >= BlackPawn) {
6400             if(toY == 0 && piece == BlackPawn ||
6401                toY == 0 && piece == BlackQueen ||
6402                toY <= 1 && piece == BlackKnight) {
6403                 *promoChoice = '+';
6404                 return FALSE;
6405             }
6406         } else {
6407             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6408                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6409                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6410                 *promoChoice = '+';
6411                 return FALSE;
6412             }
6413         }
6414     }
6415
6416     // weed out obviously illegal Pawn moves
6417     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6418         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6419         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6420         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6421         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6422         // note we are not allowed to test for valid (non-)capture, due to premove
6423     }
6424
6425     // we either have a choice what to promote to, or (in Shogi) whether to promote
6426     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6427        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6428         *promoChoice = PieceToChar(BlackFerz);  // no choice
6429         return FALSE;
6430     }
6431     // no sense asking what we must promote to if it is going to explode...
6432     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6433         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6434         return FALSE;
6435     }
6436     // give caller the default choice even if we will not make it
6437     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6438     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6439     if(        sweepSelect && gameInfo.variant != VariantGreat
6440                            && gameInfo.variant != VariantGrand
6441                            && gameInfo.variant != VariantSuper) return FALSE;
6442     if(autoQueen) return FALSE; // predetermined
6443
6444     // suppress promotion popup on illegal moves that are not premoves
6445     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6446               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6447     if(appData.testLegality && !premove) {
6448         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6449                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6450         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6451             return FALSE;
6452     }
6453
6454     return TRUE;
6455 }
6456
6457 int
6458 InPalace (int row, int column)
6459 {   /* [HGM] for Xiangqi */
6460     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6461          column < (BOARD_WIDTH + 4)/2 &&
6462          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6463     return FALSE;
6464 }
6465
6466 int
6467 PieceForSquare (int x, int y)
6468 {
6469   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6470      return -1;
6471   else
6472      return boards[currentMove][y][x];
6473 }
6474
6475 int
6476 OKToStartUserMove (int x, int y)
6477 {
6478     ChessSquare from_piece;
6479     int white_piece;
6480
6481     if (matchMode) return FALSE;
6482     if (gameMode == EditPosition) return TRUE;
6483
6484     if (x >= 0 && y >= 0)
6485       from_piece = boards[currentMove][y][x];
6486     else
6487       from_piece = EmptySquare;
6488
6489     if (from_piece == EmptySquare) return FALSE;
6490
6491     white_piece = (int)from_piece >= (int)WhitePawn &&
6492       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6493
6494     switch (gameMode) {
6495       case AnalyzeFile:
6496       case TwoMachinesPlay:
6497       case EndOfGame:
6498         return FALSE;
6499
6500       case IcsObserving:
6501       case IcsIdle:
6502         return FALSE;
6503
6504       case MachinePlaysWhite:
6505       case IcsPlayingBlack:
6506         if (appData.zippyPlay) return FALSE;
6507         if (white_piece) {
6508             DisplayMoveError(_("You are playing Black"));
6509             return FALSE;
6510         }
6511         break;
6512
6513       case MachinePlaysBlack:
6514       case IcsPlayingWhite:
6515         if (appData.zippyPlay) return FALSE;
6516         if (!white_piece) {
6517             DisplayMoveError(_("You are playing White"));
6518             return FALSE;
6519         }
6520         break;
6521
6522       case PlayFromGameFile:
6523             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6524       case EditGame:
6525         if (!white_piece && WhiteOnMove(currentMove)) {
6526             DisplayMoveError(_("It is White's turn"));
6527             return FALSE;
6528         }
6529         if (white_piece && !WhiteOnMove(currentMove)) {
6530             DisplayMoveError(_("It is Black's turn"));
6531             return FALSE;
6532         }
6533         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6534             /* Editing correspondence game history */
6535             /* Could disallow this or prompt for confirmation */
6536             cmailOldMove = -1;
6537         }
6538         break;
6539
6540       case BeginningOfGame:
6541         if (appData.icsActive) return FALSE;
6542         if (!appData.noChessProgram) {
6543             if (!white_piece) {
6544                 DisplayMoveError(_("You are playing White"));
6545                 return FALSE;
6546             }
6547         }
6548         break;
6549
6550       case Training:
6551         if (!white_piece && WhiteOnMove(currentMove)) {
6552             DisplayMoveError(_("It is White's turn"));
6553             return FALSE;
6554         }
6555         if (white_piece && !WhiteOnMove(currentMove)) {
6556             DisplayMoveError(_("It is Black's turn"));
6557             return FALSE;
6558         }
6559         break;
6560
6561       default:
6562       case IcsExamining:
6563         break;
6564     }
6565     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6566         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6567         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6568         && gameMode != AnalyzeFile && gameMode != Training) {
6569         DisplayMoveError(_("Displayed position is not current"));
6570         return FALSE;
6571     }
6572     return TRUE;
6573 }
6574
6575 Boolean
6576 OnlyMove (int *x, int *y, Boolean captures)
6577 {
6578     DisambiguateClosure cl;
6579     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6580     switch(gameMode) {
6581       case MachinePlaysBlack:
6582       case IcsPlayingWhite:
6583       case BeginningOfGame:
6584         if(!WhiteOnMove(currentMove)) return FALSE;
6585         break;
6586       case MachinePlaysWhite:
6587       case IcsPlayingBlack:
6588         if(WhiteOnMove(currentMove)) return FALSE;
6589         break;
6590       case EditGame:
6591         break;
6592       default:
6593         return FALSE;
6594     }
6595     cl.pieceIn = EmptySquare;
6596     cl.rfIn = *y;
6597     cl.ffIn = *x;
6598     cl.rtIn = -1;
6599     cl.ftIn = -1;
6600     cl.promoCharIn = NULLCHAR;
6601     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6602     if( cl.kind == NormalMove ||
6603         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6604         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6605         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6606       fromX = cl.ff;
6607       fromY = cl.rf;
6608       *x = cl.ft;
6609       *y = cl.rt;
6610       return TRUE;
6611     }
6612     if(cl.kind != ImpossibleMove) return FALSE;
6613     cl.pieceIn = EmptySquare;
6614     cl.rfIn = -1;
6615     cl.ffIn = -1;
6616     cl.rtIn = *y;
6617     cl.ftIn = *x;
6618     cl.promoCharIn = NULLCHAR;
6619     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6620     if( cl.kind == NormalMove ||
6621         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6622         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6623         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6624       fromX = cl.ff;
6625       fromY = cl.rf;
6626       *x = cl.ft;
6627       *y = cl.rt;
6628       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6629       return TRUE;
6630     }
6631     return FALSE;
6632 }
6633
6634 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6635 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6636 int lastLoadGameUseList = FALSE;
6637 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6638 ChessMove lastLoadGameStart = EndOfFile;
6639 int doubleClick;
6640
6641 void
6642 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6643 {
6644     ChessMove moveType;
6645     ChessSquare pup;
6646     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6647
6648     /* Check if the user is playing in turn.  This is complicated because we
6649        let the user "pick up" a piece before it is his turn.  So the piece he
6650        tried to pick up may have been captured by the time he puts it down!
6651        Therefore we use the color the user is supposed to be playing in this
6652        test, not the color of the piece that is currently on the starting
6653        square---except in EditGame mode, where the user is playing both
6654        sides; fortunately there the capture race can't happen.  (It can
6655        now happen in IcsExamining mode, but that's just too bad.  The user
6656        will get a somewhat confusing message in that case.)
6657        */
6658
6659     switch (gameMode) {
6660       case AnalyzeFile:
6661       case TwoMachinesPlay:
6662       case EndOfGame:
6663       case IcsObserving:
6664       case IcsIdle:
6665         /* We switched into a game mode where moves are not accepted,
6666            perhaps while the mouse button was down. */
6667         return;
6668
6669       case MachinePlaysWhite:
6670         /* User is moving for Black */
6671         if (WhiteOnMove(currentMove)) {
6672             DisplayMoveError(_("It is White's turn"));
6673             return;
6674         }
6675         break;
6676
6677       case MachinePlaysBlack:
6678         /* User is moving for White */
6679         if (!WhiteOnMove(currentMove)) {
6680             DisplayMoveError(_("It is Black's turn"));
6681             return;
6682         }
6683         break;
6684
6685       case PlayFromGameFile:
6686             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6687       case EditGame:
6688       case IcsExamining:
6689       case BeginningOfGame:
6690       case AnalyzeMode:
6691       case Training:
6692         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6693         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6694             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6695             /* User is moving for Black */
6696             if (WhiteOnMove(currentMove)) {
6697                 DisplayMoveError(_("It is White's turn"));
6698                 return;
6699             }
6700         } else {
6701             /* User is moving for White */
6702             if (!WhiteOnMove(currentMove)) {
6703                 DisplayMoveError(_("It is Black's turn"));
6704                 return;
6705             }
6706         }
6707         break;
6708
6709       case IcsPlayingBlack:
6710         /* User is moving for Black */
6711         if (WhiteOnMove(currentMove)) {
6712             if (!appData.premove) {
6713                 DisplayMoveError(_("It is White's turn"));
6714             } else if (toX >= 0 && toY >= 0) {
6715                 premoveToX = toX;
6716                 premoveToY = toY;
6717                 premoveFromX = fromX;
6718                 premoveFromY = fromY;
6719                 premovePromoChar = promoChar;
6720                 gotPremove = 1;
6721                 if (appData.debugMode)
6722                     fprintf(debugFP, "Got premove: fromX %d,"
6723                             "fromY %d, toX %d, toY %d\n",
6724                             fromX, fromY, toX, toY);
6725             }
6726             return;
6727         }
6728         break;
6729
6730       case IcsPlayingWhite:
6731         /* User is moving for White */
6732         if (!WhiteOnMove(currentMove)) {
6733             if (!appData.premove) {
6734                 DisplayMoveError(_("It is Black's turn"));
6735             } else if (toX >= 0 && toY >= 0) {
6736                 premoveToX = toX;
6737                 premoveToY = toY;
6738                 premoveFromX = fromX;
6739                 premoveFromY = fromY;
6740                 premovePromoChar = promoChar;
6741                 gotPremove = 1;
6742                 if (appData.debugMode)
6743                     fprintf(debugFP, "Got premove: fromX %d,"
6744                             "fromY %d, toX %d, toY %d\n",
6745                             fromX, fromY, toX, toY);
6746             }
6747             return;
6748         }
6749         break;
6750
6751       default:
6752         break;
6753
6754       case EditPosition:
6755         /* EditPosition, empty square, or different color piece;
6756            click-click move is possible */
6757         if (toX == -2 || toY == -2) {
6758             boards[0][fromY][fromX] = EmptySquare;
6759             DrawPosition(FALSE, boards[currentMove]);
6760             return;
6761         } else if (toX >= 0 && toY >= 0) {
6762             boards[0][toY][toX] = boards[0][fromY][fromX];
6763             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6764                 if(boards[0][fromY][0] != EmptySquare) {
6765                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6766                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6767                 }
6768             } else
6769             if(fromX == BOARD_RGHT+1) {
6770                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6771                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6772                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6773                 }
6774             } else
6775             boards[0][fromY][fromX] = gatingPiece;
6776             DrawPosition(FALSE, boards[currentMove]);
6777             return;
6778         }
6779         return;
6780     }
6781
6782     if(toX < 0 || toY < 0) return;
6783     pup = boards[currentMove][toY][toX];
6784
6785     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6786     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6787          if( pup != EmptySquare ) return;
6788          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6789            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6790                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6791            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6792            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6793            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6794            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6795          fromY = DROP_RANK;
6796     }
6797
6798     /* [HGM] always test for legality, to get promotion info */
6799     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6800                                          fromY, fromX, toY, toX, promoChar);
6801
6802     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6803
6804     /* [HGM] but possibly ignore an IllegalMove result */
6805     if (appData.testLegality) {
6806         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6807             DisplayMoveError(_("Illegal move"));
6808             return;
6809         }
6810     }
6811
6812     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6813         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6814              ClearPremoveHighlights(); // was included
6815         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6816         return;
6817     }
6818
6819     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6820 }
6821
6822 /* Common tail of UserMoveEvent and DropMenuEvent */
6823 int
6824 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6825 {
6826     char *bookHit = 0;
6827
6828     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6829         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6830         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6831         if(WhiteOnMove(currentMove)) {
6832             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6833         } else {
6834             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6835         }
6836     }
6837
6838     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6839        move type in caller when we know the move is a legal promotion */
6840     if(moveType == NormalMove && promoChar)
6841         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6842
6843     /* [HGM] <popupFix> The following if has been moved here from
6844        UserMoveEvent(). Because it seemed to belong here (why not allow
6845        piece drops in training games?), and because it can only be
6846        performed after it is known to what we promote. */
6847     if (gameMode == Training) {
6848       /* compare the move played on the board to the next move in the
6849        * game. If they match, display the move and the opponent's response.
6850        * If they don't match, display an error message.
6851        */
6852       int saveAnimate;
6853       Board testBoard;
6854       CopyBoard(testBoard, boards[currentMove]);
6855       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6856
6857       if (CompareBoards(testBoard, boards[currentMove+1])) {
6858         ForwardInner(currentMove+1);
6859
6860         /* Autoplay the opponent's response.
6861          * if appData.animate was TRUE when Training mode was entered,
6862          * the response will be animated.
6863          */
6864         saveAnimate = appData.animate;
6865         appData.animate = animateTraining;
6866         ForwardInner(currentMove+1);
6867         appData.animate = saveAnimate;
6868
6869         /* check for the end of the game */
6870         if (currentMove >= forwardMostMove) {
6871           gameMode = PlayFromGameFile;
6872           ModeHighlight();
6873           SetTrainingModeOff();
6874           DisplayInformation(_("End of game"));
6875         }
6876       } else {
6877         DisplayError(_("Incorrect move"), 0);
6878       }
6879       return 1;
6880     }
6881
6882   /* Ok, now we know that the move is good, so we can kill
6883      the previous line in Analysis Mode */
6884   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6885                                 && currentMove < forwardMostMove) {
6886     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6887     else forwardMostMove = currentMove;
6888   }
6889
6890   ClearMap();
6891
6892   /* If we need the chess program but it's dead, restart it */
6893   ResurrectChessProgram();
6894
6895   /* A user move restarts a paused game*/
6896   if (pausing)
6897     PauseEvent();
6898
6899   thinkOutput[0] = NULLCHAR;
6900
6901   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6902
6903   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6904     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6905     return 1;
6906   }
6907
6908   if (gameMode == BeginningOfGame) {
6909     if (appData.noChessProgram) {
6910       gameMode = EditGame;
6911       SetGameInfo();
6912     } else {
6913       char buf[MSG_SIZ];
6914       gameMode = MachinePlaysBlack;
6915       StartClocks();
6916       SetGameInfo();
6917       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6918       DisplayTitle(buf);
6919       if (first.sendName) {
6920         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6921         SendToProgram(buf, &first);
6922       }
6923       StartClocks();
6924     }
6925     ModeHighlight();
6926   }
6927
6928   /* Relay move to ICS or chess engine */
6929   if (appData.icsActive) {
6930     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6931         gameMode == IcsExamining) {
6932       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6933         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6934         SendToICS("draw ");
6935         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6936       }
6937       // also send plain move, in case ICS does not understand atomic claims
6938       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6939       ics_user_moved = 1;
6940     }
6941   } else {
6942     if (first.sendTime && (gameMode == BeginningOfGame ||
6943                            gameMode == MachinePlaysWhite ||
6944                            gameMode == MachinePlaysBlack)) {
6945       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6946     }
6947     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6948          // [HGM] book: if program might be playing, let it use book
6949         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6950         first.maybeThinking = TRUE;
6951     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6952         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6953         SendBoard(&first, currentMove+1);
6954         if(second.analyzing) {
6955             if(!second.useSetboard) SendToProgram("undo\n", &second);
6956             SendBoard(&second, currentMove+1);
6957         }
6958     } else {
6959         SendMoveToProgram(forwardMostMove-1, &first);
6960         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6961     }
6962     if (currentMove == cmailOldMove + 1) {
6963       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6964     }
6965   }
6966
6967   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6968
6969   switch (gameMode) {
6970   case EditGame:
6971     if(appData.testLegality)
6972     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6973     case MT_NONE:
6974     case MT_CHECK:
6975       break;
6976     case MT_CHECKMATE:
6977     case MT_STAINMATE:
6978       if (WhiteOnMove(currentMove)) {
6979         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6980       } else {
6981         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6982       }
6983       break;
6984     case MT_STALEMATE:
6985       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6986       break;
6987     }
6988     break;
6989
6990   case MachinePlaysBlack:
6991   case MachinePlaysWhite:
6992     /* disable certain menu options while machine is thinking */
6993     SetMachineThinkingEnables();
6994     break;
6995
6996   default:
6997     break;
6998   }
6999
7000   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7001   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7002
7003   if(bookHit) { // [HGM] book: simulate book reply
7004         static char bookMove[MSG_SIZ]; // a bit generous?
7005
7006         programStats.nodes = programStats.depth = programStats.time =
7007         programStats.score = programStats.got_only_move = 0;
7008         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7009
7010         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7011         strcat(bookMove, bookHit);
7012         HandleMachineMove(bookMove, &first);
7013   }
7014   return 1;
7015 }
7016
7017 void
7018 MarkByFEN(char *fen)
7019 {
7020         int r, f;
7021         if(!appData.markers || !appData.highlightDragging) return;
7022         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) marker[r][f] = 0;
7023         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7024         while(*fen) {
7025             int s = 0;
7026             marker[r][f] = 0;
7027             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7028             if(*fen == 'Y') marker[r][f++] = 1; else
7029             if(*fen == 'R') marker[r][f++] = 2; else {
7030                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7031               f += s; fen -= s>0;
7032             }
7033             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7034             if(r < 0) break;
7035             fen++;
7036         }
7037         DrawPosition(TRUE, NULL);
7038 }
7039
7040 void
7041 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7042 {
7043     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7044     Markers *m = (Markers *) closure;
7045     if(rf == fromY && ff == fromX)
7046         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7047                          || kind == WhiteCapturesEnPassant
7048                          || kind == BlackCapturesEnPassant);
7049     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7050 }
7051
7052 void
7053 MarkTargetSquares (int clear)
7054 {
7055   int x, y, sum=0;
7056   if(clear) { // no reason to ever suppress clearing
7057     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7058     if(!sum) return; // nothing was cleared,no redraw needed
7059   } else {
7060     int capt = 0;
7061     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7062        !appData.testLegality || gameMode == EditPosition) return;
7063     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7064     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7065       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7066       if(capt)
7067       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7068     }
7069   }
7070   DrawPosition(FALSE, NULL);
7071 }
7072
7073 int
7074 Explode (Board board, int fromX, int fromY, int toX, int toY)
7075 {
7076     if(gameInfo.variant == VariantAtomic &&
7077        (board[toY][toX] != EmptySquare ||                     // capture?
7078         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7079                          board[fromY][fromX] == BlackPawn   )
7080       )) {
7081         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7082         return TRUE;
7083     }
7084     return FALSE;
7085 }
7086
7087 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7088
7089 int
7090 CanPromote (ChessSquare piece, int y)
7091 {
7092         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7093         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7094         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7095            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7096            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7097          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7098         return (piece == BlackPawn && y == 1 ||
7099                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7100                 piece == BlackLance && y == 1 ||
7101                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7102 }
7103
7104 void ReportClick(char *action, int x, int y)
7105 {
7106         char buf[MSG_SIZ]; // Inform engine of what user does
7107         if(!first.highlight || gameMode == EditPosition) return;
7108         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7109         SendToProgram(buf, &first);
7110 }
7111
7112 void
7113 LeftClick (ClickType clickType, int xPix, int yPix)
7114 {
7115     int x, y;
7116     Boolean saveAnimate;
7117     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7118     char promoChoice = NULLCHAR;
7119     ChessSquare piece;
7120     static TimeMark lastClickTime, prevClickTime;
7121
7122     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7123
7124     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7125
7126     if (clickType == Press) ErrorPopDown();
7127
7128     x = EventToSquare(xPix, BOARD_WIDTH);
7129     y = EventToSquare(yPix, BOARD_HEIGHT);
7130     if (!flipView && y >= 0) {
7131         y = BOARD_HEIGHT - 1 - y;
7132     }
7133     if (flipView && x >= 0) {
7134         x = BOARD_WIDTH - 1 - x;
7135     }
7136
7137     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7138         defaultPromoChoice = promoSweep;
7139         promoSweep = EmptySquare;   // terminate sweep
7140         promoDefaultAltered = TRUE;
7141         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7142     }
7143
7144     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7145         if(clickType == Release) return; // ignore upclick of click-click destination
7146         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7147         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7148         if(gameInfo.holdingsWidth &&
7149                 (WhiteOnMove(currentMove)
7150                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7151                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7152             // click in right holdings, for determining promotion piece
7153             ChessSquare p = boards[currentMove][y][x];
7154             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7155             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7156             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7157                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7158                 fromX = fromY = -1;
7159                 return;
7160             }
7161         }
7162         DrawPosition(FALSE, boards[currentMove]);
7163         return;
7164     }
7165
7166     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7167     if(clickType == Press
7168             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7169               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7170               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7171         return;
7172
7173     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7174         // could be static click on premove from-square: abort premove
7175         gotPremove = 0;
7176         ClearPremoveHighlights();
7177     }
7178
7179     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7180         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7181
7182     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7183         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7184                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7185         defaultPromoChoice = DefaultPromoChoice(side);
7186     }
7187
7188     autoQueen = appData.alwaysPromoteToQueen;
7189
7190     if (fromX == -1) {
7191       int originalY = y;
7192       gatingPiece = EmptySquare;
7193       if (clickType != Press) {
7194         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7195             DragPieceEnd(xPix, yPix); dragging = 0;
7196             DrawPosition(FALSE, NULL);
7197         }
7198         return;
7199       }
7200       doubleClick = FALSE;
7201       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7202         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7203       }
7204       fromX = x; fromY = y; toX = toY = -1;
7205       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7206          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7207          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7208             /* First square */
7209             if (OKToStartUserMove(fromX, fromY)) {
7210                 second = 0;
7211                 ReportClick("lift", x, y);
7212                 MarkTargetSquares(0);
7213                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7214                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7215                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7216                     promoSweep = defaultPromoChoice;
7217                     selectFlag = 0; lastX = xPix; lastY = yPix;
7218                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7219                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7220                 }
7221                 if (appData.highlightDragging) {
7222                     SetHighlights(fromX, fromY, -1, -1);
7223                 } else {
7224                     ClearHighlights();
7225                 }
7226             } else fromX = fromY = -1;
7227             return;
7228         }
7229     }
7230
7231     /* fromX != -1 */
7232     if (clickType == Press && gameMode != EditPosition) {
7233         ChessSquare fromP;
7234         ChessSquare toP;
7235         int frc;
7236
7237         // ignore off-board to clicks
7238         if(y < 0 || x < 0) return;
7239
7240         /* Check if clicking again on the same color piece */
7241         fromP = boards[currentMove][fromY][fromX];
7242         toP = boards[currentMove][y][x];
7243         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7244         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7245              WhitePawn <= toP && toP <= WhiteKing &&
7246              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7247              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7248             (BlackPawn <= fromP && fromP <= BlackKing &&
7249              BlackPawn <= toP && toP <= BlackKing &&
7250              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7251              !(fromP == BlackKing && toP == BlackRook && frc))) {
7252             /* Clicked again on same color piece -- changed his mind */
7253             second = (x == fromX && y == fromY);
7254             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7255                 second = FALSE; // first double-click rather than scond click
7256                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7257             }
7258             promoDefaultAltered = FALSE;
7259             MarkTargetSquares(1);
7260            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7261             if (appData.highlightDragging) {
7262                 SetHighlights(x, y, -1, -1);
7263             } else {
7264                 ClearHighlights();
7265             }
7266             if (OKToStartUserMove(x, y)) {
7267                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7268                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7269                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7270                  gatingPiece = boards[currentMove][fromY][fromX];
7271                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7272                 fromX = x;
7273                 fromY = y; dragging = 1;
7274                 ReportClick("lift", x, y);
7275                 MarkTargetSquares(0);
7276                 DragPieceBegin(xPix, yPix, FALSE);
7277                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7278                     promoSweep = defaultPromoChoice;
7279                     selectFlag = 0; lastX = xPix; lastY = yPix;
7280                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7281                 }
7282             }
7283            }
7284            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7285            second = FALSE;
7286         }
7287         // ignore clicks on holdings
7288         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7289     }
7290
7291     if (clickType == Release && x == fromX && y == fromY) {
7292         DragPieceEnd(xPix, yPix); dragging = 0;
7293         if(clearFlag) {
7294             // a deferred attempt to click-click move an empty square on top of a piece
7295             boards[currentMove][y][x] = EmptySquare;
7296             ClearHighlights();
7297             DrawPosition(FALSE, boards[currentMove]);
7298             fromX = fromY = -1; clearFlag = 0;
7299             return;
7300         }
7301         if (appData.animateDragging) {
7302             /* Undo animation damage if any */
7303             DrawPosition(FALSE, NULL);
7304         }
7305         if (second || sweepSelecting) {
7306             /* Second up/down in same square; just abort move */
7307             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7308             second = sweepSelecting = 0;
7309             fromX = fromY = -1;
7310             gatingPiece = EmptySquare;
7311             MarkTargetSquares(1);
7312             ClearHighlights();
7313             gotPremove = 0;
7314             ClearPremoveHighlights();
7315         } else {
7316             /* First upclick in same square; start click-click mode */
7317             SetHighlights(x, y, -1, -1);
7318         }
7319         return;
7320     }
7321
7322     clearFlag = 0;
7323
7324     /* we now have a different from- and (possibly off-board) to-square */
7325     /* Completed move */
7326     if(!sweepSelecting) {
7327         toX = x;
7328         toY = y;
7329     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7330
7331     saveAnimate = appData.animate;
7332     if (clickType == Press) {
7333         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7334             // must be Edit Position mode with empty-square selected
7335             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7336             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7337             return;
7338         }
7339         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7340           if(appData.sweepSelect) {
7341             ChessSquare piece = boards[currentMove][fromY][fromX];
7342             promoSweep = defaultPromoChoice;
7343             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7344             selectFlag = 0; lastX = xPix; lastY = yPix;
7345             Sweep(0); // Pawn that is going to promote: preview promotion piece
7346             sweepSelecting = 1;
7347             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7348             MarkTargetSquares(1);
7349           }
7350           return; // promo popup appears on up-click
7351         }
7352         /* Finish clickclick move */
7353         if (appData.animate || appData.highlightLastMove) {
7354             SetHighlights(fromX, fromY, toX, toY);
7355         } else {
7356             ClearHighlights();
7357         }
7358     } else {
7359 #if 0
7360 // [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
7361         /* Finish drag move */
7362         if (appData.highlightLastMove) {
7363             SetHighlights(fromX, fromY, toX, toY);
7364         } else {
7365             ClearHighlights();
7366         }
7367 #endif
7368         DragPieceEnd(xPix, yPix); dragging = 0;
7369         /* Don't animate move and drag both */
7370         appData.animate = FALSE;
7371     }
7372
7373     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7374     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7375         ChessSquare piece = boards[currentMove][fromY][fromX];
7376         if(gameMode == EditPosition && piece != EmptySquare &&
7377            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7378             int n;
7379
7380             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7381                 n = PieceToNumber(piece - (int)BlackPawn);
7382                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7383                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7384                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7385             } else
7386             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7387                 n = PieceToNumber(piece);
7388                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7389                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7390                 boards[currentMove][n][BOARD_WIDTH-2]++;
7391             }
7392             boards[currentMove][fromY][fromX] = EmptySquare;
7393         }
7394         ClearHighlights();
7395         fromX = fromY = -1;
7396         MarkTargetSquares(1);
7397         DrawPosition(TRUE, boards[currentMove]);
7398         return;
7399     }
7400
7401     // off-board moves should not be highlighted
7402     if(x < 0 || y < 0) ClearHighlights();
7403     else ReportClick("put", x, y);
7404
7405     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7406
7407     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7408         SetHighlights(fromX, fromY, toX, toY);
7409         MarkTargetSquares(1);
7410         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7411             // [HGM] super: promotion to captured piece selected from holdings
7412             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7413             promotionChoice = TRUE;
7414             // kludge follows to temporarily execute move on display, without promoting yet
7415             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7416             boards[currentMove][toY][toX] = p;
7417             DrawPosition(FALSE, boards[currentMove]);
7418             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7419             boards[currentMove][toY][toX] = q;
7420             DisplayMessage("Click in holdings to choose piece", "");
7421             return;
7422         }
7423         PromotionPopUp();
7424     } else {
7425         int oldMove = currentMove;
7426         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7427         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7428         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7429         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7430            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7431             DrawPosition(TRUE, boards[currentMove]);
7432         MarkTargetSquares(1);
7433         fromX = fromY = -1;
7434     }
7435     appData.animate = saveAnimate;
7436     if (appData.animate || appData.animateDragging) {
7437         /* Undo animation damage if needed */
7438         DrawPosition(FALSE, NULL);
7439     }
7440 }
7441
7442 int
7443 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7444 {   // front-end-free part taken out of PieceMenuPopup
7445     int whichMenu; int xSqr, ySqr;
7446
7447     if(seekGraphUp) { // [HGM] seekgraph
7448         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7449         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7450         return -2;
7451     }
7452
7453     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7454          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7455         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7456         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7457         if(action == Press)   {
7458             originalFlip = flipView;
7459             flipView = !flipView; // temporarily flip board to see game from partners perspective
7460             DrawPosition(TRUE, partnerBoard);
7461             DisplayMessage(partnerStatus, "");
7462             partnerUp = TRUE;
7463         } else if(action == Release) {
7464             flipView = originalFlip;
7465             DrawPosition(TRUE, boards[currentMove]);
7466             partnerUp = FALSE;
7467         }
7468         return -2;
7469     }
7470
7471     xSqr = EventToSquare(x, BOARD_WIDTH);
7472     ySqr = EventToSquare(y, BOARD_HEIGHT);
7473     if (action == Release) {
7474         if(pieceSweep != EmptySquare) {
7475             EditPositionMenuEvent(pieceSweep, toX, toY);
7476             pieceSweep = EmptySquare;
7477         } else UnLoadPV(); // [HGM] pv
7478     }
7479     if (action != Press) return -2; // return code to be ignored
7480     switch (gameMode) {
7481       case IcsExamining:
7482         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7483       case EditPosition:
7484         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7485         if (xSqr < 0 || ySqr < 0) return -1;
7486         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7487         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7488         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7489         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7490         NextPiece(0);
7491         return 2; // grab
7492       case IcsObserving:
7493         if(!appData.icsEngineAnalyze) return -1;
7494       case IcsPlayingWhite:
7495       case IcsPlayingBlack:
7496         if(!appData.zippyPlay) goto noZip;
7497       case AnalyzeMode:
7498       case AnalyzeFile:
7499       case MachinePlaysWhite:
7500       case MachinePlaysBlack:
7501       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7502         if (!appData.dropMenu) {
7503           LoadPV(x, y);
7504           return 2; // flag front-end to grab mouse events
7505         }
7506         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7507            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7508       case EditGame:
7509       noZip:
7510         if (xSqr < 0 || ySqr < 0) return -1;
7511         if (!appData.dropMenu || appData.testLegality &&
7512             gameInfo.variant != VariantBughouse &&
7513             gameInfo.variant != VariantCrazyhouse) return -1;
7514         whichMenu = 1; // drop menu
7515         break;
7516       default:
7517         return -1;
7518     }
7519
7520     if (((*fromX = xSqr) < 0) ||
7521         ((*fromY = ySqr) < 0)) {
7522         *fromX = *fromY = -1;
7523         return -1;
7524     }
7525     if (flipView)
7526       *fromX = BOARD_WIDTH - 1 - *fromX;
7527     else
7528       *fromY = BOARD_HEIGHT - 1 - *fromY;
7529
7530     return whichMenu;
7531 }
7532
7533 void
7534 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7535 {
7536 //    char * hint = lastHint;
7537     FrontEndProgramStats stats;
7538
7539     stats.which = cps == &first ? 0 : 1;
7540     stats.depth = cpstats->depth;
7541     stats.nodes = cpstats->nodes;
7542     stats.score = cpstats->score;
7543     stats.time = cpstats->time;
7544     stats.pv = cpstats->movelist;
7545     stats.hint = lastHint;
7546     stats.an_move_index = 0;
7547     stats.an_move_count = 0;
7548
7549     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7550         stats.hint = cpstats->move_name;
7551         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7552         stats.an_move_count = cpstats->nr_moves;
7553     }
7554
7555     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
7556
7557     SetProgramStats( &stats );
7558 }
7559
7560 void
7561 ClearEngineOutputPane (int which)
7562 {
7563     static FrontEndProgramStats dummyStats;
7564     dummyStats.which = which;
7565     dummyStats.pv = "#";
7566     SetProgramStats( &dummyStats );
7567 }
7568
7569 #define MAXPLAYERS 500
7570
7571 char *
7572 TourneyStandings (int display)
7573 {
7574     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7575     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7576     char result, *p, *names[MAXPLAYERS];
7577
7578     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7579         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7580     names[0] = p = strdup(appData.participants);
7581     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7582
7583     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7584
7585     while(result = appData.results[nr]) {
7586         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7587         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7588         wScore = bScore = 0;
7589         switch(result) {
7590           case '+': wScore = 2; break;
7591           case '-': bScore = 2; break;
7592           case '=': wScore = bScore = 1; break;
7593           case ' ':
7594           case '*': return strdup("busy"); // tourney not finished
7595         }
7596         score[w] += wScore;
7597         score[b] += bScore;
7598         games[w]++;
7599         games[b]++;
7600         nr++;
7601     }
7602     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7603     for(w=0; w<nPlayers; w++) {
7604         bScore = -1;
7605         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7606         ranking[w] = b; points[w] = bScore; score[b] = -2;
7607     }
7608     p = malloc(nPlayers*34+1);
7609     for(w=0; w<nPlayers && w<display; w++)
7610         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7611     free(names[0]);
7612     return p;
7613 }
7614
7615 void
7616 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7617 {       // count all piece types
7618         int p, f, r;
7619         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7620         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7621         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7622                 p = board[r][f];
7623                 pCnt[p]++;
7624                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7625                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7626                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7627                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7628                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7629                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7630         }
7631 }
7632
7633 int
7634 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7635 {
7636         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7637         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7638
7639         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7640         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7641         if(myPawns == 2 && nMine == 3) // KPP
7642             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7643         if(myPawns == 1 && nMine == 2) // KP
7644             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7645         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7646             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7647         if(myPawns) return FALSE;
7648         if(pCnt[WhiteRook+side])
7649             return pCnt[BlackRook-side] ||
7650                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7651                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7652                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7653         if(pCnt[WhiteCannon+side]) {
7654             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7655             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7656         }
7657         if(pCnt[WhiteKnight+side])
7658             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7659         return FALSE;
7660 }
7661
7662 int
7663 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7664 {
7665         VariantClass v = gameInfo.variant;
7666
7667         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7668         if(v == VariantShatranj) return TRUE; // always winnable through baring
7669         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7670         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7671
7672         if(v == VariantXiangqi) {
7673                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7674
7675                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7676                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7677                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7678                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7679                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7680                 if(stale) // we have at least one last-rank P plus perhaps C
7681                     return majors // KPKX
7682                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7683                 else // KCA*E*
7684                     return pCnt[WhiteFerz+side] // KCAK
7685                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7686                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7687                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7688
7689         } else if(v == VariantKnightmate) {
7690                 if(nMine == 1) return FALSE;
7691                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7692         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7693                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7694
7695                 if(nMine == 1) return FALSE; // bare King
7696                 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
7697                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7698                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7699                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7700                 if(pCnt[WhiteKnight+side])
7701                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7702                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7703                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7704                 if(nBishops)
7705                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7706                 if(pCnt[WhiteAlfil+side])
7707                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7708                 if(pCnt[WhiteWazir+side])
7709                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7710         }
7711
7712         return TRUE;
7713 }
7714
7715 int
7716 CompareWithRights (Board b1, Board b2)
7717 {
7718     int rights = 0;
7719     if(!CompareBoards(b1, b2)) return FALSE;
7720     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7721     /* compare castling rights */
7722     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7723            rights++; /* King lost rights, while rook still had them */
7724     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7725         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7726            rights++; /* but at least one rook lost them */
7727     }
7728     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7729            rights++;
7730     if( b1[CASTLING][5] != NoRights ) {
7731         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7732            rights++;
7733     }
7734     return rights == 0;
7735 }
7736
7737 int
7738 Adjudicate (ChessProgramState *cps)
7739 {       // [HGM] some adjudications useful with buggy engines
7740         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7741         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7742         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7743         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7744         int k, drop, count = 0; static int bare = 1;
7745         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7746         Boolean canAdjudicate = !appData.icsActive;
7747
7748         // most tests only when we understand the game, i.e. legality-checking on
7749             if( appData.testLegality )
7750             {   /* [HGM] Some more adjudications for obstinate engines */
7751                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7752                 static int moveCount = 6;
7753                 ChessMove result;
7754                 char *reason = NULL;
7755
7756                 /* Count what is on board. */
7757                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7758
7759                 /* Some material-based adjudications that have to be made before stalemate test */
7760                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7761                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7762                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7763                      if(canAdjudicate && appData.checkMates) {
7764                          if(engineOpponent)
7765                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7766                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7767                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7768                          return 1;
7769                      }
7770                 }
7771
7772                 /* Bare King in Shatranj (loses) or Losers (wins) */
7773                 if( nrW == 1 || nrB == 1) {
7774                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7775                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7776                      if(canAdjudicate && appData.checkMates) {
7777                          if(engineOpponent)
7778                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7779                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7780                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7781                          return 1;
7782                      }
7783                   } else
7784                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7785                   {    /* bare King */
7786                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7787                         if(canAdjudicate && appData.checkMates) {
7788                             /* but only adjudicate if adjudication enabled */
7789                             if(engineOpponent)
7790                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7791                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7792                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7793                             return 1;
7794                         }
7795                   }
7796                 } else bare = 1;
7797
7798
7799             // don't wait for engine to announce game end if we can judge ourselves
7800             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7801               case MT_CHECK:
7802                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7803                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7804                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7805                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7806                             checkCnt++;
7807                         if(checkCnt >= 2) {
7808                             reason = "Xboard adjudication: 3rd check";
7809                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7810                             break;
7811                         }
7812                     }
7813                 }
7814               case MT_NONE:
7815               default:
7816                 break;
7817               case MT_STALEMATE:
7818               case MT_STAINMATE:
7819                 reason = "Xboard adjudication: Stalemate";
7820                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7821                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7822                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7823                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7824                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7825                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7826                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7827                                                                         EP_CHECKMATE : EP_WINS);
7828                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7829                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7830                 }
7831                 break;
7832               case MT_CHECKMATE:
7833                 reason = "Xboard adjudication: Checkmate";
7834                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7835                 if(gameInfo.variant == VariantShogi) {
7836                     if(forwardMostMove > backwardMostMove
7837                        && moveList[forwardMostMove-1][1] == '@'
7838                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7839                         reason = "XBoard adjudication: pawn-drop mate";
7840                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7841                     }
7842                 }
7843                 break;
7844             }
7845
7846                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7847                     case EP_STALEMATE:
7848                         result = GameIsDrawn; break;
7849                     case EP_CHECKMATE:
7850                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7851                     case EP_WINS:
7852                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7853                     default:
7854                         result = EndOfFile;
7855                 }
7856                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7857                     if(engineOpponent)
7858                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7859                     GameEnds( result, reason, GE_XBOARD );
7860                     return 1;
7861                 }
7862
7863                 /* Next absolutely insufficient mating material. */
7864                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7865                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7866                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7867
7868                      /* always flag draws, for judging claims */
7869                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7870
7871                      if(canAdjudicate && appData.materialDraws) {
7872                          /* but only adjudicate them if adjudication enabled */
7873                          if(engineOpponent) {
7874                            SendToProgram("force\n", engineOpponent); // suppress reply
7875                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7876                          }
7877                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7878                          return 1;
7879                      }
7880                 }
7881
7882                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7883                 if(gameInfo.variant == VariantXiangqi ?
7884                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7885                  : nrW + nrB == 4 &&
7886                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7887                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7888                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7889                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7890                    ) ) {
7891                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7892                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7893                           if(engineOpponent) {
7894                             SendToProgram("force\n", engineOpponent); // suppress reply
7895                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7896                           }
7897                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7898                           return 1;
7899                      }
7900                 } else moveCount = 6;
7901             }
7902
7903         // Repetition draws and 50-move rule can be applied independently of legality testing
7904
7905                 /* Check for rep-draws */
7906                 count = 0;
7907                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7908                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7909                 for(k = forwardMostMove-2;
7910                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7911                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7912                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7913                     k-=2)
7914                 {   int rights=0;
7915                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7916                         /* compare castling rights */
7917                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7918                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7919                                 rights++; /* King lost rights, while rook still had them */
7920                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7921                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7922                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7923                                    rights++; /* but at least one rook lost them */
7924                         }
7925                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7926                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7927                                 rights++;
7928                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7929                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7930                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7931                                    rights++;
7932                         }
7933                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7934                             && appData.drawRepeats > 1) {
7935                              /* adjudicate after user-specified nr of repeats */
7936                              int result = GameIsDrawn;
7937                              char *details = "XBoard adjudication: repetition draw";
7938                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7939                                 // [HGM] xiangqi: check for forbidden perpetuals
7940                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7941                                 for(m=forwardMostMove; m>k; m-=2) {
7942                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7943                                         ourPerpetual = 0; // the current mover did not always check
7944                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7945                                         hisPerpetual = 0; // the opponent did not always check
7946                                 }
7947                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7948                                                                         ourPerpetual, hisPerpetual);
7949                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7950                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7951                                     details = "Xboard adjudication: perpetual checking";
7952                                 } else
7953                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7954                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7955                                 } else
7956                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
7957                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
7958                                         result = BlackWins;
7959                                         details = "Xboard adjudication: repetition";
7960                                     }
7961                                 } else // it must be XQ
7962                                 // Now check for perpetual chases
7963                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7964                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7965                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7966                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7967                                         static char resdet[MSG_SIZ];
7968                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7969                                         details = resdet;
7970                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7971                                     } else
7972                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7973                                         break; // Abort repetition-checking loop.
7974                                 }
7975                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7976                              }
7977                              if(engineOpponent) {
7978                                SendToProgram("force\n", engineOpponent); // suppress reply
7979                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7980                              }
7981                              GameEnds( result, details, GE_XBOARD );
7982                              return 1;
7983                         }
7984                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7985                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7986                     }
7987                 }
7988
7989                 /* Now we test for 50-move draws. Determine ply count */
7990                 count = forwardMostMove;
7991                 /* look for last irreversble move */
7992                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7993                     count--;
7994                 /* if we hit starting position, add initial plies */
7995                 if( count == backwardMostMove )
7996                     count -= initialRulePlies;
7997                 count = forwardMostMove - count;
7998                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7999                         // adjust reversible move counter for checks in Xiangqi
8000                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8001                         if(i < backwardMostMove) i = backwardMostMove;
8002                         while(i <= forwardMostMove) {
8003                                 lastCheck = inCheck; // check evasion does not count
8004                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8005                                 if(inCheck || lastCheck) count--; // check does not count
8006                                 i++;
8007                         }
8008                 }
8009                 if( count >= 100)
8010                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8011                          /* this is used to judge if draw claims are legal */
8012                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8013                          if(engineOpponent) {
8014                            SendToProgram("force\n", engineOpponent); // suppress reply
8015                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8016                          }
8017                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8018                          return 1;
8019                 }
8020
8021                 /* if draw offer is pending, treat it as a draw claim
8022                  * when draw condition present, to allow engines a way to
8023                  * claim draws before making their move to avoid a race
8024                  * condition occurring after their move
8025                  */
8026                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8027                          char *p = NULL;
8028                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8029                              p = "Draw claim: 50-move rule";
8030                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8031                              p = "Draw claim: 3-fold repetition";
8032                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8033                              p = "Draw claim: insufficient mating material";
8034                          if( p != NULL && canAdjudicate) {
8035                              if(engineOpponent) {
8036                                SendToProgram("force\n", engineOpponent); // suppress reply
8037                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8038                              }
8039                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8040                              return 1;
8041                          }
8042                 }
8043
8044                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8045                     if(engineOpponent) {
8046                       SendToProgram("force\n", engineOpponent); // suppress reply
8047                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8048                     }
8049                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8050                     return 1;
8051                 }
8052         return 0;
8053 }
8054
8055 char *
8056 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8057 {   // [HGM] book: this routine intercepts moves to simulate book replies
8058     char *bookHit = NULL;
8059
8060     //first determine if the incoming move brings opponent into his book
8061     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8062         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8063     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8064     if(bookHit != NULL && !cps->bookSuspend) {
8065         // make sure opponent is not going to reply after receiving move to book position
8066         SendToProgram("force\n", cps);
8067         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8068     }
8069     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8070     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8071     // now arrange restart after book miss
8072     if(bookHit) {
8073         // after a book hit we never send 'go', and the code after the call to this routine
8074         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8075         char buf[MSG_SIZ], *move = bookHit;
8076         if(cps->useSAN) {
8077             int fromX, fromY, toX, toY;
8078             char promoChar;
8079             ChessMove moveType;
8080             move = buf + 30;
8081             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8082                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8083                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8084                                     PosFlags(forwardMostMove),
8085                                     fromY, fromX, toY, toX, promoChar, move);
8086             } else {
8087                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8088                 bookHit = NULL;
8089             }
8090         }
8091         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8092         SendToProgram(buf, cps);
8093         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8094     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8095         SendToProgram("go\n", cps);
8096         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8097     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8098         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8099             SendToProgram("go\n", cps);
8100         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8101     }
8102     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8103 }
8104
8105 int
8106 LoadError (char *errmess, ChessProgramState *cps)
8107 {   // unloads engine and switches back to -ncp mode if it was first
8108     if(cps->initDone) return FALSE;
8109     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8110     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8111     cps->pr = NoProc;
8112     if(cps == &first) {
8113         appData.noChessProgram = TRUE;
8114         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8115         gameMode = BeginningOfGame; ModeHighlight();
8116         SetNCPMode();
8117     }
8118     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8119     DisplayMessage("", ""); // erase waiting message
8120     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8121     return TRUE;
8122 }
8123
8124 char *savedMessage;
8125 ChessProgramState *savedState;
8126 void
8127 DeferredBookMove (void)
8128 {
8129         if(savedState->lastPing != savedState->lastPong)
8130                     ScheduleDelayedEvent(DeferredBookMove, 10);
8131         else
8132         HandleMachineMove(savedMessage, savedState);
8133 }
8134
8135 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8136 static ChessProgramState *stalledEngine;
8137 static char stashedInputMove[MSG_SIZ];
8138
8139 void
8140 HandleMachineMove (char *message, ChessProgramState *cps)
8141 {
8142     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8143     char realname[MSG_SIZ];
8144     int fromX, fromY, toX, toY;
8145     ChessMove moveType;
8146     char promoChar;
8147     char *p, *pv=buf1;
8148     int machineWhite, oldError;
8149     char *bookHit;
8150
8151     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8152         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8153         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8154             DisplayError(_("Invalid pairing from pairing engine"), 0);
8155             return;
8156         }
8157         pairingReceived = 1;
8158         NextMatchGame();
8159         return; // Skim the pairing messages here.
8160     }
8161
8162     oldError = cps->userError; cps->userError = 0;
8163
8164 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8165     /*
8166      * Kludge to ignore BEL characters
8167      */
8168     while (*message == '\007') message++;
8169
8170     /*
8171      * [HGM] engine debug message: ignore lines starting with '#' character
8172      */
8173     if(cps->debug && *message == '#') return;
8174
8175     /*
8176      * Look for book output
8177      */
8178     if (cps == &first && bookRequested) {
8179         if (message[0] == '\t' || message[0] == ' ') {
8180             /* Part of the book output is here; append it */
8181             strcat(bookOutput, message);
8182             strcat(bookOutput, "  \n");
8183             return;
8184         } else if (bookOutput[0] != NULLCHAR) {
8185             /* All of book output has arrived; display it */
8186             char *p = bookOutput;
8187             while (*p != NULLCHAR) {
8188                 if (*p == '\t') *p = ' ';
8189                 p++;
8190             }
8191             DisplayInformation(bookOutput);
8192             bookRequested = FALSE;
8193             /* Fall through to parse the current output */
8194         }
8195     }
8196
8197     /*
8198      * Look for machine move.
8199      */
8200     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8201         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8202     {
8203         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8204             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8205             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8206             stalledEngine = cps;
8207             if(appData.ponderNextMove) { // bring opponent out of ponder
8208                 if(gameMode == TwoMachinesPlay) {
8209                     if(cps->other->pause)
8210                         PauseEngine(cps->other);
8211                     else
8212                         SendToProgram("easy\n", cps->other);
8213                 }
8214             }
8215             StopClocks();
8216             return;
8217         }
8218
8219         /* This method is only useful on engines that support ping */
8220         if (cps->lastPing != cps->lastPong) {
8221           if (gameMode == BeginningOfGame) {
8222             /* Extra move from before last new; ignore */
8223             if (appData.debugMode) {
8224                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8225             }
8226           } else {
8227             if (appData.debugMode) {
8228                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8229                         cps->which, gameMode);
8230             }
8231
8232             SendToProgram("undo\n", cps);
8233           }
8234           return;
8235         }
8236
8237         switch (gameMode) {
8238           case BeginningOfGame:
8239             /* Extra move from before last reset; ignore */
8240             if (appData.debugMode) {
8241                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8242             }
8243             return;
8244
8245           case EndOfGame:
8246           case IcsIdle:
8247           default:
8248             /* Extra move after we tried to stop.  The mode test is
8249                not a reliable way of detecting this problem, but it's
8250                the best we can do on engines that don't support ping.
8251             */
8252             if (appData.debugMode) {
8253                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8254                         cps->which, gameMode);
8255             }
8256             SendToProgram("undo\n", cps);
8257             return;
8258
8259           case MachinePlaysWhite:
8260           case IcsPlayingWhite:
8261             machineWhite = TRUE;
8262             break;
8263
8264           case MachinePlaysBlack:
8265           case IcsPlayingBlack:
8266             machineWhite = FALSE;
8267             break;
8268
8269           case TwoMachinesPlay:
8270             machineWhite = (cps->twoMachinesColor[0] == 'w');
8271             break;
8272         }
8273         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8274             if (appData.debugMode) {
8275                 fprintf(debugFP,
8276                         "Ignoring move out of turn by %s, gameMode %d"
8277                         ", forwardMost %d\n",
8278                         cps->which, gameMode, forwardMostMove);
8279             }
8280             return;
8281         }
8282
8283         if(cps->alphaRank) AlphaRank(machineMove, 4);
8284         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8285                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8286             /* Machine move could not be parsed; ignore it. */
8287           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8288                     machineMove, _(cps->which));
8289             DisplayMoveError(buf1);
8290             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8291                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8292             if (gameMode == TwoMachinesPlay) {
8293               GameEnds(machineWhite ? BlackWins : WhiteWins,
8294                        buf1, GE_XBOARD);
8295             }
8296             return;
8297         }
8298
8299         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8300         /* So we have to redo legality test with true e.p. status here,  */
8301         /* to make sure an illegal e.p. capture does not slip through,   */
8302         /* to cause a forfeit on a justified illegal-move complaint      */
8303         /* of the opponent.                                              */
8304         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8305            ChessMove moveType;
8306            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8307                              fromY, fromX, toY, toX, promoChar);
8308             if(moveType == IllegalMove) {
8309               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8310                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8311                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8312                            buf1, GE_XBOARD);
8313                 return;
8314            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8315            /* [HGM] Kludge to handle engines that send FRC-style castling
8316               when they shouldn't (like TSCP-Gothic) */
8317            switch(moveType) {
8318              case WhiteASideCastleFR:
8319              case BlackASideCastleFR:
8320                toX+=2;
8321                currentMoveString[2]++;
8322                break;
8323              case WhiteHSideCastleFR:
8324              case BlackHSideCastleFR:
8325                toX--;
8326                currentMoveString[2]--;
8327                break;
8328              default: ; // nothing to do, but suppresses warning of pedantic compilers
8329            }
8330         }
8331         hintRequested = FALSE;
8332         lastHint[0] = NULLCHAR;
8333         bookRequested = FALSE;
8334         /* Program may be pondering now */
8335         cps->maybeThinking = TRUE;
8336         if (cps->sendTime == 2) cps->sendTime = 1;
8337         if (cps->offeredDraw) cps->offeredDraw--;
8338
8339         /* [AS] Save move info*/
8340         pvInfoList[ forwardMostMove ].score = programStats.score;
8341         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8342         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8343
8344         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8345
8346         /* Test suites abort the 'game' after one move */
8347         if(*appData.finger) {
8348            static FILE *f;
8349            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8350            if(!f) f = fopen(appData.finger, "w");
8351            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8352            else { DisplayFatalError("Bad output file", errno, 0); return; }
8353            free(fen);
8354            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8355         }
8356
8357         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8358         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8359             int count = 0;
8360
8361             while( count < adjudicateLossPlies ) {
8362                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8363
8364                 if( count & 1 ) {
8365                     score = -score; /* Flip score for winning side */
8366                 }
8367
8368                 if( score > adjudicateLossThreshold ) {
8369                     break;
8370                 }
8371
8372                 count++;
8373             }
8374
8375             if( count >= adjudicateLossPlies ) {
8376                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8377
8378                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8379                     "Xboard adjudication",
8380                     GE_XBOARD );
8381
8382                 return;
8383             }
8384         }
8385
8386         if(Adjudicate(cps)) {
8387             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8388             return; // [HGM] adjudicate: for all automatic game ends
8389         }
8390
8391 #if ZIPPY
8392         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8393             first.initDone) {
8394           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8395                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8396                 SendToICS("draw ");
8397                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8398           }
8399           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8400           ics_user_moved = 1;
8401           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8402                 char buf[3*MSG_SIZ];
8403
8404                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8405                         programStats.score / 100.,
8406                         programStats.depth,
8407                         programStats.time / 100.,
8408                         (unsigned int)programStats.nodes,
8409                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8410                         programStats.movelist);
8411                 SendToICS(buf);
8412 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8413           }
8414         }
8415 #endif
8416
8417         /* [AS] Clear stats for next move */
8418         ClearProgramStats();
8419         thinkOutput[0] = NULLCHAR;
8420         hiddenThinkOutputState = 0;
8421
8422         bookHit = NULL;
8423         if (gameMode == TwoMachinesPlay) {
8424             /* [HGM] relaying draw offers moved to after reception of move */
8425             /* and interpreting offer as claim if it brings draw condition */
8426             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8427                 SendToProgram("draw\n", cps->other);
8428             }
8429             if (cps->other->sendTime) {
8430                 SendTimeRemaining(cps->other,
8431                                   cps->other->twoMachinesColor[0] == 'w');
8432             }
8433             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8434             if (firstMove && !bookHit) {
8435                 firstMove = FALSE;
8436                 if (cps->other->useColors) {
8437                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8438                 }
8439                 SendToProgram("go\n", cps->other);
8440             }
8441             cps->other->maybeThinking = TRUE;
8442         }
8443
8444         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8445
8446         if (!pausing && appData.ringBellAfterMoves) {
8447             RingBell();
8448         }
8449
8450         /*
8451          * Reenable menu items that were disabled while
8452          * machine was thinking
8453          */
8454         if (gameMode != TwoMachinesPlay)
8455             SetUserThinkingEnables();
8456
8457         // [HGM] book: after book hit opponent has received move and is now in force mode
8458         // force the book reply into it, and then fake that it outputted this move by jumping
8459         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8460         if(bookHit) {
8461                 static char bookMove[MSG_SIZ]; // a bit generous?
8462
8463                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8464                 strcat(bookMove, bookHit);
8465                 message = bookMove;
8466                 cps = cps->other;
8467                 programStats.nodes = programStats.depth = programStats.time =
8468                 programStats.score = programStats.got_only_move = 0;
8469                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8470
8471                 if(cps->lastPing != cps->lastPong) {
8472                     savedMessage = message; // args for deferred call
8473                     savedState = cps;
8474                     ScheduleDelayedEvent(DeferredBookMove, 10);
8475                     return;
8476                 }
8477                 goto FakeBookMove;
8478         }
8479
8480         return;
8481     }
8482
8483     /* Set special modes for chess engines.  Later something general
8484      *  could be added here; for now there is just one kludge feature,
8485      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8486      *  when "xboard" is given as an interactive command.
8487      */
8488     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8489         cps->useSigint = FALSE;
8490         cps->useSigterm = FALSE;
8491     }
8492     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8493       ParseFeatures(message+8, cps);
8494       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8495     }
8496
8497     if (!strncmp(message, "setup ", 6) && 
8498         (!appData.testLegality || gameInfo.variant == VariantFairy || NonStandardBoardSize())
8499                                         ) { // [HGM] allow first engine to define opening position
8500       int dummy, s=6; char buf[MSG_SIZ];
8501       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8502       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8503       if(startedFromSetupPosition) return;
8504       if(sscanf(message+s, "%dx%d+%d", &dummy, &dummy, &dummy) == 3) while(message[s] && message[s++] != ' '); // for compatibility with Alien Edition
8505       ParseFEN(boards[0], &dummy, message+s);
8506       DrawPosition(TRUE, boards[0]);
8507       startedFromSetupPosition = TRUE;
8508       return;
8509     }
8510     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8511      * want this, I was asked to put it in, and obliged.
8512      */
8513     if (!strncmp(message, "setboard ", 9)) {
8514         Board initial_position;
8515
8516         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8517
8518         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8519             DisplayError(_("Bad FEN received from engine"), 0);
8520             return ;
8521         } else {
8522            Reset(TRUE, FALSE);
8523            CopyBoard(boards[0], initial_position);
8524            initialRulePlies = FENrulePlies;
8525            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8526            else gameMode = MachinePlaysBlack;
8527            DrawPosition(FALSE, boards[currentMove]);
8528         }
8529         return;
8530     }
8531
8532     /*
8533      * Look for communication commands
8534      */
8535     if (!strncmp(message, "telluser ", 9)) {
8536         if(message[9] == '\\' && message[10] == '\\')
8537             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8538         PlayTellSound();
8539         DisplayNote(message + 9);
8540         return;
8541     }
8542     if (!strncmp(message, "tellusererror ", 14)) {
8543         cps->userError = 1;
8544         if(message[14] == '\\' && message[15] == '\\')
8545             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8546         PlayTellSound();
8547         DisplayError(message + 14, 0);
8548         return;
8549     }
8550     if (!strncmp(message, "tellopponent ", 13)) {
8551       if (appData.icsActive) {
8552         if (loggedOn) {
8553           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8554           SendToICS(buf1);
8555         }
8556       } else {
8557         DisplayNote(message + 13);
8558       }
8559       return;
8560     }
8561     if (!strncmp(message, "tellothers ", 11)) {
8562       if (appData.icsActive) {
8563         if (loggedOn) {
8564           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8565           SendToICS(buf1);
8566         }
8567       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8568       return;
8569     }
8570     if (!strncmp(message, "tellall ", 8)) {
8571       if (appData.icsActive) {
8572         if (loggedOn) {
8573           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8574           SendToICS(buf1);
8575         }
8576       } else {
8577         DisplayNote(message + 8);
8578       }
8579       return;
8580     }
8581     if (strncmp(message, "warning", 7) == 0) {
8582         /* Undocumented feature, use tellusererror in new code */
8583         DisplayError(message, 0);
8584         return;
8585     }
8586     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8587         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8588         strcat(realname, " query");
8589         AskQuestion(realname, buf2, buf1, cps->pr);
8590         return;
8591     }
8592     /* Commands from the engine directly to ICS.  We don't allow these to be
8593      *  sent until we are logged on. Crafty kibitzes have been known to
8594      *  interfere with the login process.
8595      */
8596     if (loggedOn) {
8597         if (!strncmp(message, "tellics ", 8)) {
8598             SendToICS(message + 8);
8599             SendToICS("\n");
8600             return;
8601         }
8602         if (!strncmp(message, "tellicsnoalias ", 15)) {
8603             SendToICS(ics_prefix);
8604             SendToICS(message + 15);
8605             SendToICS("\n");
8606             return;
8607         }
8608         /* The following are for backward compatibility only */
8609         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8610             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8611             SendToICS(ics_prefix);
8612             SendToICS(message);
8613             SendToICS("\n");
8614             return;
8615         }
8616     }
8617     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8618         return;
8619     }
8620     if(!strncmp(message, "highlight ", 10)) {
8621         if(appData.testLegality && appData.markers) return;
8622         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8623         return;
8624     }
8625     /*
8626      * If the move is illegal, cancel it and redraw the board.
8627      * Also deal with other error cases.  Matching is rather loose
8628      * here to accommodate engines written before the spec.
8629      */
8630     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8631         strncmp(message, "Error", 5) == 0) {
8632         if (StrStr(message, "name") ||
8633             StrStr(message, "rating") || StrStr(message, "?") ||
8634             StrStr(message, "result") || StrStr(message, "board") ||
8635             StrStr(message, "bk") || StrStr(message, "computer") ||
8636             StrStr(message, "variant") || StrStr(message, "hint") ||
8637             StrStr(message, "random") || StrStr(message, "depth") ||
8638             StrStr(message, "accepted")) {
8639             return;
8640         }
8641         if (StrStr(message, "protover")) {
8642           /* Program is responding to input, so it's apparently done
8643              initializing, and this error message indicates it is
8644              protocol version 1.  So we don't need to wait any longer
8645              for it to initialize and send feature commands. */
8646           FeatureDone(cps, 1);
8647           cps->protocolVersion = 1;
8648           return;
8649         }
8650         cps->maybeThinking = FALSE;
8651
8652         if (StrStr(message, "draw")) {
8653             /* Program doesn't have "draw" command */
8654             cps->sendDrawOffers = 0;
8655             return;
8656         }
8657         if (cps->sendTime != 1 &&
8658             (StrStr(message, "time") || StrStr(message, "otim"))) {
8659           /* Program apparently doesn't have "time" or "otim" command */
8660           cps->sendTime = 0;
8661           return;
8662         }
8663         if (StrStr(message, "analyze")) {
8664             cps->analysisSupport = FALSE;
8665             cps->analyzing = FALSE;
8666 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8667             EditGameEvent(); // [HGM] try to preserve loaded game
8668             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8669             DisplayError(buf2, 0);
8670             return;
8671         }
8672         if (StrStr(message, "(no matching move)st")) {
8673           /* Special kludge for GNU Chess 4 only */
8674           cps->stKludge = TRUE;
8675           SendTimeControl(cps, movesPerSession, timeControl,
8676                           timeIncrement, appData.searchDepth,
8677                           searchTime);
8678           return;
8679         }
8680         if (StrStr(message, "(no matching move)sd")) {
8681           /* Special kludge for GNU Chess 4 only */
8682           cps->sdKludge = TRUE;
8683           SendTimeControl(cps, movesPerSession, timeControl,
8684                           timeIncrement, appData.searchDepth,
8685                           searchTime);
8686           return;
8687         }
8688         if (!StrStr(message, "llegal")) {
8689             return;
8690         }
8691         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8692             gameMode == IcsIdle) return;
8693         if (forwardMostMove <= backwardMostMove) return;
8694         if (pausing) PauseEvent();
8695       if(appData.forceIllegal) {
8696             // [HGM] illegal: machine refused move; force position after move into it
8697           SendToProgram("force\n", cps);
8698           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8699                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8700                 // when black is to move, while there might be nothing on a2 or black
8701                 // might already have the move. So send the board as if white has the move.
8702                 // But first we must change the stm of the engine, as it refused the last move
8703                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8704                 if(WhiteOnMove(forwardMostMove)) {
8705                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8706                     SendBoard(cps, forwardMostMove); // kludgeless board
8707                 } else {
8708                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8709                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8710                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8711                 }
8712           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8713             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8714                  gameMode == TwoMachinesPlay)
8715               SendToProgram("go\n", cps);
8716             return;
8717       } else
8718         if (gameMode == PlayFromGameFile) {
8719             /* Stop reading this game file */
8720             gameMode = EditGame;
8721             ModeHighlight();
8722         }
8723         /* [HGM] illegal-move claim should forfeit game when Xboard */
8724         /* only passes fully legal moves                            */
8725         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8726             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8727                                 "False illegal-move claim", GE_XBOARD );
8728             return; // do not take back move we tested as valid
8729         }
8730         currentMove = forwardMostMove-1;
8731         DisplayMove(currentMove-1); /* before DisplayMoveError */
8732         SwitchClocks(forwardMostMove-1); // [HGM] race
8733         DisplayBothClocks();
8734         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8735                 parseList[currentMove], _(cps->which));
8736         DisplayMoveError(buf1);
8737         DrawPosition(FALSE, boards[currentMove]);
8738
8739         SetUserThinkingEnables();
8740         return;
8741     }
8742     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8743         /* Program has a broken "time" command that
8744            outputs a string not ending in newline.
8745            Don't use it. */
8746         cps->sendTime = 0;
8747     }
8748
8749     /*
8750      * If chess program startup fails, exit with an error message.
8751      * Attempts to recover here are futile. [HGM] Well, we try anyway
8752      */
8753     if ((StrStr(message, "unknown host") != NULL)
8754         || (StrStr(message, "No remote directory") != NULL)
8755         || (StrStr(message, "not found") != NULL)
8756         || (StrStr(message, "No such file") != NULL)
8757         || (StrStr(message, "can't alloc") != NULL)
8758         || (StrStr(message, "Permission denied") != NULL)) {
8759
8760         cps->maybeThinking = FALSE;
8761         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8762                 _(cps->which), cps->program, cps->host, message);
8763         RemoveInputSource(cps->isr);
8764         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8765             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8766             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8767         }
8768         return;
8769     }
8770
8771     /*
8772      * Look for hint output
8773      */
8774     if (sscanf(message, "Hint: %s", buf1) == 1) {
8775         if (cps == &first && hintRequested) {
8776             hintRequested = FALSE;
8777             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8778                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8779                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8780                                     PosFlags(forwardMostMove),
8781                                     fromY, fromX, toY, toX, promoChar, buf1);
8782                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8783                 DisplayInformation(buf2);
8784             } else {
8785                 /* Hint move could not be parsed!? */
8786               snprintf(buf2, sizeof(buf2),
8787                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8788                         buf1, _(cps->which));
8789                 DisplayError(buf2, 0);
8790             }
8791         } else {
8792           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8793         }
8794         return;
8795     }
8796
8797     /*
8798      * Ignore other messages if game is not in progress
8799      */
8800     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8801         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8802
8803     /*
8804      * look for win, lose, draw, or draw offer
8805      */
8806     if (strncmp(message, "1-0", 3) == 0) {
8807         char *p, *q, *r = "";
8808         p = strchr(message, '{');
8809         if (p) {
8810             q = strchr(p, '}');
8811             if (q) {
8812                 *q = NULLCHAR;
8813                 r = p + 1;
8814             }
8815         }
8816         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8817         return;
8818     } else if (strncmp(message, "0-1", 3) == 0) {
8819         char *p, *q, *r = "";
8820         p = strchr(message, '{');
8821         if (p) {
8822             q = strchr(p, '}');
8823             if (q) {
8824                 *q = NULLCHAR;
8825                 r = p + 1;
8826             }
8827         }
8828         /* Kludge for Arasan 4.1 bug */
8829         if (strcmp(r, "Black resigns") == 0) {
8830             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8831             return;
8832         }
8833         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8834         return;
8835     } else if (strncmp(message, "1/2", 3) == 0) {
8836         char *p, *q, *r = "";
8837         p = strchr(message, '{');
8838         if (p) {
8839             q = strchr(p, '}');
8840             if (q) {
8841                 *q = NULLCHAR;
8842                 r = p + 1;
8843             }
8844         }
8845
8846         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8847         return;
8848
8849     } else if (strncmp(message, "White resign", 12) == 0) {
8850         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8851         return;
8852     } else if (strncmp(message, "Black resign", 12) == 0) {
8853         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8854         return;
8855     } else if (strncmp(message, "White matches", 13) == 0 ||
8856                strncmp(message, "Black matches", 13) == 0   ) {
8857         /* [HGM] ignore GNUShogi noises */
8858         return;
8859     } else if (strncmp(message, "White", 5) == 0 &&
8860                message[5] != '(' &&
8861                StrStr(message, "Black") == NULL) {
8862         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8863         return;
8864     } else if (strncmp(message, "Black", 5) == 0 &&
8865                message[5] != '(') {
8866         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8867         return;
8868     } else if (strcmp(message, "resign") == 0 ||
8869                strcmp(message, "computer resigns") == 0) {
8870         switch (gameMode) {
8871           case MachinePlaysBlack:
8872           case IcsPlayingBlack:
8873             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8874             break;
8875           case MachinePlaysWhite:
8876           case IcsPlayingWhite:
8877             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8878             break;
8879           case TwoMachinesPlay:
8880             if (cps->twoMachinesColor[0] == 'w')
8881               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8882             else
8883               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8884             break;
8885           default:
8886             /* can't happen */
8887             break;
8888         }
8889         return;
8890     } else if (strncmp(message, "opponent mates", 14) == 0) {
8891         switch (gameMode) {
8892           case MachinePlaysBlack:
8893           case IcsPlayingBlack:
8894             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8895             break;
8896           case MachinePlaysWhite:
8897           case IcsPlayingWhite:
8898             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8899             break;
8900           case TwoMachinesPlay:
8901             if (cps->twoMachinesColor[0] == 'w')
8902               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8903             else
8904               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8905             break;
8906           default:
8907             /* can't happen */
8908             break;
8909         }
8910         return;
8911     } else if (strncmp(message, "computer mates", 14) == 0) {
8912         switch (gameMode) {
8913           case MachinePlaysBlack:
8914           case IcsPlayingBlack:
8915             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8916             break;
8917           case MachinePlaysWhite:
8918           case IcsPlayingWhite:
8919             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8920             break;
8921           case TwoMachinesPlay:
8922             if (cps->twoMachinesColor[0] == 'w')
8923               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8924             else
8925               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8926             break;
8927           default:
8928             /* can't happen */
8929             break;
8930         }
8931         return;
8932     } else if (strncmp(message, "checkmate", 9) == 0) {
8933         if (WhiteOnMove(forwardMostMove)) {
8934             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8935         } else {
8936             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8937         }
8938         return;
8939     } else if (strstr(message, "Draw") != NULL ||
8940                strstr(message, "game is a draw") != NULL) {
8941         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8942         return;
8943     } else if (strstr(message, "offer") != NULL &&
8944                strstr(message, "draw") != NULL) {
8945 #if ZIPPY
8946         if (appData.zippyPlay && first.initDone) {
8947             /* Relay offer to ICS */
8948             SendToICS(ics_prefix);
8949             SendToICS("draw\n");
8950         }
8951 #endif
8952         cps->offeredDraw = 2; /* valid until this engine moves twice */
8953         if (gameMode == TwoMachinesPlay) {
8954             if (cps->other->offeredDraw) {
8955                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8956             /* [HGM] in two-machine mode we delay relaying draw offer      */
8957             /* until after we also have move, to see if it is really claim */
8958             }
8959         } else if (gameMode == MachinePlaysWhite ||
8960                    gameMode == MachinePlaysBlack) {
8961           if (userOfferedDraw) {
8962             DisplayInformation(_("Machine accepts your draw offer"));
8963             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8964           } else {
8965             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8966           }
8967         }
8968     }
8969
8970
8971     /*
8972      * Look for thinking output
8973      */
8974     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8975           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8976                                 ) {
8977         int plylev, mvleft, mvtot, curscore, time;
8978         char mvname[MOVE_LEN];
8979         u64 nodes; // [DM]
8980         char plyext;
8981         int ignore = FALSE;
8982         int prefixHint = FALSE;
8983         mvname[0] = NULLCHAR;
8984
8985         switch (gameMode) {
8986           case MachinePlaysBlack:
8987           case IcsPlayingBlack:
8988             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8989             break;
8990           case MachinePlaysWhite:
8991           case IcsPlayingWhite:
8992             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8993             break;
8994           case AnalyzeMode:
8995           case AnalyzeFile:
8996             break;
8997           case IcsObserving: /* [DM] icsEngineAnalyze */
8998             if (!appData.icsEngineAnalyze) ignore = TRUE;
8999             break;
9000           case TwoMachinesPlay:
9001             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9002                 ignore = TRUE;
9003             }
9004             break;
9005           default:
9006             ignore = TRUE;
9007             break;
9008         }
9009
9010         if (!ignore) {
9011             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9012             buf1[0] = NULLCHAR;
9013             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9014                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9015
9016                 if (plyext != ' ' && plyext != '\t') {
9017                     time *= 100;
9018                 }
9019
9020                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9021                 if( cps->scoreIsAbsolute &&
9022                     ( gameMode == MachinePlaysBlack ||
9023                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9024                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9025                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9026                      !WhiteOnMove(currentMove)
9027                     ) )
9028                 {
9029                     curscore = -curscore;
9030                 }
9031
9032                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9033
9034                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9035                         char buf[MSG_SIZ];
9036                         FILE *f;
9037                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9038                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9039                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9040                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9041                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9042                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9043                                 fclose(f);
9044                         } else DisplayError(_("failed writing PV"), 0);
9045                 }
9046
9047                 tempStats.depth = plylev;
9048                 tempStats.nodes = nodes;
9049                 tempStats.time = time;
9050                 tempStats.score = curscore;
9051                 tempStats.got_only_move = 0;
9052
9053                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9054                         int ticklen;
9055
9056                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9057                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9058                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9059                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9060                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9061                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9062                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9063                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9064                 }
9065
9066                 /* Buffer overflow protection */
9067                 if (pv[0] != NULLCHAR) {
9068                     if (strlen(pv) >= sizeof(tempStats.movelist)
9069                         && appData.debugMode) {
9070                         fprintf(debugFP,
9071                                 "PV is too long; using the first %u bytes.\n",
9072                                 (unsigned) sizeof(tempStats.movelist) - 1);
9073                     }
9074
9075                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9076                 } else {
9077                     sprintf(tempStats.movelist, " no PV\n");
9078                 }
9079
9080                 if (tempStats.seen_stat) {
9081                     tempStats.ok_to_send = 1;
9082                 }
9083
9084                 if (strchr(tempStats.movelist, '(') != NULL) {
9085                     tempStats.line_is_book = 1;
9086                     tempStats.nr_moves = 0;
9087                     tempStats.moves_left = 0;
9088                 } else {
9089                     tempStats.line_is_book = 0;
9090                 }
9091
9092                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9093                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9094
9095                 SendProgramStatsToFrontend( cps, &tempStats );
9096
9097                 /*
9098                     [AS] Protect the thinkOutput buffer from overflow... this
9099                     is only useful if buf1 hasn't overflowed first!
9100                 */
9101                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9102                          plylev,
9103                          (gameMode == TwoMachinesPlay ?
9104                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9105                          ((double) curscore) / 100.0,
9106                          prefixHint ? lastHint : "",
9107                          prefixHint ? " " : "" );
9108
9109                 if( buf1[0] != NULLCHAR ) {
9110                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9111
9112                     if( strlen(pv) > max_len ) {
9113                         if( appData.debugMode) {
9114                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9115                         }
9116                         pv[max_len+1] = '\0';
9117                     }
9118
9119                     strcat( thinkOutput, pv);
9120                 }
9121
9122                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9123                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9124                     DisplayMove(currentMove - 1);
9125                 }
9126                 return;
9127
9128             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9129                 /* crafty (9.25+) says "(only move) <move>"
9130                  * if there is only 1 legal move
9131                  */
9132                 sscanf(p, "(only move) %s", buf1);
9133                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9134                 sprintf(programStats.movelist, "%s (only move)", buf1);
9135                 programStats.depth = 1;
9136                 programStats.nr_moves = 1;
9137                 programStats.moves_left = 1;
9138                 programStats.nodes = 1;
9139                 programStats.time = 1;
9140                 programStats.got_only_move = 1;
9141
9142                 /* Not really, but we also use this member to
9143                    mean "line isn't going to change" (Crafty
9144                    isn't searching, so stats won't change) */
9145                 programStats.line_is_book = 1;
9146
9147                 SendProgramStatsToFrontend( cps, &programStats );
9148
9149                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9150                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9151                     DisplayMove(currentMove - 1);
9152                 }
9153                 return;
9154             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9155                               &time, &nodes, &plylev, &mvleft,
9156                               &mvtot, mvname) >= 5) {
9157                 /* The stat01: line is from Crafty (9.29+) in response
9158                    to the "." command */
9159                 programStats.seen_stat = 1;
9160                 cps->maybeThinking = TRUE;
9161
9162                 if (programStats.got_only_move || !appData.periodicUpdates)
9163                   return;
9164
9165                 programStats.depth = plylev;
9166                 programStats.time = time;
9167                 programStats.nodes = nodes;
9168                 programStats.moves_left = mvleft;
9169                 programStats.nr_moves = mvtot;
9170                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9171                 programStats.ok_to_send = 1;
9172                 programStats.movelist[0] = '\0';
9173
9174                 SendProgramStatsToFrontend( cps, &programStats );
9175
9176                 return;
9177
9178             } else if (strncmp(message,"++",2) == 0) {
9179                 /* Crafty 9.29+ outputs this */
9180                 programStats.got_fail = 2;
9181                 return;
9182
9183             } else if (strncmp(message,"--",2) == 0) {
9184                 /* Crafty 9.29+ outputs this */
9185                 programStats.got_fail = 1;
9186                 return;
9187
9188             } else if (thinkOutput[0] != NULLCHAR &&
9189                        strncmp(message, "    ", 4) == 0) {
9190                 unsigned message_len;
9191
9192                 p = message;
9193                 while (*p && *p == ' ') p++;
9194
9195                 message_len = strlen( p );
9196
9197                 /* [AS] Avoid buffer overflow */
9198                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9199                     strcat(thinkOutput, " ");
9200                     strcat(thinkOutput, p);
9201                 }
9202
9203                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9204                     strcat(programStats.movelist, " ");
9205                     strcat(programStats.movelist, p);
9206                 }
9207
9208                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9209                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9210                     DisplayMove(currentMove - 1);
9211                 }
9212                 return;
9213             }
9214         }
9215         else {
9216             buf1[0] = NULLCHAR;
9217
9218             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9219                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9220             {
9221                 ChessProgramStats cpstats;
9222
9223                 if (plyext != ' ' && plyext != '\t') {
9224                     time *= 100;
9225                 }
9226
9227                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9228                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9229                     curscore = -curscore;
9230                 }
9231
9232                 cpstats.depth = plylev;
9233                 cpstats.nodes = nodes;
9234                 cpstats.time = time;
9235                 cpstats.score = curscore;
9236                 cpstats.got_only_move = 0;
9237                 cpstats.movelist[0] = '\0';
9238
9239                 if (buf1[0] != NULLCHAR) {
9240                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9241                 }
9242
9243                 cpstats.ok_to_send = 0;
9244                 cpstats.line_is_book = 0;
9245                 cpstats.nr_moves = 0;
9246                 cpstats.moves_left = 0;
9247
9248                 SendProgramStatsToFrontend( cps, &cpstats );
9249             }
9250         }
9251     }
9252 }
9253
9254
9255 /* Parse a game score from the character string "game", and
9256    record it as the history of the current game.  The game
9257    score is NOT assumed to start from the standard position.
9258    The display is not updated in any way.
9259    */
9260 void
9261 ParseGameHistory (char *game)
9262 {
9263     ChessMove moveType;
9264     int fromX, fromY, toX, toY, boardIndex;
9265     char promoChar;
9266     char *p, *q;
9267     char buf[MSG_SIZ];
9268
9269     if (appData.debugMode)
9270       fprintf(debugFP, "Parsing game history: %s\n", game);
9271
9272     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9273     gameInfo.site = StrSave(appData.icsHost);
9274     gameInfo.date = PGNDate();
9275     gameInfo.round = StrSave("-");
9276
9277     /* Parse out names of players */
9278     while (*game == ' ') game++;
9279     p = buf;
9280     while (*game != ' ') *p++ = *game++;
9281     *p = NULLCHAR;
9282     gameInfo.white = StrSave(buf);
9283     while (*game == ' ') game++;
9284     p = buf;
9285     while (*game != ' ' && *game != '\n') *p++ = *game++;
9286     *p = NULLCHAR;
9287     gameInfo.black = StrSave(buf);
9288
9289     /* Parse moves */
9290     boardIndex = blackPlaysFirst ? 1 : 0;
9291     yynewstr(game);
9292     for (;;) {
9293         yyboardindex = boardIndex;
9294         moveType = (ChessMove) Myylex();
9295         switch (moveType) {
9296           case IllegalMove:             /* maybe suicide chess, etc. */
9297   if (appData.debugMode) {
9298     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9299     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9300     setbuf(debugFP, NULL);
9301   }
9302           case WhitePromotion:
9303           case BlackPromotion:
9304           case WhiteNonPromotion:
9305           case BlackNonPromotion:
9306           case NormalMove:
9307           case WhiteCapturesEnPassant:
9308           case BlackCapturesEnPassant:
9309           case WhiteKingSideCastle:
9310           case WhiteQueenSideCastle:
9311           case BlackKingSideCastle:
9312           case BlackQueenSideCastle:
9313           case WhiteKingSideCastleWild:
9314           case WhiteQueenSideCastleWild:
9315           case BlackKingSideCastleWild:
9316           case BlackQueenSideCastleWild:
9317           /* PUSH Fabien */
9318           case WhiteHSideCastleFR:
9319           case WhiteASideCastleFR:
9320           case BlackHSideCastleFR:
9321           case BlackASideCastleFR:
9322           /* POP Fabien */
9323             fromX = currentMoveString[0] - AAA;
9324             fromY = currentMoveString[1] - ONE;
9325             toX = currentMoveString[2] - AAA;
9326             toY = currentMoveString[3] - ONE;
9327             promoChar = currentMoveString[4];
9328             break;
9329           case WhiteDrop:
9330           case BlackDrop:
9331             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9332             fromX = moveType == WhiteDrop ?
9333               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9334             (int) CharToPiece(ToLower(currentMoveString[0]));
9335             fromY = DROP_RANK;
9336             toX = currentMoveString[2] - AAA;
9337             toY = currentMoveString[3] - ONE;
9338             promoChar = NULLCHAR;
9339             break;
9340           case AmbiguousMove:
9341             /* bug? */
9342             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9343   if (appData.debugMode) {
9344     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9345     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9346     setbuf(debugFP, NULL);
9347   }
9348             DisplayError(buf, 0);
9349             return;
9350           case ImpossibleMove:
9351             /* bug? */
9352             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9353   if (appData.debugMode) {
9354     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9355     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9356     setbuf(debugFP, NULL);
9357   }
9358             DisplayError(buf, 0);
9359             return;
9360           case EndOfFile:
9361             if (boardIndex < backwardMostMove) {
9362                 /* Oops, gap.  How did that happen? */
9363                 DisplayError(_("Gap in move list"), 0);
9364                 return;
9365             }
9366             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9367             if (boardIndex > forwardMostMove) {
9368                 forwardMostMove = boardIndex;
9369             }
9370             return;
9371           case ElapsedTime:
9372             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9373                 strcat(parseList[boardIndex-1], " ");
9374                 strcat(parseList[boardIndex-1], yy_text);
9375             }
9376             continue;
9377           case Comment:
9378           case PGNTag:
9379           case NAG:
9380           default:
9381             /* ignore */
9382             continue;
9383           case WhiteWins:
9384           case BlackWins:
9385           case GameIsDrawn:
9386           case GameUnfinished:
9387             if (gameMode == IcsExamining) {
9388                 if (boardIndex < backwardMostMove) {
9389                     /* Oops, gap.  How did that happen? */
9390                     return;
9391                 }
9392                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9393                 return;
9394             }
9395             gameInfo.result = moveType;
9396             p = strchr(yy_text, '{');
9397             if (p == NULL) p = strchr(yy_text, '(');
9398             if (p == NULL) {
9399                 p = yy_text;
9400                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9401             } else {
9402                 q = strchr(p, *p == '{' ? '}' : ')');
9403                 if (q != NULL) *q = NULLCHAR;
9404                 p++;
9405             }
9406             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9407             gameInfo.resultDetails = StrSave(p);
9408             continue;
9409         }
9410         if (boardIndex >= forwardMostMove &&
9411             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9412             backwardMostMove = blackPlaysFirst ? 1 : 0;
9413             return;
9414         }
9415         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9416                                  fromY, fromX, toY, toX, promoChar,
9417                                  parseList[boardIndex]);
9418         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9419         /* currentMoveString is set as a side-effect of yylex */
9420         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9421         strcat(moveList[boardIndex], "\n");
9422         boardIndex++;
9423         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9424         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9425           case MT_NONE:
9426           case MT_STALEMATE:
9427           default:
9428             break;
9429           case MT_CHECK:
9430             if(gameInfo.variant != VariantShogi)
9431                 strcat(parseList[boardIndex - 1], "+");
9432             break;
9433           case MT_CHECKMATE:
9434           case MT_STAINMATE:
9435             strcat(parseList[boardIndex - 1], "#");
9436             break;
9437         }
9438     }
9439 }
9440
9441
9442 /* Apply a move to the given board  */
9443 void
9444 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9445 {
9446   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9447   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9448
9449     /* [HGM] compute & store e.p. status and castling rights for new position */
9450     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9451
9452       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9453       oldEP = (signed char)board[EP_STATUS];
9454       board[EP_STATUS] = EP_NONE;
9455
9456   if (fromY == DROP_RANK) {
9457         /* must be first */
9458         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9459             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9460             return;
9461         }
9462         piece = board[toY][toX] = (ChessSquare) fromX;
9463   } else {
9464       int i;
9465
9466       if( board[toY][toX] != EmptySquare )
9467            board[EP_STATUS] = EP_CAPTURE;
9468
9469       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9470            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9471                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9472       } else
9473       if( board[fromY][fromX] == WhitePawn ) {
9474            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9475                board[EP_STATUS] = EP_PAWN_MOVE;
9476            if( toY-fromY==2) {
9477                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9478                         gameInfo.variant != VariantBerolina || toX < fromX)
9479                       board[EP_STATUS] = toX | berolina;
9480                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9481                         gameInfo.variant != VariantBerolina || toX > fromX)
9482                       board[EP_STATUS] = toX;
9483            }
9484       } else
9485       if( board[fromY][fromX] == BlackPawn ) {
9486            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9487                board[EP_STATUS] = EP_PAWN_MOVE;
9488            if( toY-fromY== -2) {
9489                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9490                         gameInfo.variant != VariantBerolina || toX < fromX)
9491                       board[EP_STATUS] = toX | berolina;
9492                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9493                         gameInfo.variant != VariantBerolina || toX > fromX)
9494                       board[EP_STATUS] = toX;
9495            }
9496        }
9497
9498        for(i=0; i<nrCastlingRights; i++) {
9499            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9500               board[CASTLING][i] == toX   && castlingRank[i] == toY
9501              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9502        }
9503
9504        if(gameInfo.variant == VariantSChess) { // update virginity
9505            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9506            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9507            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9508            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9509        }
9510
9511      if (fromX == toX && fromY == toY) return;
9512
9513      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9514      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9515      if(gameInfo.variant == VariantKnightmate)
9516          king += (int) WhiteUnicorn - (int) WhiteKing;
9517
9518     /* Code added by Tord: */
9519     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9520     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9521         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9522       board[fromY][fromX] = EmptySquare;
9523       board[toY][toX] = EmptySquare;
9524       if((toX > fromX) != (piece == WhiteRook)) {
9525         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9526       } else {
9527         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9528       }
9529     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9530                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9531       board[fromY][fromX] = EmptySquare;
9532       board[toY][toX] = EmptySquare;
9533       if((toX > fromX) != (piece == BlackRook)) {
9534         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9535       } else {
9536         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9537       }
9538     /* End of code added by Tord */
9539
9540     } else if (board[fromY][fromX] == king
9541         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9542         && toY == fromY && toX > fromX+1) {
9543         board[fromY][fromX] = EmptySquare;
9544         board[toY][toX] = king;
9545         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9546         board[fromY][BOARD_RGHT-1] = EmptySquare;
9547     } else if (board[fromY][fromX] == king
9548         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9549                && toY == fromY && toX < fromX-1) {
9550         board[fromY][fromX] = EmptySquare;
9551         board[toY][toX] = king;
9552         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9553         board[fromY][BOARD_LEFT] = EmptySquare;
9554     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9555                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9556                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9557                ) {
9558         /* white pawn promotion */
9559         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9560         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9561             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9562         board[fromY][fromX] = EmptySquare;
9563     } else if ((fromY >= BOARD_HEIGHT>>1)
9564                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9565                && (toX != fromX)
9566                && gameInfo.variant != VariantXiangqi
9567                && gameInfo.variant != VariantBerolina
9568                && (board[fromY][fromX] == WhitePawn)
9569                && (board[toY][toX] == EmptySquare)) {
9570         board[fromY][fromX] = EmptySquare;
9571         board[toY][toX] = WhitePawn;
9572         captured = board[toY - 1][toX];
9573         board[toY - 1][toX] = EmptySquare;
9574     } else if ((fromY == BOARD_HEIGHT-4)
9575                && (toX == fromX)
9576                && gameInfo.variant == VariantBerolina
9577                && (board[fromY][fromX] == WhitePawn)
9578                && (board[toY][toX] == EmptySquare)) {
9579         board[fromY][fromX] = EmptySquare;
9580         board[toY][toX] = WhitePawn;
9581         if(oldEP & EP_BEROLIN_A) {
9582                 captured = board[fromY][fromX-1];
9583                 board[fromY][fromX-1] = EmptySquare;
9584         }else{  captured = board[fromY][fromX+1];
9585                 board[fromY][fromX+1] = EmptySquare;
9586         }
9587     } else if (board[fromY][fromX] == king
9588         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9589                && toY == fromY && toX > fromX+1) {
9590         board[fromY][fromX] = EmptySquare;
9591         board[toY][toX] = king;
9592         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9593         board[fromY][BOARD_RGHT-1] = EmptySquare;
9594     } else if (board[fromY][fromX] == king
9595         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9596                && toY == fromY && toX < fromX-1) {
9597         board[fromY][fromX] = EmptySquare;
9598         board[toY][toX] = king;
9599         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9600         board[fromY][BOARD_LEFT] = EmptySquare;
9601     } else if (fromY == 7 && fromX == 3
9602                && board[fromY][fromX] == BlackKing
9603                && toY == 7 && toX == 5) {
9604         board[fromY][fromX] = EmptySquare;
9605         board[toY][toX] = BlackKing;
9606         board[fromY][7] = EmptySquare;
9607         board[toY][4] = BlackRook;
9608     } else if (fromY == 7 && fromX == 3
9609                && board[fromY][fromX] == BlackKing
9610                && toY == 7 && toX == 1) {
9611         board[fromY][fromX] = EmptySquare;
9612         board[toY][toX] = BlackKing;
9613         board[fromY][0] = EmptySquare;
9614         board[toY][2] = BlackRook;
9615     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9616                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9617                && toY < promoRank && promoChar
9618                ) {
9619         /* black pawn promotion */
9620         board[toY][toX] = CharToPiece(ToLower(promoChar));
9621         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9622             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9623         board[fromY][fromX] = EmptySquare;
9624     } else if ((fromY < BOARD_HEIGHT>>1)
9625                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9626                && (toX != fromX)
9627                && gameInfo.variant != VariantXiangqi
9628                && gameInfo.variant != VariantBerolina
9629                && (board[fromY][fromX] == BlackPawn)
9630                && (board[toY][toX] == EmptySquare)) {
9631         board[fromY][fromX] = EmptySquare;
9632         board[toY][toX] = BlackPawn;
9633         captured = board[toY + 1][toX];
9634         board[toY + 1][toX] = EmptySquare;
9635     } else if ((fromY == 3)
9636                && (toX == fromX)
9637                && gameInfo.variant == VariantBerolina
9638                && (board[fromY][fromX] == BlackPawn)
9639                && (board[toY][toX] == EmptySquare)) {
9640         board[fromY][fromX] = EmptySquare;
9641         board[toY][toX] = BlackPawn;
9642         if(oldEP & EP_BEROLIN_A) {
9643                 captured = board[fromY][fromX-1];
9644                 board[fromY][fromX-1] = EmptySquare;
9645         }else{  captured = board[fromY][fromX+1];
9646                 board[fromY][fromX+1] = EmptySquare;
9647         }
9648     } else {
9649         board[toY][toX] = board[fromY][fromX];
9650         board[fromY][fromX] = EmptySquare;
9651     }
9652   }
9653
9654     if (gameInfo.holdingsWidth != 0) {
9655
9656       /* !!A lot more code needs to be written to support holdings  */
9657       /* [HGM] OK, so I have written it. Holdings are stored in the */
9658       /* penultimate board files, so they are automaticlly stored   */
9659       /* in the game history.                                       */
9660       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9661                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9662         /* Delete from holdings, by decreasing count */
9663         /* and erasing image if necessary            */
9664         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9665         if(p < (int) BlackPawn) { /* white drop */
9666              p -= (int)WhitePawn;
9667                  p = PieceToNumber((ChessSquare)p);
9668              if(p >= gameInfo.holdingsSize) p = 0;
9669              if(--board[p][BOARD_WIDTH-2] <= 0)
9670                   board[p][BOARD_WIDTH-1] = EmptySquare;
9671              if((int)board[p][BOARD_WIDTH-2] < 0)
9672                         board[p][BOARD_WIDTH-2] = 0;
9673         } else {                  /* black drop */
9674              p -= (int)BlackPawn;
9675                  p = PieceToNumber((ChessSquare)p);
9676              if(p >= gameInfo.holdingsSize) p = 0;
9677              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9678                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9679              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9680                         board[BOARD_HEIGHT-1-p][1] = 0;
9681         }
9682       }
9683       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9684           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9685         /* [HGM] holdings: Add to holdings, if holdings exist */
9686         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9687                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9688                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9689         }
9690         p = (int) captured;
9691         if (p >= (int) BlackPawn) {
9692           p -= (int)BlackPawn;
9693           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9694                   /* in Shogi restore piece to its original  first */
9695                   captured = (ChessSquare) (DEMOTED captured);
9696                   p = DEMOTED p;
9697           }
9698           p = PieceToNumber((ChessSquare)p);
9699           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9700           board[p][BOARD_WIDTH-2]++;
9701           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9702         } else {
9703           p -= (int)WhitePawn;
9704           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9705                   captured = (ChessSquare) (DEMOTED captured);
9706                   p = DEMOTED p;
9707           }
9708           p = PieceToNumber((ChessSquare)p);
9709           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9710           board[BOARD_HEIGHT-1-p][1]++;
9711           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9712         }
9713       }
9714     } else if (gameInfo.variant == VariantAtomic) {
9715       if (captured != EmptySquare) {
9716         int y, x;
9717         for (y = toY-1; y <= toY+1; y++) {
9718           for (x = toX-1; x <= toX+1; x++) {
9719             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9720                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9721               board[y][x] = EmptySquare;
9722             }
9723           }
9724         }
9725         board[toY][toX] = EmptySquare;
9726       }
9727     }
9728     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9729         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9730     } else
9731     if(promoChar == '+') {
9732         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9733         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9734     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9735         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9736         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9737            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9738         board[toY][toX] = newPiece;
9739     }
9740     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9741                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9742         // [HGM] superchess: take promotion piece out of holdings
9743         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9744         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9745             if(!--board[k][BOARD_WIDTH-2])
9746                 board[k][BOARD_WIDTH-1] = EmptySquare;
9747         } else {
9748             if(!--board[BOARD_HEIGHT-1-k][1])
9749                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9750         }
9751     }
9752
9753 }
9754
9755 /* Updates forwardMostMove */
9756 void
9757 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9758 {
9759 //    forwardMostMove++; // [HGM] bare: moved downstream
9760
9761     (void) CoordsToAlgebraic(boards[forwardMostMove],
9762                              PosFlags(forwardMostMove),
9763                              fromY, fromX, toY, toX, promoChar,
9764                              parseList[forwardMostMove]);
9765
9766     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9767         int timeLeft; static int lastLoadFlag=0; int king, piece;
9768         piece = boards[forwardMostMove][fromY][fromX];
9769         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9770         if(gameInfo.variant == VariantKnightmate)
9771             king += (int) WhiteUnicorn - (int) WhiteKing;
9772         if(forwardMostMove == 0) {
9773             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9774                 fprintf(serverMoves, "%s;", UserName());
9775             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9776                 fprintf(serverMoves, "%s;", second.tidy);
9777             fprintf(serverMoves, "%s;", first.tidy);
9778             if(gameMode == MachinePlaysWhite)
9779                 fprintf(serverMoves, "%s;", UserName());
9780             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9781                 fprintf(serverMoves, "%s;", second.tidy);
9782         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9783         lastLoadFlag = loadFlag;
9784         // print base move
9785         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9786         // print castling suffix
9787         if( toY == fromY && piece == king ) {
9788             if(toX-fromX > 1)
9789                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9790             if(fromX-toX >1)
9791                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9792         }
9793         // e.p. suffix
9794         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9795              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9796              boards[forwardMostMove][toY][toX] == EmptySquare
9797              && fromX != toX && fromY != toY)
9798                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9799         // promotion suffix
9800         if(promoChar != NULLCHAR) {
9801             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9802                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9803                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9804             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9805         }
9806         if(!loadFlag) {
9807                 char buf[MOVE_LEN*2], *p; int len;
9808             fprintf(serverMoves, "/%d/%d",
9809                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9810             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9811             else                      timeLeft = blackTimeRemaining/1000;
9812             fprintf(serverMoves, "/%d", timeLeft);
9813                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9814                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9815                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9816                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9817             fprintf(serverMoves, "/%s", buf);
9818         }
9819         fflush(serverMoves);
9820     }
9821
9822     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9823         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9824       return;
9825     }
9826     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9827     if (commentList[forwardMostMove+1] != NULL) {
9828         free(commentList[forwardMostMove+1]);
9829         commentList[forwardMostMove+1] = NULL;
9830     }
9831     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9832     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9833     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9834     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9835     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9836     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9837     adjustedClock = FALSE;
9838     gameInfo.result = GameUnfinished;
9839     if (gameInfo.resultDetails != NULL) {
9840         free(gameInfo.resultDetails);
9841         gameInfo.resultDetails = NULL;
9842     }
9843     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9844                               moveList[forwardMostMove - 1]);
9845     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9846       case MT_NONE:
9847       case MT_STALEMATE:
9848       default:
9849         break;
9850       case MT_CHECK:
9851         if(gameInfo.variant != VariantShogi)
9852             strcat(parseList[forwardMostMove - 1], "+");
9853         break;
9854       case MT_CHECKMATE:
9855       case MT_STAINMATE:
9856         strcat(parseList[forwardMostMove - 1], "#");
9857         break;
9858     }
9859
9860 }
9861
9862 /* Updates currentMove if not pausing */
9863 void
9864 ShowMove (int fromX, int fromY, int toX, int toY)
9865 {
9866     int instant = (gameMode == PlayFromGameFile) ?
9867         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9868     if(appData.noGUI) return;
9869     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9870         if (!instant) {
9871             if (forwardMostMove == currentMove + 1) {
9872                 AnimateMove(boards[forwardMostMove - 1],
9873                             fromX, fromY, toX, toY);
9874             }
9875         }
9876         currentMove = forwardMostMove;
9877     }
9878
9879     if (instant) return;
9880
9881     DisplayMove(currentMove - 1);
9882     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9883             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9884                 SetHighlights(fromX, fromY, toX, toY);
9885             }
9886     }
9887     DrawPosition(FALSE, boards[currentMove]);
9888     DisplayBothClocks();
9889     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9890 }
9891
9892 void
9893 SendEgtPath (ChessProgramState *cps)
9894 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9895         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9896
9897         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9898
9899         while(*p) {
9900             char c, *q = name+1, *r, *s;
9901
9902             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9903             while(*p && *p != ',') *q++ = *p++;
9904             *q++ = ':'; *q = 0;
9905             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9906                 strcmp(name, ",nalimov:") == 0 ) {
9907                 // take nalimov path from the menu-changeable option first, if it is defined
9908               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9909                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9910             } else
9911             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9912                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9913                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9914                 s = r = StrStr(s, ":") + 1; // beginning of path info
9915                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9916                 c = *r; *r = 0;             // temporarily null-terminate path info
9917                     *--q = 0;               // strip of trailig ':' from name
9918                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9919                 *r = c;
9920                 SendToProgram(buf,cps);     // send egtbpath command for this format
9921             }
9922             if(*p == ',') p++; // read away comma to position for next format name
9923         }
9924 }
9925
9926 static int
9927 NonStandardBoardSize ()
9928 {
9929       /* [HGM] Awkward testing. Should really be a table */
9930       int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9931       if( gameInfo.variant == VariantXiangqi )
9932            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9933       if( gameInfo.variant == VariantShogi )
9934            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9935       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9936            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9937       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9938           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9939            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9940       if( gameInfo.variant == VariantCourier )
9941            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9942       if( gameInfo.variant == VariantSuper )
9943            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9944       if( gameInfo.variant == VariantGreat )
9945            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9946       if( gameInfo.variant == VariantSChess )
9947            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9948       if( gameInfo.variant == VariantGrand )
9949            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9950       return overruled;
9951 }
9952
9953 void
9954 InitChessProgram (ChessProgramState *cps, int setup)
9955 /* setup needed to setup FRC opening position */
9956 {
9957     char buf[MSG_SIZ], b[MSG_SIZ];
9958     if (appData.noChessProgram) return;
9959     hintRequested = FALSE;
9960     bookRequested = FALSE;
9961
9962     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9963     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9964     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9965     if(cps->memSize) { /* [HGM] memory */
9966       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9967         SendToProgram(buf, cps);
9968     }
9969     SendEgtPath(cps); /* [HGM] EGT */
9970     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9971       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9972         SendToProgram(buf, cps);
9973     }
9974
9975     SendToProgram(cps->initString, cps);
9976     if (gameInfo.variant != VariantNormal &&
9977         gameInfo.variant != VariantLoadable
9978         /* [HGM] also send variant if board size non-standard */
9979         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9980                                             ) {
9981       char *v = VariantName(gameInfo.variant);
9982       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9983         /* [HGM] in protocol 1 we have to assume all variants valid */
9984         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9985         DisplayFatalError(buf, 0, 1);
9986         return;
9987       }
9988
9989       if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
9990         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9991                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9992            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9993            if(StrStr(cps->variants, b) == NULL) {
9994                // specific sized variant not known, check if general sizing allowed
9995                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9996                    if(StrStr(cps->variants, "boardsize") == NULL) {
9997                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9998                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9999                        DisplayFatalError(buf, 0, 1);
10000                        return;
10001                    }
10002                    /* [HGM] here we really should compare with the maximum supported board size */
10003                }
10004            }
10005       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10006       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10007       SendToProgram(buf, cps);
10008     }
10009     currentlyInitializedVariant = gameInfo.variant;
10010
10011     /* [HGM] send opening position in FRC to first engine */
10012     if(setup) {
10013           SendToProgram("force\n", cps);
10014           SendBoard(cps, 0);
10015           /* engine is now in force mode! Set flag to wake it up after first move. */
10016           setboardSpoiledMachineBlack = 1;
10017     }
10018
10019     if (cps->sendICS) {
10020       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10021       SendToProgram(buf, cps);
10022     }
10023     cps->maybeThinking = FALSE;
10024     cps->offeredDraw = 0;
10025     if (!appData.icsActive) {
10026         SendTimeControl(cps, movesPerSession, timeControl,
10027                         timeIncrement, appData.searchDepth,
10028                         searchTime);
10029     }
10030     if (appData.showThinking
10031         // [HGM] thinking: four options require thinking output to be sent
10032         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10033                                 ) {
10034         SendToProgram("post\n", cps);
10035     }
10036     SendToProgram("hard\n", cps);
10037     if (!appData.ponderNextMove) {
10038         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10039            it without being sure what state we are in first.  "hard"
10040            is not a toggle, so that one is OK.
10041          */
10042         SendToProgram("easy\n", cps);
10043     }
10044     if (cps->usePing) {
10045       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10046       SendToProgram(buf, cps);
10047     }
10048     cps->initDone = TRUE;
10049     ClearEngineOutputPane(cps == &second);
10050 }
10051
10052
10053 void
10054 ResendOptions (ChessProgramState *cps)
10055 { // send the stored value of the options
10056   int i;
10057   char buf[MSG_SIZ];
10058   Option *opt = cps->option;
10059   for(i=0; i<cps->nrOptions; i++, opt++) {
10060       switch(opt->type) {
10061         case Spin:
10062         case Slider:
10063         case CheckBox:
10064             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10065           break;
10066         case ComboBox:
10067           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10068           break;
10069         default:
10070             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10071           break;
10072         case Button:
10073         case SaveButton:
10074           continue;
10075       }
10076       SendToProgram(buf, cps);
10077   }
10078 }
10079
10080 void
10081 StartChessProgram (ChessProgramState *cps)
10082 {
10083     char buf[MSG_SIZ];
10084     int err;
10085
10086     if (appData.noChessProgram) return;
10087     cps->initDone = FALSE;
10088
10089     if (strcmp(cps->host, "localhost") == 0) {
10090         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10091     } else if (*appData.remoteShell == NULLCHAR) {
10092         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10093     } else {
10094         if (*appData.remoteUser == NULLCHAR) {
10095           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10096                     cps->program);
10097         } else {
10098           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10099                     cps->host, appData.remoteUser, cps->program);
10100         }
10101         err = StartChildProcess(buf, "", &cps->pr);
10102     }
10103
10104     if (err != 0) {
10105       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10106         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10107         if(cps != &first) return;
10108         appData.noChessProgram = TRUE;
10109         ThawUI();
10110         SetNCPMode();
10111 //      DisplayFatalError(buf, err, 1);
10112 //      cps->pr = NoProc;
10113 //      cps->isr = NULL;
10114         return;
10115     }
10116
10117     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10118     if (cps->protocolVersion > 1) {
10119       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10120       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10121         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10122         cps->comboCnt = 0;  //                and values of combo boxes
10123       }
10124       SendToProgram(buf, cps);
10125       if(cps->reload) ResendOptions(cps);
10126     } else {
10127       SendToProgram("xboard\n", cps);
10128     }
10129 }
10130
10131 void
10132 TwoMachinesEventIfReady P((void))
10133 {
10134   static int curMess = 0;
10135   if (first.lastPing != first.lastPong) {
10136     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10137     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10138     return;
10139   }
10140   if (second.lastPing != second.lastPong) {
10141     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10142     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10143     return;
10144   }
10145   DisplayMessage("", ""); curMess = 0;
10146   TwoMachinesEvent();
10147 }
10148
10149 char *
10150 MakeName (char *template)
10151 {
10152     time_t clock;
10153     struct tm *tm;
10154     static char buf[MSG_SIZ];
10155     char *p = buf;
10156     int i;
10157
10158     clock = time((time_t *)NULL);
10159     tm = localtime(&clock);
10160
10161     while(*p++ = *template++) if(p[-1] == '%') {
10162         switch(*template++) {
10163           case 0:   *p = 0; return buf;
10164           case 'Y': i = tm->tm_year+1900; break;
10165           case 'y': i = tm->tm_year-100; break;
10166           case 'M': i = tm->tm_mon+1; break;
10167           case 'd': i = tm->tm_mday; break;
10168           case 'h': i = tm->tm_hour; break;
10169           case 'm': i = tm->tm_min; break;
10170           case 's': i = tm->tm_sec; break;
10171           default:  i = 0;
10172         }
10173         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10174     }
10175     return buf;
10176 }
10177
10178 int
10179 CountPlayers (char *p)
10180 {
10181     int n = 0;
10182     while(p = strchr(p, '\n')) p++, n++; // count participants
10183     return n;
10184 }
10185
10186 FILE *
10187 WriteTourneyFile (char *results, FILE *f)
10188 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10189     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10190     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10191         // create a file with tournament description
10192         fprintf(f, "-participants {%s}\n", appData.participants);
10193         fprintf(f, "-seedBase %d\n", appData.seedBase);
10194         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10195         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10196         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10197         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10198         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10199         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10200         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10201         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10202         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10203         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10204         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10205         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10206         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10207         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10208         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10209         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10210         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10211         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10212         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10213         fprintf(f, "-smpCores %d\n", appData.smpCores);
10214         if(searchTime > 0)
10215                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10216         else {
10217                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10218                 fprintf(f, "-tc %s\n", appData.timeControl);
10219                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10220         }
10221         fprintf(f, "-results \"%s\"\n", results);
10222     }
10223     return f;
10224 }
10225
10226 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10227
10228 void
10229 Substitute (char *participants, int expunge)
10230 {
10231     int i, changed, changes=0, nPlayers=0;
10232     char *p, *q, *r, buf[MSG_SIZ];
10233     if(participants == NULL) return;
10234     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10235     r = p = participants; q = appData.participants;
10236     while(*p && *p == *q) {
10237         if(*p == '\n') r = p+1, nPlayers++;
10238         p++; q++;
10239     }
10240     if(*p) { // difference
10241         while(*p && *p++ != '\n');
10242         while(*q && *q++ != '\n');
10243       changed = nPlayers;
10244         changes = 1 + (strcmp(p, q) != 0);
10245     }
10246     if(changes == 1) { // a single engine mnemonic was changed
10247         q = r; while(*q) nPlayers += (*q++ == '\n');
10248         p = buf; while(*r && (*p = *r++) != '\n') p++;
10249         *p = NULLCHAR;
10250         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10251         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10252         if(mnemonic[i]) { // The substitute is valid
10253             FILE *f;
10254             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10255                 flock(fileno(f), LOCK_EX);
10256                 ParseArgsFromFile(f);
10257                 fseek(f, 0, SEEK_SET);
10258                 FREE(appData.participants); appData.participants = participants;
10259                 if(expunge) { // erase results of replaced engine
10260                     int len = strlen(appData.results), w, b, dummy;
10261                     for(i=0; i<len; i++) {
10262                         Pairing(i, nPlayers, &w, &b, &dummy);
10263                         if((w == changed || b == changed) && appData.results[i] == '*') {
10264                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10265                             fclose(f);
10266                             return;
10267                         }
10268                     }
10269                     for(i=0; i<len; i++) {
10270                         Pairing(i, nPlayers, &w, &b, &dummy);
10271                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10272                     }
10273                 }
10274                 WriteTourneyFile(appData.results, f);
10275                 fclose(f); // release lock
10276                 return;
10277             }
10278         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10279     }
10280     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10281     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10282     free(participants);
10283     return;
10284 }
10285
10286 int
10287 CheckPlayers (char *participants)
10288 {
10289         int i;
10290         char buf[MSG_SIZ], *p;
10291         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10292         while(p = strchr(participants, '\n')) {
10293             *p = NULLCHAR;
10294             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10295             if(!mnemonic[i]) {
10296                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10297                 *p = '\n';
10298                 DisplayError(buf, 0);
10299                 return 1;
10300             }
10301             *p = '\n';
10302             participants = p + 1;
10303         }
10304         return 0;
10305 }
10306
10307 int
10308 CreateTourney (char *name)
10309 {
10310         FILE *f;
10311         if(matchMode && strcmp(name, appData.tourneyFile)) {
10312              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10313         }
10314         if(name[0] == NULLCHAR) {
10315             if(appData.participants[0])
10316                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10317             return 0;
10318         }
10319         f = fopen(name, "r");
10320         if(f) { // file exists
10321             ASSIGN(appData.tourneyFile, name);
10322             ParseArgsFromFile(f); // parse it
10323         } else {
10324             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10325             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10326                 DisplayError(_("Not enough participants"), 0);
10327                 return 0;
10328             }
10329             if(CheckPlayers(appData.participants)) return 0;
10330             ASSIGN(appData.tourneyFile, name);
10331             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10332             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10333         }
10334         fclose(f);
10335         appData.noChessProgram = FALSE;
10336         appData.clockMode = TRUE;
10337         SetGNUMode();
10338         return 1;
10339 }
10340
10341 int
10342 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10343 {
10344     char buf[MSG_SIZ], *p, *q;
10345     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10346     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10347     skip = !all && group[0]; // if group requested, we start in skip mode
10348     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10349         p = names; q = buf; header = 0;
10350         while(*p && *p != '\n') *q++ = *p++;
10351         *q = 0;
10352         if(*p == '\n') p++;
10353         if(buf[0] == '#') {
10354             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10355             depth++; // we must be entering a new group
10356             if(all) continue; // suppress printing group headers when complete list requested
10357             header = 1;
10358             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10359         }
10360         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10361         if(engineList[i]) free(engineList[i]);
10362         engineList[i] = strdup(buf);
10363         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10364         if(engineMnemonic[i]) free(engineMnemonic[i]);
10365         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10366             strcat(buf, " (");
10367             sscanf(q + 8, "%s", buf + strlen(buf));
10368             strcat(buf, ")");
10369         }
10370         engineMnemonic[i] = strdup(buf);
10371         i++;
10372     }
10373     engineList[i] = engineMnemonic[i] = NULL;
10374     return i;
10375 }
10376
10377 // following implemented as macro to avoid type limitations
10378 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10379
10380 void
10381 SwapEngines (int n)
10382 {   // swap settings for first engine and other engine (so far only some selected options)
10383     int h;
10384     char *p;
10385     if(n == 0) return;
10386     SWAP(directory, p)
10387     SWAP(chessProgram, p)
10388     SWAP(isUCI, h)
10389     SWAP(hasOwnBookUCI, h)
10390     SWAP(protocolVersion, h)
10391     SWAP(reuse, h)
10392     SWAP(scoreIsAbsolute, h)
10393     SWAP(timeOdds, h)
10394     SWAP(logo, p)
10395     SWAP(pgnName, p)
10396     SWAP(pvSAN, h)
10397     SWAP(engOptions, p)
10398     SWAP(engInitString, p)
10399     SWAP(computerString, p)
10400     SWAP(features, p)
10401     SWAP(fenOverride, p)
10402     SWAP(NPS, h)
10403     SWAP(accumulateTC, h)
10404     SWAP(host, p)
10405 }
10406
10407 int
10408 GetEngineLine (char *s, int n)
10409 {
10410     int i;
10411     char buf[MSG_SIZ];
10412     extern char *icsNames;
10413     if(!s || !*s) return 0;
10414     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10415     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10416     if(!mnemonic[i]) return 0;
10417     if(n == 11) return 1; // just testing if there was a match
10418     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10419     if(n == 1) SwapEngines(n);
10420     ParseArgsFromString(buf);
10421     if(n == 1) SwapEngines(n);
10422     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10423         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10424         ParseArgsFromString(buf);
10425     }
10426     return 1;
10427 }
10428
10429 int
10430 SetPlayer (int player, char *p)
10431 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10432     int i;
10433     char buf[MSG_SIZ], *engineName;
10434     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10435     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10436     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10437     if(mnemonic[i]) {
10438         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10439         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10440         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10441         ParseArgsFromString(buf);
10442     } else { // no engine with this nickname is installed!
10443         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10444         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10445         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10446         ModeHighlight();
10447         DisplayError(buf, 0);
10448         return 0;
10449     }
10450     free(engineName);
10451     return i;
10452 }
10453
10454 char *recentEngines;
10455
10456 void
10457 RecentEngineEvent (int nr)
10458 {
10459     int n;
10460 //    SwapEngines(1); // bump first to second
10461 //    ReplaceEngine(&second, 1); // and load it there
10462     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10463     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10464     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10465         ReplaceEngine(&first, 0);
10466         FloatToFront(&appData.recentEngineList, command[n]);
10467     }
10468 }
10469
10470 int
10471 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10472 {   // determine players from game number
10473     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10474
10475     if(appData.tourneyType == 0) {
10476         roundsPerCycle = (nPlayers - 1) | 1;
10477         pairingsPerRound = nPlayers / 2;
10478     } else if(appData.tourneyType > 0) {
10479         roundsPerCycle = nPlayers - appData.tourneyType;
10480         pairingsPerRound = appData.tourneyType;
10481     }
10482     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10483     gamesPerCycle = gamesPerRound * roundsPerCycle;
10484     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10485     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10486     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10487     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10488     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10489     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10490
10491     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10492     if(appData.roundSync) *syncInterval = gamesPerRound;
10493
10494     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10495
10496     if(appData.tourneyType == 0) {
10497         if(curPairing == (nPlayers-1)/2 ) {
10498             *whitePlayer = curRound;
10499             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10500         } else {
10501             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10502             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10503             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10504             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10505         }
10506     } else if(appData.tourneyType > 1) {
10507         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10508         *whitePlayer = curRound + appData.tourneyType;
10509     } else if(appData.tourneyType > 0) {
10510         *whitePlayer = curPairing;
10511         *blackPlayer = curRound + appData.tourneyType;
10512     }
10513
10514     // take care of white/black alternation per round.
10515     // For cycles and games this is already taken care of by default, derived from matchGame!
10516     return curRound & 1;
10517 }
10518
10519 int
10520 NextTourneyGame (int nr, int *swapColors)
10521 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10522     char *p, *q;
10523     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10524     FILE *tf;
10525     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10526     tf = fopen(appData.tourneyFile, "r");
10527     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10528     ParseArgsFromFile(tf); fclose(tf);
10529     InitTimeControls(); // TC might be altered from tourney file
10530
10531     nPlayers = CountPlayers(appData.participants); // count participants
10532     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10533     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10534
10535     if(syncInterval) {
10536         p = q = appData.results;
10537         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10538         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10539             DisplayMessage(_("Waiting for other game(s)"),"");
10540             waitingForGame = TRUE;
10541             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10542             return 0;
10543         }
10544         waitingForGame = FALSE;
10545     }
10546
10547     if(appData.tourneyType < 0) {
10548         if(nr>=0 && !pairingReceived) {
10549             char buf[1<<16];
10550             if(pairing.pr == NoProc) {
10551                 if(!appData.pairingEngine[0]) {
10552                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10553                     return 0;
10554                 }
10555                 StartChessProgram(&pairing); // starts the pairing engine
10556             }
10557             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10558             SendToProgram(buf, &pairing);
10559             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10560             SendToProgram(buf, &pairing);
10561             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10562         }
10563         pairingReceived = 0;                              // ... so we continue here
10564         *swapColors = 0;
10565         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10566         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10567         matchGame = 1; roundNr = nr / syncInterval + 1;
10568     }
10569
10570     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10571
10572     // redefine engines, engine dir, etc.
10573     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10574     if(first.pr == NoProc) {
10575       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10576       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10577     }
10578     if(second.pr == NoProc) {
10579       SwapEngines(1);
10580       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10581       SwapEngines(1);         // and make that valid for second engine by swapping
10582       InitEngine(&second, 1);
10583     }
10584     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10585     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10586     return OK;
10587 }
10588
10589 void
10590 NextMatchGame ()
10591 {   // performs game initialization that does not invoke engines, and then tries to start the game
10592     int res, firstWhite, swapColors = 0;
10593     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10594     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
10595         char buf[MSG_SIZ];
10596         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10597         if(strcmp(buf, currentDebugFile)) { // name has changed
10598             FILE *f = fopen(buf, "w");
10599             if(f) { // if opening the new file failed, just keep using the old one
10600                 ASSIGN(currentDebugFile, buf);
10601                 fclose(debugFP);
10602                 debugFP = f;
10603             }
10604             if(appData.serverFileName) {
10605                 if(serverFP) fclose(serverFP);
10606                 serverFP = fopen(appData.serverFileName, "w");
10607                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10608                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10609             }
10610         }
10611     }
10612     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10613     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10614     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10615     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10616     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10617     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10618     Reset(FALSE, first.pr != NoProc);
10619     res = LoadGameOrPosition(matchGame); // setup game
10620     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10621     if(!res) return; // abort when bad game/pos file
10622     TwoMachinesEvent();
10623 }
10624
10625 void
10626 UserAdjudicationEvent (int result)
10627 {
10628     ChessMove gameResult = GameIsDrawn;
10629
10630     if( result > 0 ) {
10631         gameResult = WhiteWins;
10632     }
10633     else if( result < 0 ) {
10634         gameResult = BlackWins;
10635     }
10636
10637     if( gameMode == TwoMachinesPlay ) {
10638         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10639     }
10640 }
10641
10642
10643 // [HGM] save: calculate checksum of game to make games easily identifiable
10644 int
10645 StringCheckSum (char *s)
10646 {
10647         int i = 0;
10648         if(s==NULL) return 0;
10649         while(*s) i = i*259 + *s++;
10650         return i;
10651 }
10652
10653 int
10654 GameCheckSum ()
10655 {
10656         int i, sum=0;
10657         for(i=backwardMostMove; i<forwardMostMove; i++) {
10658                 sum += pvInfoList[i].depth;
10659                 sum += StringCheckSum(parseList[i]);
10660                 sum += StringCheckSum(commentList[i]);
10661                 sum *= 261;
10662         }
10663         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10664         return sum + StringCheckSum(commentList[i]);
10665 } // end of save patch
10666
10667 void
10668 GameEnds (ChessMove result, char *resultDetails, int whosays)
10669 {
10670     GameMode nextGameMode;
10671     int isIcsGame;
10672     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10673
10674     if(endingGame) return; /* [HGM] crash: forbid recursion */
10675     endingGame = 1;
10676     if(twoBoards) { // [HGM] dual: switch back to one board
10677         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10678         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10679     }
10680     if (appData.debugMode) {
10681       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10682               result, resultDetails ? resultDetails : "(null)", whosays);
10683     }
10684
10685     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10686
10687     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10688
10689     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10690         /* If we are playing on ICS, the server decides when the
10691            game is over, but the engine can offer to draw, claim
10692            a draw, or resign.
10693          */
10694 #if ZIPPY
10695         if (appData.zippyPlay && first.initDone) {
10696             if (result == GameIsDrawn) {
10697                 /* In case draw still needs to be claimed */
10698                 SendToICS(ics_prefix);
10699                 SendToICS("draw\n");
10700             } else if (StrCaseStr(resultDetails, "resign")) {
10701                 SendToICS(ics_prefix);
10702                 SendToICS("resign\n");
10703             }
10704         }
10705 #endif
10706         endingGame = 0; /* [HGM] crash */
10707         return;
10708     }
10709
10710     /* If we're loading the game from a file, stop */
10711     if (whosays == GE_FILE) {
10712       (void) StopLoadGameTimer();
10713       gameFileFP = NULL;
10714     }
10715
10716     /* Cancel draw offers */
10717     first.offeredDraw = second.offeredDraw = 0;
10718
10719     /* If this is an ICS game, only ICS can really say it's done;
10720        if not, anyone can. */
10721     isIcsGame = (gameMode == IcsPlayingWhite ||
10722                  gameMode == IcsPlayingBlack ||
10723                  gameMode == IcsObserving    ||
10724                  gameMode == IcsExamining);
10725
10726     if (!isIcsGame || whosays == GE_ICS) {
10727         /* OK -- not an ICS game, or ICS said it was done */
10728         StopClocks();
10729         if (!isIcsGame && !appData.noChessProgram)
10730           SetUserThinkingEnables();
10731
10732         /* [HGM] if a machine claims the game end we verify this claim */
10733         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10734             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10735                 char claimer;
10736                 ChessMove trueResult = (ChessMove) -1;
10737
10738                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10739                                             first.twoMachinesColor[0] :
10740                                             second.twoMachinesColor[0] ;
10741
10742                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10743                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10744                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10745                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10746                 } else
10747                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10748                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10749                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10750                 } else
10751                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10752                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10753                 }
10754
10755                 // now verify win claims, but not in drop games, as we don't understand those yet
10756                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10757                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10758                     (result == WhiteWins && claimer == 'w' ||
10759                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10760                       if (appData.debugMode) {
10761                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10762                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10763                       }
10764                       if(result != trueResult) {
10765                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10766                               result = claimer == 'w' ? BlackWins : WhiteWins;
10767                               resultDetails = buf;
10768                       }
10769                 } else
10770                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10771                     && (forwardMostMove <= backwardMostMove ||
10772                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10773                         (claimer=='b')==(forwardMostMove&1))
10774                                                                                   ) {
10775                       /* [HGM] verify: draws that were not flagged are false claims */
10776                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10777                       result = claimer == 'w' ? BlackWins : WhiteWins;
10778                       resultDetails = buf;
10779                 }
10780                 /* (Claiming a loss is accepted no questions asked!) */
10781             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10782                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10783                 result = GameUnfinished;
10784                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10785             }
10786             /* [HGM] bare: don't allow bare King to win */
10787             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10788                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10789                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10790                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10791                && result != GameIsDrawn)
10792             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10793                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10794                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10795                         if(p >= 0 && p <= (int)WhiteKing) k++;
10796                 }
10797                 if (appData.debugMode) {
10798                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10799                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10800                 }
10801                 if(k <= 1) {
10802                         result = GameIsDrawn;
10803                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10804                         resultDetails = buf;
10805                 }
10806             }
10807         }
10808
10809
10810         if(serverMoves != NULL && !loadFlag) { char c = '=';
10811             if(result==WhiteWins) c = '+';
10812             if(result==BlackWins) c = '-';
10813             if(resultDetails != NULL)
10814                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10815         }
10816         if (resultDetails != NULL) {
10817             gameInfo.result = result;
10818             gameInfo.resultDetails = StrSave(resultDetails);
10819
10820             /* display last move only if game was not loaded from file */
10821             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10822                 DisplayMove(currentMove - 1);
10823
10824             if (forwardMostMove != 0) {
10825                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10826                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10827                                                                 ) {
10828                     if (*appData.saveGameFile != NULLCHAR) {
10829                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10830                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10831                         else
10832                         SaveGameToFile(appData.saveGameFile, TRUE);
10833                     } else if (appData.autoSaveGames) {
10834                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
10835                     }
10836                     if (*appData.savePositionFile != NULLCHAR) {
10837                         SavePositionToFile(appData.savePositionFile);
10838                     }
10839                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10840                 }
10841             }
10842
10843             /* Tell program how game ended in case it is learning */
10844             /* [HGM] Moved this to after saving the PGN, just in case */
10845             /* engine died and we got here through time loss. In that */
10846             /* case we will get a fatal error writing the pipe, which */
10847             /* would otherwise lose us the PGN.                       */
10848             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10849             /* output during GameEnds should never be fatal anymore   */
10850             if (gameMode == MachinePlaysWhite ||
10851                 gameMode == MachinePlaysBlack ||
10852                 gameMode == TwoMachinesPlay ||
10853                 gameMode == IcsPlayingWhite ||
10854                 gameMode == IcsPlayingBlack ||
10855                 gameMode == BeginningOfGame) {
10856                 char buf[MSG_SIZ];
10857                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10858                         resultDetails);
10859                 if (first.pr != NoProc) {
10860                     SendToProgram(buf, &first);
10861                 }
10862                 if (second.pr != NoProc &&
10863                     gameMode == TwoMachinesPlay) {
10864                     SendToProgram(buf, &second);
10865                 }
10866             }
10867         }
10868
10869         if (appData.icsActive) {
10870             if (appData.quietPlay &&
10871                 (gameMode == IcsPlayingWhite ||
10872                  gameMode == IcsPlayingBlack)) {
10873                 SendToICS(ics_prefix);
10874                 SendToICS("set shout 1\n");
10875             }
10876             nextGameMode = IcsIdle;
10877             ics_user_moved = FALSE;
10878             /* clean up premove.  It's ugly when the game has ended and the
10879              * premove highlights are still on the board.
10880              */
10881             if (gotPremove) {
10882               gotPremove = FALSE;
10883               ClearPremoveHighlights();
10884               DrawPosition(FALSE, boards[currentMove]);
10885             }
10886             if (whosays == GE_ICS) {
10887                 switch (result) {
10888                 case WhiteWins:
10889                     if (gameMode == IcsPlayingWhite)
10890                         PlayIcsWinSound();
10891                     else if(gameMode == IcsPlayingBlack)
10892                         PlayIcsLossSound();
10893                     break;
10894                 case BlackWins:
10895                     if (gameMode == IcsPlayingBlack)
10896                         PlayIcsWinSound();
10897                     else if(gameMode == IcsPlayingWhite)
10898                         PlayIcsLossSound();
10899                     break;
10900                 case GameIsDrawn:
10901                     PlayIcsDrawSound();
10902                     break;
10903                 default:
10904                     PlayIcsUnfinishedSound();
10905                 }
10906             }
10907             if(appData.quitNext) { ExitEvent(0); return; }
10908         } else if (gameMode == EditGame ||
10909                    gameMode == PlayFromGameFile ||
10910                    gameMode == AnalyzeMode ||
10911                    gameMode == AnalyzeFile) {
10912             nextGameMode = gameMode;
10913         } else {
10914             nextGameMode = EndOfGame;
10915         }
10916         pausing = FALSE;
10917         ModeHighlight();
10918     } else {
10919         nextGameMode = gameMode;
10920     }
10921
10922     if (appData.noChessProgram) {
10923         gameMode = nextGameMode;
10924         ModeHighlight();
10925         endingGame = 0; /* [HGM] crash */
10926         return;
10927     }
10928
10929     if (first.reuse) {
10930         /* Put first chess program into idle state */
10931         if (first.pr != NoProc &&
10932             (gameMode == MachinePlaysWhite ||
10933              gameMode == MachinePlaysBlack ||
10934              gameMode == TwoMachinesPlay ||
10935              gameMode == IcsPlayingWhite ||
10936              gameMode == IcsPlayingBlack ||
10937              gameMode == BeginningOfGame)) {
10938             SendToProgram("force\n", &first);
10939             if (first.usePing) {
10940               char buf[MSG_SIZ];
10941               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10942               SendToProgram(buf, &first);
10943             }
10944         }
10945     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10946         /* Kill off first chess program */
10947         if (first.isr != NULL)
10948           RemoveInputSource(first.isr);
10949         first.isr = NULL;
10950
10951         if (first.pr != NoProc) {
10952             ExitAnalyzeMode();
10953             DoSleep( appData.delayBeforeQuit );
10954             SendToProgram("quit\n", &first);
10955             DoSleep( appData.delayAfterQuit );
10956             DestroyChildProcess(first.pr, first.useSigterm);
10957             first.reload = TRUE;
10958         }
10959         first.pr = NoProc;
10960     }
10961     if (second.reuse) {
10962         /* Put second chess program into idle state */
10963         if (second.pr != NoProc &&
10964             gameMode == TwoMachinesPlay) {
10965             SendToProgram("force\n", &second);
10966             if (second.usePing) {
10967               char buf[MSG_SIZ];
10968               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10969               SendToProgram(buf, &second);
10970             }
10971         }
10972     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10973         /* Kill off second chess program */
10974         if (second.isr != NULL)
10975           RemoveInputSource(second.isr);
10976         second.isr = NULL;
10977
10978         if (second.pr != NoProc) {
10979             DoSleep( appData.delayBeforeQuit );
10980             SendToProgram("quit\n", &second);
10981             DoSleep( appData.delayAfterQuit );
10982             DestroyChildProcess(second.pr, second.useSigterm);
10983             second.reload = TRUE;
10984         }
10985         second.pr = NoProc;
10986     }
10987
10988     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
10989         char resChar = '=';
10990         switch (result) {
10991         case WhiteWins:
10992           resChar = '+';
10993           if (first.twoMachinesColor[0] == 'w') {
10994             first.matchWins++;
10995           } else {
10996             second.matchWins++;
10997           }
10998           break;
10999         case BlackWins:
11000           resChar = '-';
11001           if (first.twoMachinesColor[0] == 'b') {
11002             first.matchWins++;
11003           } else {
11004             second.matchWins++;
11005           }
11006           break;
11007         case GameUnfinished:
11008           resChar = ' ';
11009         default:
11010           break;
11011         }
11012
11013         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11014         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11015             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11016             ReserveGame(nextGame, resChar); // sets nextGame
11017             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11018             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11019         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11020
11021         if (nextGame <= appData.matchGames && !abortMatch) {
11022             gameMode = nextGameMode;
11023             matchGame = nextGame; // this will be overruled in tourney mode!
11024             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11025             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11026             endingGame = 0; /* [HGM] crash */
11027             return;
11028         } else {
11029             gameMode = nextGameMode;
11030             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11031                      first.tidy, second.tidy,
11032                      first.matchWins, second.matchWins,
11033                      appData.matchGames - (first.matchWins + second.matchWins));
11034             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11035             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11036             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11037             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11038                 first.twoMachinesColor = "black\n";
11039                 second.twoMachinesColor = "white\n";
11040             } else {
11041                 first.twoMachinesColor = "white\n";
11042                 second.twoMachinesColor = "black\n";
11043             }
11044         }
11045     }
11046     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11047         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11048       ExitAnalyzeMode();
11049     gameMode = nextGameMode;
11050     ModeHighlight();
11051     endingGame = 0;  /* [HGM] crash */
11052     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11053         if(matchMode == TRUE) { // match through command line: exit with or without popup
11054             if(ranking) {
11055                 ToNrEvent(forwardMostMove);
11056                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11057                 else ExitEvent(0);
11058             } else DisplayFatalError(buf, 0, 0);
11059         } else { // match through menu; just stop, with or without popup
11060             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11061             ModeHighlight();
11062             if(ranking){
11063                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11064             } else DisplayNote(buf);
11065       }
11066       if(ranking) free(ranking);
11067     }
11068 }
11069
11070 /* Assumes program was just initialized (initString sent).
11071    Leaves program in force mode. */
11072 void
11073 FeedMovesToProgram (ChessProgramState *cps, int upto)
11074 {
11075     int i;
11076
11077     if (appData.debugMode)
11078       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11079               startedFromSetupPosition ? "position and " : "",
11080               backwardMostMove, upto, cps->which);
11081     if(currentlyInitializedVariant != gameInfo.variant) {
11082       char buf[MSG_SIZ];
11083         // [HGM] variantswitch: make engine aware of new variant
11084         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11085                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11086         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11087         SendToProgram(buf, cps);
11088         currentlyInitializedVariant = gameInfo.variant;
11089     }
11090     SendToProgram("force\n", cps);
11091     if (startedFromSetupPosition) {
11092         SendBoard(cps, backwardMostMove);
11093     if (appData.debugMode) {
11094         fprintf(debugFP, "feedMoves\n");
11095     }
11096     }
11097     for (i = backwardMostMove; i < upto; i++) {
11098         SendMoveToProgram(i, cps);
11099     }
11100 }
11101
11102
11103 int
11104 ResurrectChessProgram ()
11105 {
11106      /* The chess program may have exited.
11107         If so, restart it and feed it all the moves made so far. */
11108     static int doInit = 0;
11109
11110     if (appData.noChessProgram) return 1;
11111
11112     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11113         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11114         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11115         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11116     } else {
11117         if (first.pr != NoProc) return 1;
11118         StartChessProgram(&first);
11119     }
11120     InitChessProgram(&first, FALSE);
11121     FeedMovesToProgram(&first, currentMove);
11122
11123     if (!first.sendTime) {
11124         /* can't tell gnuchess what its clock should read,
11125            so we bow to its notion. */
11126         ResetClocks();
11127         timeRemaining[0][currentMove] = whiteTimeRemaining;
11128         timeRemaining[1][currentMove] = blackTimeRemaining;
11129     }
11130
11131     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11132                 appData.icsEngineAnalyze) && first.analysisSupport) {
11133       SendToProgram("analyze\n", &first);
11134       first.analyzing = TRUE;
11135     }
11136     return 1;
11137 }
11138
11139 /*
11140  * Button procedures
11141  */
11142 void
11143 Reset (int redraw, int init)
11144 {
11145     int i;
11146
11147     if (appData.debugMode) {
11148         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11149                 redraw, init, gameMode);
11150     }
11151     CleanupTail(); // [HGM] vari: delete any stored variations
11152     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11153     pausing = pauseExamInvalid = FALSE;
11154     startedFromSetupPosition = blackPlaysFirst = FALSE;
11155     firstMove = TRUE;
11156     whiteFlag = blackFlag = FALSE;
11157     userOfferedDraw = FALSE;
11158     hintRequested = bookRequested = FALSE;
11159     first.maybeThinking = FALSE;
11160     second.maybeThinking = FALSE;
11161     first.bookSuspend = FALSE; // [HGM] book
11162     second.bookSuspend = FALSE;
11163     thinkOutput[0] = NULLCHAR;
11164     lastHint[0] = NULLCHAR;
11165     ClearGameInfo(&gameInfo);
11166     gameInfo.variant = StringToVariant(appData.variant);
11167     ics_user_moved = ics_clock_paused = FALSE;
11168     ics_getting_history = H_FALSE;
11169     ics_gamenum = -1;
11170     white_holding[0] = black_holding[0] = NULLCHAR;
11171     ClearProgramStats();
11172     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11173
11174     ResetFrontEnd();
11175     ClearHighlights();
11176     flipView = appData.flipView;
11177     ClearPremoveHighlights();
11178     gotPremove = FALSE;
11179     alarmSounded = FALSE;
11180
11181     GameEnds(EndOfFile, NULL, GE_PLAYER);
11182     if(appData.serverMovesName != NULL) {
11183         /* [HGM] prepare to make moves file for broadcasting */
11184         clock_t t = clock();
11185         if(serverMoves != NULL) fclose(serverMoves);
11186         serverMoves = fopen(appData.serverMovesName, "r");
11187         if(serverMoves != NULL) {
11188             fclose(serverMoves);
11189             /* delay 15 sec before overwriting, so all clients can see end */
11190             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11191         }
11192         serverMoves = fopen(appData.serverMovesName, "w");
11193     }
11194
11195     ExitAnalyzeMode();
11196     gameMode = BeginningOfGame;
11197     ModeHighlight();
11198     if(appData.icsActive) gameInfo.variant = VariantNormal;
11199     currentMove = forwardMostMove = backwardMostMove = 0;
11200     MarkTargetSquares(1);
11201     InitPosition(redraw);
11202     for (i = 0; i < MAX_MOVES; i++) {
11203         if (commentList[i] != NULL) {
11204             free(commentList[i]);
11205             commentList[i] = NULL;
11206         }
11207     }
11208     ResetClocks();
11209     timeRemaining[0][0] = whiteTimeRemaining;
11210     timeRemaining[1][0] = blackTimeRemaining;
11211
11212     if (first.pr == NoProc) {
11213         StartChessProgram(&first);
11214     }
11215     if (init) {
11216             InitChessProgram(&first, startedFromSetupPosition);
11217     }
11218     DisplayTitle("");
11219     DisplayMessage("", "");
11220     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11221     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11222     ClearMap();        // [HGM] exclude: invalidate map
11223 }
11224
11225 void
11226 AutoPlayGameLoop ()
11227 {
11228     for (;;) {
11229         if (!AutoPlayOneMove())
11230           return;
11231         if (matchMode || appData.timeDelay == 0)
11232           continue;
11233         if (appData.timeDelay < 0)
11234           return;
11235         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11236         break;
11237     }
11238 }
11239
11240 void
11241 AnalyzeNextGame()
11242 {
11243     ReloadGame(1); // next game
11244 }
11245
11246 int
11247 AutoPlayOneMove ()
11248 {
11249     int fromX, fromY, toX, toY;
11250
11251     if (appData.debugMode) {
11252       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11253     }
11254
11255     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11256       return FALSE;
11257
11258     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11259       pvInfoList[currentMove].depth = programStats.depth;
11260       pvInfoList[currentMove].score = programStats.score;
11261       pvInfoList[currentMove].time  = 0;
11262       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11263       else { // append analysis of final position as comment
11264         char buf[MSG_SIZ];
11265         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11266         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11267       }
11268       programStats.depth = 0;
11269     }
11270
11271     if (currentMove >= forwardMostMove) {
11272       if(gameMode == AnalyzeFile) {
11273           if(appData.loadGameIndex == -1) {
11274             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11275           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11276           } else {
11277           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11278         }
11279       }
11280 //      gameMode = EndOfGame;
11281 //      ModeHighlight();
11282
11283       /* [AS] Clear current move marker at the end of a game */
11284       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11285
11286       return FALSE;
11287     }
11288
11289     toX = moveList[currentMove][2] - AAA;
11290     toY = moveList[currentMove][3] - ONE;
11291
11292     if (moveList[currentMove][1] == '@') {
11293         if (appData.highlightLastMove) {
11294             SetHighlights(-1, -1, toX, toY);
11295         }
11296     } else {
11297         fromX = moveList[currentMove][0] - AAA;
11298         fromY = moveList[currentMove][1] - ONE;
11299
11300         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11301
11302         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11303
11304         if (appData.highlightLastMove) {
11305             SetHighlights(fromX, fromY, toX, toY);
11306         }
11307     }
11308     DisplayMove(currentMove);
11309     SendMoveToProgram(currentMove++, &first);
11310     DisplayBothClocks();
11311     DrawPosition(FALSE, boards[currentMove]);
11312     // [HGM] PV info: always display, routine tests if empty
11313     DisplayComment(currentMove - 1, commentList[currentMove]);
11314     return TRUE;
11315 }
11316
11317
11318 int
11319 LoadGameOneMove (ChessMove readAhead)
11320 {
11321     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11322     char promoChar = NULLCHAR;
11323     ChessMove moveType;
11324     char move[MSG_SIZ];
11325     char *p, *q;
11326
11327     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11328         gameMode != AnalyzeMode && gameMode != Training) {
11329         gameFileFP = NULL;
11330         return FALSE;
11331     }
11332
11333     yyboardindex = forwardMostMove;
11334     if (readAhead != EndOfFile) {
11335       moveType = readAhead;
11336     } else {
11337       if (gameFileFP == NULL)
11338           return FALSE;
11339       moveType = (ChessMove) Myylex();
11340     }
11341
11342     done = FALSE;
11343     switch (moveType) {
11344       case Comment:
11345         if (appData.debugMode)
11346           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11347         p = yy_text;
11348
11349         /* append the comment but don't display it */
11350         AppendComment(currentMove, p, FALSE);
11351         return TRUE;
11352
11353       case WhiteCapturesEnPassant:
11354       case BlackCapturesEnPassant:
11355       case WhitePromotion:
11356       case BlackPromotion:
11357       case WhiteNonPromotion:
11358       case BlackNonPromotion:
11359       case NormalMove:
11360       case WhiteKingSideCastle:
11361       case WhiteQueenSideCastle:
11362       case BlackKingSideCastle:
11363       case BlackQueenSideCastle:
11364       case WhiteKingSideCastleWild:
11365       case WhiteQueenSideCastleWild:
11366       case BlackKingSideCastleWild:
11367       case BlackQueenSideCastleWild:
11368       /* PUSH Fabien */
11369       case WhiteHSideCastleFR:
11370       case WhiteASideCastleFR:
11371       case BlackHSideCastleFR:
11372       case BlackASideCastleFR:
11373       /* POP Fabien */
11374         if (appData.debugMode)
11375           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11376         fromX = currentMoveString[0] - AAA;
11377         fromY = currentMoveString[1] - ONE;
11378         toX = currentMoveString[2] - AAA;
11379         toY = currentMoveString[3] - ONE;
11380         promoChar = currentMoveString[4];
11381         break;
11382
11383       case WhiteDrop:
11384       case BlackDrop:
11385         if (appData.debugMode)
11386           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11387         fromX = moveType == WhiteDrop ?
11388           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11389         (int) CharToPiece(ToLower(currentMoveString[0]));
11390         fromY = DROP_RANK;
11391         toX = currentMoveString[2] - AAA;
11392         toY = currentMoveString[3] - ONE;
11393         break;
11394
11395       case WhiteWins:
11396       case BlackWins:
11397       case GameIsDrawn:
11398       case GameUnfinished:
11399         if (appData.debugMode)
11400           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11401         p = strchr(yy_text, '{');
11402         if (p == NULL) p = strchr(yy_text, '(');
11403         if (p == NULL) {
11404             p = yy_text;
11405             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11406         } else {
11407             q = strchr(p, *p == '{' ? '}' : ')');
11408             if (q != NULL) *q = NULLCHAR;
11409             p++;
11410         }
11411         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11412         GameEnds(moveType, p, GE_FILE);
11413         done = TRUE;
11414         if (cmailMsgLoaded) {
11415             ClearHighlights();
11416             flipView = WhiteOnMove(currentMove);
11417             if (moveType == GameUnfinished) flipView = !flipView;
11418             if (appData.debugMode)
11419               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11420         }
11421         break;
11422
11423       case EndOfFile:
11424         if (appData.debugMode)
11425           fprintf(debugFP, "Parser hit end of file\n");
11426         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11427           case MT_NONE:
11428           case MT_CHECK:
11429             break;
11430           case MT_CHECKMATE:
11431           case MT_STAINMATE:
11432             if (WhiteOnMove(currentMove)) {
11433                 GameEnds(BlackWins, "Black mates", GE_FILE);
11434             } else {
11435                 GameEnds(WhiteWins, "White mates", GE_FILE);
11436             }
11437             break;
11438           case MT_STALEMATE:
11439             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11440             break;
11441         }
11442         done = TRUE;
11443         break;
11444
11445       case MoveNumberOne:
11446         if (lastLoadGameStart == GNUChessGame) {
11447             /* GNUChessGames have numbers, but they aren't move numbers */
11448             if (appData.debugMode)
11449               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11450                       yy_text, (int) moveType);
11451             return LoadGameOneMove(EndOfFile); /* tail recursion */
11452         }
11453         /* else fall thru */
11454
11455       case XBoardGame:
11456       case GNUChessGame:
11457       case PGNTag:
11458         /* Reached start of next game in file */
11459         if (appData.debugMode)
11460           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11461         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11462           case MT_NONE:
11463           case MT_CHECK:
11464             break;
11465           case MT_CHECKMATE:
11466           case MT_STAINMATE:
11467             if (WhiteOnMove(currentMove)) {
11468                 GameEnds(BlackWins, "Black mates", GE_FILE);
11469             } else {
11470                 GameEnds(WhiteWins, "White mates", GE_FILE);
11471             }
11472             break;
11473           case MT_STALEMATE:
11474             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11475             break;
11476         }
11477         done = TRUE;
11478         break;
11479
11480       case PositionDiagram:     /* should not happen; ignore */
11481       case ElapsedTime:         /* ignore */
11482       case NAG:                 /* ignore */
11483         if (appData.debugMode)
11484           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11485                   yy_text, (int) moveType);
11486         return LoadGameOneMove(EndOfFile); /* tail recursion */
11487
11488       case IllegalMove:
11489         if (appData.testLegality) {
11490             if (appData.debugMode)
11491               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11492             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11493                     (forwardMostMove / 2) + 1,
11494                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11495             DisplayError(move, 0);
11496             done = TRUE;
11497         } else {
11498             if (appData.debugMode)
11499               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11500                       yy_text, currentMoveString);
11501             fromX = currentMoveString[0] - AAA;
11502             fromY = currentMoveString[1] - ONE;
11503             toX = currentMoveString[2] - AAA;
11504             toY = currentMoveString[3] - ONE;
11505             promoChar = currentMoveString[4];
11506         }
11507         break;
11508
11509       case AmbiguousMove:
11510         if (appData.debugMode)
11511           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11512         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11513                 (forwardMostMove / 2) + 1,
11514                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11515         DisplayError(move, 0);
11516         done = TRUE;
11517         break;
11518
11519       default:
11520       case ImpossibleMove:
11521         if (appData.debugMode)
11522           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11523         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11524                 (forwardMostMove / 2) + 1,
11525                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11526         DisplayError(move, 0);
11527         done = TRUE;
11528         break;
11529     }
11530
11531     if (done) {
11532         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11533             DrawPosition(FALSE, boards[currentMove]);
11534             DisplayBothClocks();
11535             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11536               DisplayComment(currentMove - 1, commentList[currentMove]);
11537         }
11538         (void) StopLoadGameTimer();
11539         gameFileFP = NULL;
11540         cmailOldMove = forwardMostMove;
11541         return FALSE;
11542     } else {
11543         /* currentMoveString is set as a side-effect of yylex */
11544
11545         thinkOutput[0] = NULLCHAR;
11546         MakeMove(fromX, fromY, toX, toY, promoChar);
11547         currentMove = forwardMostMove;
11548         return TRUE;
11549     }
11550 }
11551
11552 /* Load the nth game from the given file */
11553 int
11554 LoadGameFromFile (char *filename, int n, char *title, int useList)
11555 {
11556     FILE *f;
11557     char buf[MSG_SIZ];
11558
11559     if (strcmp(filename, "-") == 0) {
11560         f = stdin;
11561         title = "stdin";
11562     } else {
11563         f = fopen(filename, "rb");
11564         if (f == NULL) {
11565           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11566             DisplayError(buf, errno);
11567             return FALSE;
11568         }
11569     }
11570     if (fseek(f, 0, 0) == -1) {
11571         /* f is not seekable; probably a pipe */
11572         useList = FALSE;
11573     }
11574     if (useList && n == 0) {
11575         int error = GameListBuild(f);
11576         if (error) {
11577             DisplayError(_("Cannot build game list"), error);
11578         } else if (!ListEmpty(&gameList) &&
11579                    ((ListGame *) gameList.tailPred)->number > 1) {
11580             GameListPopUp(f, title);
11581             return TRUE;
11582         }
11583         GameListDestroy();
11584         n = 1;
11585     }
11586     if (n == 0) n = 1;
11587     return LoadGame(f, n, title, FALSE);
11588 }
11589
11590
11591 void
11592 MakeRegisteredMove ()
11593 {
11594     int fromX, fromY, toX, toY;
11595     char promoChar;
11596     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11597         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11598           case CMAIL_MOVE:
11599           case CMAIL_DRAW:
11600             if (appData.debugMode)
11601               fprintf(debugFP, "Restoring %s for game %d\n",
11602                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11603
11604             thinkOutput[0] = NULLCHAR;
11605             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11606             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11607             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11608             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11609             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11610             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11611             MakeMove(fromX, fromY, toX, toY, promoChar);
11612             ShowMove(fromX, fromY, toX, toY);
11613
11614             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11615               case MT_NONE:
11616               case MT_CHECK:
11617                 break;
11618
11619               case MT_CHECKMATE:
11620               case MT_STAINMATE:
11621                 if (WhiteOnMove(currentMove)) {
11622                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11623                 } else {
11624                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11625                 }
11626                 break;
11627
11628               case MT_STALEMATE:
11629                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11630                 break;
11631             }
11632
11633             break;
11634
11635           case CMAIL_RESIGN:
11636             if (WhiteOnMove(currentMove)) {
11637                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11638             } else {
11639                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11640             }
11641             break;
11642
11643           case CMAIL_ACCEPT:
11644             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11645             break;
11646
11647           default:
11648             break;
11649         }
11650     }
11651
11652     return;
11653 }
11654
11655 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11656 int
11657 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11658 {
11659     int retVal;
11660
11661     if (gameNumber > nCmailGames) {
11662         DisplayError(_("No more games in this message"), 0);
11663         return FALSE;
11664     }
11665     if (f == lastLoadGameFP) {
11666         int offset = gameNumber - lastLoadGameNumber;
11667         if (offset == 0) {
11668             cmailMsg[0] = NULLCHAR;
11669             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11670                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11671                 nCmailMovesRegistered--;
11672             }
11673             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11674             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11675                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11676             }
11677         } else {
11678             if (! RegisterMove()) return FALSE;
11679         }
11680     }
11681
11682     retVal = LoadGame(f, gameNumber, title, useList);
11683
11684     /* Make move registered during previous look at this game, if any */
11685     MakeRegisteredMove();
11686
11687     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11688         commentList[currentMove]
11689           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11690         DisplayComment(currentMove - 1, commentList[currentMove]);
11691     }
11692
11693     return retVal;
11694 }
11695
11696 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11697 int
11698 ReloadGame (int offset)
11699 {
11700     int gameNumber = lastLoadGameNumber + offset;
11701     if (lastLoadGameFP == NULL) {
11702         DisplayError(_("No game has been loaded yet"), 0);
11703         return FALSE;
11704     }
11705     if (gameNumber <= 0) {
11706         DisplayError(_("Can't back up any further"), 0);
11707         return FALSE;
11708     }
11709     if (cmailMsgLoaded) {
11710         return CmailLoadGame(lastLoadGameFP, gameNumber,
11711                              lastLoadGameTitle, lastLoadGameUseList);
11712     } else {
11713         return LoadGame(lastLoadGameFP, gameNumber,
11714                         lastLoadGameTitle, lastLoadGameUseList);
11715     }
11716 }
11717
11718 int keys[EmptySquare+1];
11719
11720 int
11721 PositionMatches (Board b1, Board b2)
11722 {
11723     int r, f, sum=0;
11724     switch(appData.searchMode) {
11725         case 1: return CompareWithRights(b1, b2);
11726         case 2:
11727             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11728                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11729             }
11730             return TRUE;
11731         case 3:
11732             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11733               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11734                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11735             }
11736             return sum==0;
11737         case 4:
11738             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11739                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11740             }
11741             return sum==0;
11742     }
11743     return TRUE;
11744 }
11745
11746 #define Q_PROMO  4
11747 #define Q_EP     3
11748 #define Q_BCASTL 2
11749 #define Q_WCASTL 1
11750
11751 int pieceList[256], quickBoard[256];
11752 ChessSquare pieceType[256] = { EmptySquare };
11753 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11754 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11755 int soughtTotal, turn;
11756 Boolean epOK, flipSearch;
11757
11758 typedef struct {
11759     unsigned char piece, to;
11760 } Move;
11761
11762 #define DSIZE (250000)
11763
11764 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11765 Move *moveDatabase = initialSpace;
11766 unsigned int movePtr, dataSize = DSIZE;
11767
11768 int
11769 MakePieceList (Board board, int *counts)
11770 {
11771     int r, f, n=Q_PROMO, total=0;
11772     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11773     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11774         int sq = f + (r<<4);
11775         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11776             quickBoard[sq] = ++n;
11777             pieceList[n] = sq;
11778             pieceType[n] = board[r][f];
11779             counts[board[r][f]]++;
11780             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11781             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11782             total++;
11783         }
11784     }
11785     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11786     return total;
11787 }
11788
11789 void
11790 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11791 {
11792     int sq = fromX + (fromY<<4);
11793     int piece = quickBoard[sq];
11794     quickBoard[sq] = 0;
11795     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11796     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11797         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11798         moveDatabase[movePtr++].piece = Q_WCASTL;
11799         quickBoard[sq] = piece;
11800         piece = quickBoard[from]; quickBoard[from] = 0;
11801         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11802     } else
11803     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11804         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11805         moveDatabase[movePtr++].piece = Q_BCASTL;
11806         quickBoard[sq] = piece;
11807         piece = quickBoard[from]; quickBoard[from] = 0;
11808         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11809     } else
11810     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11811         quickBoard[(fromY<<4)+toX] = 0;
11812         moveDatabase[movePtr].piece = Q_EP;
11813         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11814         moveDatabase[movePtr].to = sq;
11815     } else
11816     if(promoPiece != pieceType[piece]) {
11817         moveDatabase[movePtr++].piece = Q_PROMO;
11818         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11819     }
11820     moveDatabase[movePtr].piece = piece;
11821     quickBoard[sq] = piece;
11822     movePtr++;
11823 }
11824
11825 int
11826 PackGame (Board board)
11827 {
11828     Move *newSpace = NULL;
11829     moveDatabase[movePtr].piece = 0; // terminate previous game
11830     if(movePtr > dataSize) {
11831         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11832         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11833         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11834         if(newSpace) {
11835             int i;
11836             Move *p = moveDatabase, *q = newSpace;
11837             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11838             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11839             moveDatabase = newSpace;
11840         } else { // calloc failed, we must be out of memory. Too bad...
11841             dataSize = 0; // prevent calloc events for all subsequent games
11842             return 0;     // and signal this one isn't cached
11843         }
11844     }
11845     movePtr++;
11846     MakePieceList(board, counts);
11847     return movePtr;
11848 }
11849
11850 int
11851 QuickCompare (Board board, int *minCounts, int *maxCounts)
11852 {   // compare according to search mode
11853     int r, f;
11854     switch(appData.searchMode)
11855     {
11856       case 1: // exact position match
11857         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11858         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11859             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11860         }
11861         break;
11862       case 2: // can have extra material on empty squares
11863         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11864             if(board[r][f] == EmptySquare) continue;
11865             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11866         }
11867         break;
11868       case 3: // material with exact Pawn structure
11869         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11870             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11871             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11872         } // fall through to material comparison
11873       case 4: // exact material
11874         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11875         break;
11876       case 6: // material range with given imbalance
11877         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11878         // fall through to range comparison
11879       case 5: // material range
11880         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11881     }
11882     return TRUE;
11883 }
11884
11885 int
11886 QuickScan (Board board, Move *move)
11887 {   // reconstruct game,and compare all positions in it
11888     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11889     do {
11890         int piece = move->piece;
11891         int to = move->to, from = pieceList[piece];
11892         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11893           if(!piece) return -1;
11894           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11895             piece = (++move)->piece;
11896             from = pieceList[piece];
11897             counts[pieceType[piece]]--;
11898             pieceType[piece] = (ChessSquare) move->to;
11899             counts[move->to]++;
11900           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11901             counts[pieceType[quickBoard[to]]]--;
11902             quickBoard[to] = 0; total--;
11903             move++;
11904             continue;
11905           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11906             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11907             from  = pieceList[piece]; // so this must be King
11908             quickBoard[from] = 0;
11909             pieceList[piece] = to;
11910             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11911             quickBoard[from] = 0; // rook
11912             quickBoard[to] = piece;
11913             to = move->to; piece = move->piece;
11914             goto aftercastle;
11915           }
11916         }
11917         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11918         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11919         quickBoard[from] = 0;
11920       aftercastle:
11921         quickBoard[to] = piece;
11922         pieceList[piece] = to;
11923         cnt++; turn ^= 3;
11924         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11925            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11926            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11927                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11928           ) {
11929             static int lastCounts[EmptySquare+1];
11930             int i;
11931             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11932             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11933         } else stretch = 0;
11934         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11935         move++;
11936     } while(1);
11937 }
11938
11939 void
11940 InitSearch ()
11941 {
11942     int r, f;
11943     flipSearch = FALSE;
11944     CopyBoard(soughtBoard, boards[currentMove]);
11945     soughtTotal = MakePieceList(soughtBoard, maxSought);
11946     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11947     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11948     CopyBoard(reverseBoard, boards[currentMove]);
11949     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11950         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11951         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11952         reverseBoard[r][f] = piece;
11953     }
11954     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11955     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11956     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11957                  || (boards[currentMove][CASTLING][2] == NoRights ||
11958                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11959                  && (boards[currentMove][CASTLING][5] == NoRights ||
11960                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11961       ) {
11962         flipSearch = TRUE;
11963         CopyBoard(flipBoard, soughtBoard);
11964         CopyBoard(rotateBoard, reverseBoard);
11965         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11966             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11967             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11968         }
11969     }
11970     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11971     if(appData.searchMode >= 5) {
11972         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11973         MakePieceList(soughtBoard, minSought);
11974         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11975     }
11976     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11977         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11978 }
11979
11980 GameInfo dummyInfo;
11981 static int creatingBook;
11982
11983 int
11984 GameContainsPosition (FILE *f, ListGame *lg)
11985 {
11986     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11987     int fromX, fromY, toX, toY;
11988     char promoChar;
11989     static int initDone=FALSE;
11990
11991     // weed out games based on numerical tag comparison
11992     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11993     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11994     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11995     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11996     if(!initDone) {
11997         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11998         initDone = TRUE;
11999     }
12000     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
12001     else CopyBoard(boards[scratch], initialPosition); // default start position
12002     if(lg->moves) {
12003         turn = btm + 1;
12004         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12005         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12006     }
12007     if(btm) plyNr++;
12008     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12009     fseek(f, lg->offset, 0);
12010     yynewfile(f);
12011     while(1) {
12012         yyboardindex = scratch;
12013         quickFlag = plyNr+1;
12014         next = Myylex();
12015         quickFlag = 0;
12016         switch(next) {
12017             case PGNTag:
12018                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12019             default:
12020                 continue;
12021
12022             case XBoardGame:
12023             case GNUChessGame:
12024                 if(plyNr) return -1; // after we have seen moves, this is for new game
12025               continue;
12026
12027             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12028             case ImpossibleMove:
12029             case WhiteWins: // game ends here with these four
12030             case BlackWins:
12031             case GameIsDrawn:
12032             case GameUnfinished:
12033                 return -1;
12034
12035             case IllegalMove:
12036                 if(appData.testLegality) return -1;
12037             case WhiteCapturesEnPassant:
12038             case BlackCapturesEnPassant:
12039             case WhitePromotion:
12040             case BlackPromotion:
12041             case WhiteNonPromotion:
12042             case BlackNonPromotion:
12043             case NormalMove:
12044             case WhiteKingSideCastle:
12045             case WhiteQueenSideCastle:
12046             case BlackKingSideCastle:
12047             case BlackQueenSideCastle:
12048             case WhiteKingSideCastleWild:
12049             case WhiteQueenSideCastleWild:
12050             case BlackKingSideCastleWild:
12051             case BlackQueenSideCastleWild:
12052             case WhiteHSideCastleFR:
12053             case WhiteASideCastleFR:
12054             case BlackHSideCastleFR:
12055             case BlackASideCastleFR:
12056                 fromX = currentMoveString[0] - AAA;
12057                 fromY = currentMoveString[1] - ONE;
12058                 toX = currentMoveString[2] - AAA;
12059                 toY = currentMoveString[3] - ONE;
12060                 promoChar = currentMoveString[4];
12061                 break;
12062             case WhiteDrop:
12063             case BlackDrop:
12064                 fromX = next == WhiteDrop ?
12065                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12066                   (int) CharToPiece(ToLower(currentMoveString[0]));
12067                 fromY = DROP_RANK;
12068                 toX = currentMoveString[2] - AAA;
12069                 toY = currentMoveString[3] - ONE;
12070                 promoChar = 0;
12071                 break;
12072         }
12073         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12074         plyNr++;
12075         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12076         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12077         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12078         if(appData.findMirror) {
12079             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12080             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12081         }
12082     }
12083 }
12084
12085 /* Load the nth game from open file f */
12086 int
12087 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12088 {
12089     ChessMove cm;
12090     char buf[MSG_SIZ];
12091     int gn = gameNumber;
12092     ListGame *lg = NULL;
12093     int numPGNTags = 0;
12094     int err, pos = -1;
12095     GameMode oldGameMode;
12096     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12097
12098     if (appData.debugMode)
12099         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12100
12101     if (gameMode == Training )
12102         SetTrainingModeOff();
12103
12104     oldGameMode = gameMode;
12105     if (gameMode != BeginningOfGame) {
12106       Reset(FALSE, TRUE);
12107     }
12108
12109     gameFileFP = f;
12110     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12111         fclose(lastLoadGameFP);
12112     }
12113
12114     if (useList) {
12115         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12116
12117         if (lg) {
12118             fseek(f, lg->offset, 0);
12119             GameListHighlight(gameNumber);
12120             pos = lg->position;
12121             gn = 1;
12122         }
12123         else {
12124             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12125               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12126             else
12127             DisplayError(_("Game number out of range"), 0);
12128             return FALSE;
12129         }
12130     } else {
12131         GameListDestroy();
12132         if (fseek(f, 0, 0) == -1) {
12133             if (f == lastLoadGameFP ?
12134                 gameNumber == lastLoadGameNumber + 1 :
12135                 gameNumber == 1) {
12136                 gn = 1;
12137             } else {
12138                 DisplayError(_("Can't seek on game file"), 0);
12139                 return FALSE;
12140             }
12141         }
12142     }
12143     lastLoadGameFP = f;
12144     lastLoadGameNumber = gameNumber;
12145     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12146     lastLoadGameUseList = useList;
12147
12148     yynewfile(f);
12149
12150     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12151       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12152                 lg->gameInfo.black);
12153             DisplayTitle(buf);
12154     } else if (*title != NULLCHAR) {
12155         if (gameNumber > 1) {
12156           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12157             DisplayTitle(buf);
12158         } else {
12159             DisplayTitle(title);
12160         }
12161     }
12162
12163     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12164         gameMode = PlayFromGameFile;
12165         ModeHighlight();
12166     }
12167
12168     currentMove = forwardMostMove = backwardMostMove = 0;
12169     CopyBoard(boards[0], initialPosition);
12170     StopClocks();
12171
12172     /*
12173      * Skip the first gn-1 games in the file.
12174      * Also skip over anything that precedes an identifiable
12175      * start of game marker, to avoid being confused by
12176      * garbage at the start of the file.  Currently
12177      * recognized start of game markers are the move number "1",
12178      * the pattern "gnuchess .* game", the pattern
12179      * "^[#;%] [^ ]* game file", and a PGN tag block.
12180      * A game that starts with one of the latter two patterns
12181      * will also have a move number 1, possibly
12182      * following a position diagram.
12183      * 5-4-02: Let's try being more lenient and allowing a game to
12184      * start with an unnumbered move.  Does that break anything?
12185      */
12186     cm = lastLoadGameStart = EndOfFile;
12187     while (gn > 0) {
12188         yyboardindex = forwardMostMove;
12189         cm = (ChessMove) Myylex();
12190         switch (cm) {
12191           case EndOfFile:
12192             if (cmailMsgLoaded) {
12193                 nCmailGames = CMAIL_MAX_GAMES - gn;
12194             } else {
12195                 Reset(TRUE, TRUE);
12196                 DisplayError(_("Game not found in file"), 0);
12197             }
12198             return FALSE;
12199
12200           case GNUChessGame:
12201           case XBoardGame:
12202             gn--;
12203             lastLoadGameStart = cm;
12204             break;
12205
12206           case MoveNumberOne:
12207             switch (lastLoadGameStart) {
12208               case GNUChessGame:
12209               case XBoardGame:
12210               case PGNTag:
12211                 break;
12212               case MoveNumberOne:
12213               case EndOfFile:
12214                 gn--;           /* count this game */
12215                 lastLoadGameStart = cm;
12216                 break;
12217               default:
12218                 /* impossible */
12219                 break;
12220             }
12221             break;
12222
12223           case PGNTag:
12224             switch (lastLoadGameStart) {
12225               case GNUChessGame:
12226               case PGNTag:
12227               case MoveNumberOne:
12228               case EndOfFile:
12229                 gn--;           /* count this game */
12230                 lastLoadGameStart = cm;
12231                 break;
12232               case XBoardGame:
12233                 lastLoadGameStart = cm; /* game counted already */
12234                 break;
12235               default:
12236                 /* impossible */
12237                 break;
12238             }
12239             if (gn > 0) {
12240                 do {
12241                     yyboardindex = forwardMostMove;
12242                     cm = (ChessMove) Myylex();
12243                 } while (cm == PGNTag || cm == Comment);
12244             }
12245             break;
12246
12247           case WhiteWins:
12248           case BlackWins:
12249           case GameIsDrawn:
12250             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12251                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12252                     != CMAIL_OLD_RESULT) {
12253                     nCmailResults ++ ;
12254                     cmailResult[  CMAIL_MAX_GAMES
12255                                 - gn - 1] = CMAIL_OLD_RESULT;
12256                 }
12257             }
12258             break;
12259
12260           case NormalMove:
12261             /* Only a NormalMove can be at the start of a game
12262              * without a position diagram. */
12263             if (lastLoadGameStart == EndOfFile ) {
12264               gn--;
12265               lastLoadGameStart = MoveNumberOne;
12266             }
12267             break;
12268
12269           default:
12270             break;
12271         }
12272     }
12273
12274     if (appData.debugMode)
12275       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12276
12277     if (cm == XBoardGame) {
12278         /* Skip any header junk before position diagram and/or move 1 */
12279         for (;;) {
12280             yyboardindex = forwardMostMove;
12281             cm = (ChessMove) Myylex();
12282
12283             if (cm == EndOfFile ||
12284                 cm == GNUChessGame || cm == XBoardGame) {
12285                 /* Empty game; pretend end-of-file and handle later */
12286                 cm = EndOfFile;
12287                 break;
12288             }
12289
12290             if (cm == MoveNumberOne || cm == PositionDiagram ||
12291                 cm == PGNTag || cm == Comment)
12292               break;
12293         }
12294     } else if (cm == GNUChessGame) {
12295         if (gameInfo.event != NULL) {
12296             free(gameInfo.event);
12297         }
12298         gameInfo.event = StrSave(yy_text);
12299     }
12300
12301     startedFromSetupPosition = FALSE;
12302     while (cm == PGNTag) {
12303         if (appData.debugMode)
12304           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12305         err = ParsePGNTag(yy_text, &gameInfo);
12306         if (!err) numPGNTags++;
12307
12308         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12309         if(gameInfo.variant != oldVariant) {
12310             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12311             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12312             InitPosition(TRUE);
12313             oldVariant = gameInfo.variant;
12314             if (appData.debugMode)
12315               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12316         }
12317
12318
12319         if (gameInfo.fen != NULL) {
12320           Board initial_position;
12321           startedFromSetupPosition = TRUE;
12322           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12323             Reset(TRUE, TRUE);
12324             DisplayError(_("Bad FEN position in file"), 0);
12325             return FALSE;
12326           }
12327           CopyBoard(boards[0], initial_position);
12328           if (blackPlaysFirst) {
12329             currentMove = forwardMostMove = backwardMostMove = 1;
12330             CopyBoard(boards[1], initial_position);
12331             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12332             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12333             timeRemaining[0][1] = whiteTimeRemaining;
12334             timeRemaining[1][1] = blackTimeRemaining;
12335             if (commentList[0] != NULL) {
12336               commentList[1] = commentList[0];
12337               commentList[0] = NULL;
12338             }
12339           } else {
12340             currentMove = forwardMostMove = backwardMostMove = 0;
12341           }
12342           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12343           {   int i;
12344               initialRulePlies = FENrulePlies;
12345               for( i=0; i< nrCastlingRights; i++ )
12346                   initialRights[i] = initial_position[CASTLING][i];
12347           }
12348           yyboardindex = forwardMostMove;
12349           free(gameInfo.fen);
12350           gameInfo.fen = NULL;
12351         }
12352
12353         yyboardindex = forwardMostMove;
12354         cm = (ChessMove) Myylex();
12355
12356         /* Handle comments interspersed among the tags */
12357         while (cm == Comment) {
12358             char *p;
12359             if (appData.debugMode)
12360               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12361             p = yy_text;
12362             AppendComment(currentMove, p, FALSE);
12363             yyboardindex = forwardMostMove;
12364             cm = (ChessMove) Myylex();
12365         }
12366     }
12367
12368     /* don't rely on existence of Event tag since if game was
12369      * pasted from clipboard the Event tag may not exist
12370      */
12371     if (numPGNTags > 0){
12372         char *tags;
12373         if (gameInfo.variant == VariantNormal) {
12374           VariantClass v = StringToVariant(gameInfo.event);
12375           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12376           if(v < VariantShogi) gameInfo.variant = v;
12377         }
12378         if (!matchMode) {
12379           if( appData.autoDisplayTags ) {
12380             tags = PGNTags(&gameInfo);
12381             TagsPopUp(tags, CmailMsg());
12382             free(tags);
12383           }
12384         }
12385     } else {
12386         /* Make something up, but don't display it now */
12387         SetGameInfo();
12388         TagsPopDown();
12389     }
12390
12391     if (cm == PositionDiagram) {
12392         int i, j;
12393         char *p;
12394         Board initial_position;
12395
12396         if (appData.debugMode)
12397           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12398
12399         if (!startedFromSetupPosition) {
12400             p = yy_text;
12401             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12402               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12403                 switch (*p) {
12404                   case '{':
12405                   case '[':
12406                   case '-':
12407                   case ' ':
12408                   case '\t':
12409                   case '\n':
12410                   case '\r':
12411                     break;
12412                   default:
12413                     initial_position[i][j++] = CharToPiece(*p);
12414                     break;
12415                 }
12416             while (*p == ' ' || *p == '\t' ||
12417                    *p == '\n' || *p == '\r') p++;
12418
12419             if (strncmp(p, "black", strlen("black"))==0)
12420               blackPlaysFirst = TRUE;
12421             else
12422               blackPlaysFirst = FALSE;
12423             startedFromSetupPosition = TRUE;
12424
12425             CopyBoard(boards[0], initial_position);
12426             if (blackPlaysFirst) {
12427                 currentMove = forwardMostMove = backwardMostMove = 1;
12428                 CopyBoard(boards[1], initial_position);
12429                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12430                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12431                 timeRemaining[0][1] = whiteTimeRemaining;
12432                 timeRemaining[1][1] = blackTimeRemaining;
12433                 if (commentList[0] != NULL) {
12434                     commentList[1] = commentList[0];
12435                     commentList[0] = NULL;
12436                 }
12437             } else {
12438                 currentMove = forwardMostMove = backwardMostMove = 0;
12439             }
12440         }
12441         yyboardindex = forwardMostMove;
12442         cm = (ChessMove) Myylex();
12443     }
12444
12445   if(!creatingBook) {
12446     if (first.pr == NoProc) {
12447         StartChessProgram(&first);
12448     }
12449     InitChessProgram(&first, FALSE);
12450     SendToProgram("force\n", &first);
12451     if (startedFromSetupPosition) {
12452         SendBoard(&first, forwardMostMove);
12453     if (appData.debugMode) {
12454         fprintf(debugFP, "Load Game\n");
12455     }
12456         DisplayBothClocks();
12457     }
12458   }
12459
12460     /* [HGM] server: flag to write setup moves in broadcast file as one */
12461     loadFlag = appData.suppressLoadMoves;
12462
12463     while (cm == Comment) {
12464         char *p;
12465         if (appData.debugMode)
12466           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12467         p = yy_text;
12468         AppendComment(currentMove, p, FALSE);
12469         yyboardindex = forwardMostMove;
12470         cm = (ChessMove) Myylex();
12471     }
12472
12473     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12474         cm == WhiteWins || cm == BlackWins ||
12475         cm == GameIsDrawn || cm == GameUnfinished) {
12476         DisplayMessage("", _("No moves in game"));
12477         if (cmailMsgLoaded) {
12478             if (appData.debugMode)
12479               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12480             ClearHighlights();
12481             flipView = FALSE;
12482         }
12483         DrawPosition(FALSE, boards[currentMove]);
12484         DisplayBothClocks();
12485         gameMode = EditGame;
12486         ModeHighlight();
12487         gameFileFP = NULL;
12488         cmailOldMove = 0;
12489         return TRUE;
12490     }
12491
12492     // [HGM] PV info: routine tests if comment empty
12493     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12494         DisplayComment(currentMove - 1, commentList[currentMove]);
12495     }
12496     if (!matchMode && appData.timeDelay != 0)
12497       DrawPosition(FALSE, boards[currentMove]);
12498
12499     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12500       programStats.ok_to_send = 1;
12501     }
12502
12503     /* if the first token after the PGN tags is a move
12504      * and not move number 1, retrieve it from the parser
12505      */
12506     if (cm != MoveNumberOne)
12507         LoadGameOneMove(cm);
12508
12509     /* load the remaining moves from the file */
12510     while (LoadGameOneMove(EndOfFile)) {
12511       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12512       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12513     }
12514
12515     /* rewind to the start of the game */
12516     currentMove = backwardMostMove;
12517
12518     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12519
12520     if (oldGameMode == AnalyzeFile) {
12521       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12522       AnalyzeFileEvent();
12523     } else
12524     if (oldGameMode == AnalyzeMode) {
12525       AnalyzeFileEvent();
12526     }
12527
12528     if(creatingBook) return TRUE;
12529     if (!matchMode && pos > 0) {
12530         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12531     } else
12532     if (matchMode || appData.timeDelay == 0) {
12533       ToEndEvent();
12534     } else if (appData.timeDelay > 0) {
12535       AutoPlayGameLoop();
12536     }
12537
12538     if (appData.debugMode)
12539         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12540
12541     loadFlag = 0; /* [HGM] true game starts */
12542     return TRUE;
12543 }
12544
12545 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12546 int
12547 ReloadPosition (int offset)
12548 {
12549     int positionNumber = lastLoadPositionNumber + offset;
12550     if (lastLoadPositionFP == NULL) {
12551         DisplayError(_("No position has been loaded yet"), 0);
12552         return FALSE;
12553     }
12554     if (positionNumber <= 0) {
12555         DisplayError(_("Can't back up any further"), 0);
12556         return FALSE;
12557     }
12558     return LoadPosition(lastLoadPositionFP, positionNumber,
12559                         lastLoadPositionTitle);
12560 }
12561
12562 /* Load the nth position from the given file */
12563 int
12564 LoadPositionFromFile (char *filename, int n, char *title)
12565 {
12566     FILE *f;
12567     char buf[MSG_SIZ];
12568
12569     if (strcmp(filename, "-") == 0) {
12570         return LoadPosition(stdin, n, "stdin");
12571     } else {
12572         f = fopen(filename, "rb");
12573         if (f == NULL) {
12574             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12575             DisplayError(buf, errno);
12576             return FALSE;
12577         } else {
12578             return LoadPosition(f, n, title);
12579         }
12580     }
12581 }
12582
12583 /* Load the nth position from the given open file, and close it */
12584 int
12585 LoadPosition (FILE *f, int positionNumber, char *title)
12586 {
12587     char *p, line[MSG_SIZ];
12588     Board initial_position;
12589     int i, j, fenMode, pn;
12590
12591     if (gameMode == Training )
12592         SetTrainingModeOff();
12593
12594     if (gameMode != BeginningOfGame) {
12595         Reset(FALSE, TRUE);
12596     }
12597     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12598         fclose(lastLoadPositionFP);
12599     }
12600     if (positionNumber == 0) positionNumber = 1;
12601     lastLoadPositionFP = f;
12602     lastLoadPositionNumber = positionNumber;
12603     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12604     if (first.pr == NoProc && !appData.noChessProgram) {
12605       StartChessProgram(&first);
12606       InitChessProgram(&first, FALSE);
12607     }
12608     pn = positionNumber;
12609     if (positionNumber < 0) {
12610         /* Negative position number means to seek to that byte offset */
12611         if (fseek(f, -positionNumber, 0) == -1) {
12612             DisplayError(_("Can't seek on position file"), 0);
12613             return FALSE;
12614         };
12615         pn = 1;
12616     } else {
12617         if (fseek(f, 0, 0) == -1) {
12618             if (f == lastLoadPositionFP ?
12619                 positionNumber == lastLoadPositionNumber + 1 :
12620                 positionNumber == 1) {
12621                 pn = 1;
12622             } else {
12623                 DisplayError(_("Can't seek on position file"), 0);
12624                 return FALSE;
12625             }
12626         }
12627     }
12628     /* See if this file is FEN or old-style xboard */
12629     if (fgets(line, MSG_SIZ, f) == NULL) {
12630         DisplayError(_("Position not found in file"), 0);
12631         return FALSE;
12632     }
12633     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12634     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12635
12636     if (pn >= 2) {
12637         if (fenMode || line[0] == '#') pn--;
12638         while (pn > 0) {
12639             /* skip positions before number pn */
12640             if (fgets(line, MSG_SIZ, f) == NULL) {
12641                 Reset(TRUE, TRUE);
12642                 DisplayError(_("Position not found in file"), 0);
12643                 return FALSE;
12644             }
12645             if (fenMode || line[0] == '#') pn--;
12646         }
12647     }
12648
12649     if (fenMode) {
12650         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12651             DisplayError(_("Bad FEN position in file"), 0);
12652             return FALSE;
12653         }
12654     } else {
12655         (void) fgets(line, MSG_SIZ, f);
12656         (void) fgets(line, MSG_SIZ, f);
12657
12658         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12659             (void) fgets(line, MSG_SIZ, f);
12660             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12661                 if (*p == ' ')
12662                   continue;
12663                 initial_position[i][j++] = CharToPiece(*p);
12664             }
12665         }
12666
12667         blackPlaysFirst = FALSE;
12668         if (!feof(f)) {
12669             (void) fgets(line, MSG_SIZ, f);
12670             if (strncmp(line, "black", strlen("black"))==0)
12671               blackPlaysFirst = TRUE;
12672         }
12673     }
12674     startedFromSetupPosition = TRUE;
12675
12676     CopyBoard(boards[0], initial_position);
12677     if (blackPlaysFirst) {
12678         currentMove = forwardMostMove = backwardMostMove = 1;
12679         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12680         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12681         CopyBoard(boards[1], initial_position);
12682         DisplayMessage("", _("Black to play"));
12683     } else {
12684         currentMove = forwardMostMove = backwardMostMove = 0;
12685         DisplayMessage("", _("White to play"));
12686     }
12687     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12688     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12689         SendToProgram("force\n", &first);
12690         SendBoard(&first, forwardMostMove);
12691     }
12692     if (appData.debugMode) {
12693 int i, j;
12694   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12695   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12696         fprintf(debugFP, "Load Position\n");
12697     }
12698
12699     if (positionNumber > 1) {
12700       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12701         DisplayTitle(line);
12702     } else {
12703         DisplayTitle(title);
12704     }
12705     gameMode = EditGame;
12706     ModeHighlight();
12707     ResetClocks();
12708     timeRemaining[0][1] = whiteTimeRemaining;
12709     timeRemaining[1][1] = blackTimeRemaining;
12710     DrawPosition(FALSE, boards[currentMove]);
12711
12712     return TRUE;
12713 }
12714
12715
12716 void
12717 CopyPlayerNameIntoFileName (char **dest, char *src)
12718 {
12719     while (*src != NULLCHAR && *src != ',') {
12720         if (*src == ' ') {
12721             *(*dest)++ = '_';
12722             src++;
12723         } else {
12724             *(*dest)++ = *src++;
12725         }
12726     }
12727 }
12728
12729 char *
12730 DefaultFileName (char *ext)
12731 {
12732     static char def[MSG_SIZ];
12733     char *p;
12734
12735     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12736         p = def;
12737         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12738         *p++ = '-';
12739         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12740         *p++ = '.';
12741         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12742     } else {
12743         def[0] = NULLCHAR;
12744     }
12745     return def;
12746 }
12747
12748 /* Save the current game to the given file */
12749 int
12750 SaveGameToFile (char *filename, int append)
12751 {
12752     FILE *f;
12753     char buf[MSG_SIZ];
12754     int result, i, t,tot=0;
12755
12756     if (strcmp(filename, "-") == 0) {
12757         return SaveGame(stdout, 0, NULL);
12758     } else {
12759         for(i=0; i<10; i++) { // upto 10 tries
12760              f = fopen(filename, append ? "a" : "w");
12761              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12762              if(f || errno != 13) break;
12763              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12764              tot += t;
12765         }
12766         if (f == NULL) {
12767             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12768             DisplayError(buf, errno);
12769             return FALSE;
12770         } else {
12771             safeStrCpy(buf, lastMsg, MSG_SIZ);
12772             DisplayMessage(_("Waiting for access to save file"), "");
12773             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12774             DisplayMessage(_("Saving game"), "");
12775             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12776             result = SaveGame(f, 0, NULL);
12777             DisplayMessage(buf, "");
12778             return result;
12779         }
12780     }
12781 }
12782
12783 char *
12784 SavePart (char *str)
12785 {
12786     static char buf[MSG_SIZ];
12787     char *p;
12788
12789     p = strchr(str, ' ');
12790     if (p == NULL) return str;
12791     strncpy(buf, str, p - str);
12792     buf[p - str] = NULLCHAR;
12793     return buf;
12794 }
12795
12796 #define PGN_MAX_LINE 75
12797
12798 #define PGN_SIDE_WHITE  0
12799 #define PGN_SIDE_BLACK  1
12800
12801 static int
12802 FindFirstMoveOutOfBook (int side)
12803 {
12804     int result = -1;
12805
12806     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12807         int index = backwardMostMove;
12808         int has_book_hit = 0;
12809
12810         if( (index % 2) != side ) {
12811             index++;
12812         }
12813
12814         while( index < forwardMostMove ) {
12815             /* Check to see if engine is in book */
12816             int depth = pvInfoList[index].depth;
12817             int score = pvInfoList[index].score;
12818             int in_book = 0;
12819
12820             if( depth <= 2 ) {
12821                 in_book = 1;
12822             }
12823             else if( score == 0 && depth == 63 ) {
12824                 in_book = 1; /* Zappa */
12825             }
12826             else if( score == 2 && depth == 99 ) {
12827                 in_book = 1; /* Abrok */
12828             }
12829
12830             has_book_hit += in_book;
12831
12832             if( ! in_book ) {
12833                 result = index;
12834
12835                 break;
12836             }
12837
12838             index += 2;
12839         }
12840     }
12841
12842     return result;
12843 }
12844
12845 void
12846 GetOutOfBookInfo (char * buf)
12847 {
12848     int oob[2];
12849     int i;
12850     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12851
12852     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12853     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12854
12855     *buf = '\0';
12856
12857     if( oob[0] >= 0 || oob[1] >= 0 ) {
12858         for( i=0; i<2; i++ ) {
12859             int idx = oob[i];
12860
12861             if( idx >= 0 ) {
12862                 if( i > 0 && oob[0] >= 0 ) {
12863                     strcat( buf, "   " );
12864                 }
12865
12866                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12867                 sprintf( buf+strlen(buf), "%s%.2f",
12868                     pvInfoList[idx].score >= 0 ? "+" : "",
12869                     pvInfoList[idx].score / 100.0 );
12870             }
12871         }
12872     }
12873 }
12874
12875 /* Save game in PGN style and close the file */
12876 int
12877 SaveGamePGN (FILE *f)
12878 {
12879     int i, offset, linelen, newblock;
12880 //    char *movetext;
12881     char numtext[32];
12882     int movelen, numlen, blank;
12883     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12884
12885     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12886
12887     PrintPGNTags(f, &gameInfo);
12888
12889     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12890
12891     if (backwardMostMove > 0 || startedFromSetupPosition) {
12892         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
12893         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12894         fprintf(f, "\n{--------------\n");
12895         PrintPosition(f, backwardMostMove);
12896         fprintf(f, "--------------}\n");
12897         free(fen);
12898     }
12899     else {
12900         /* [AS] Out of book annotation */
12901         if( appData.saveOutOfBookInfo ) {
12902             char buf[64];
12903
12904             GetOutOfBookInfo( buf );
12905
12906             if( buf[0] != '\0' ) {
12907                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12908             }
12909         }
12910
12911         fprintf(f, "\n");
12912     }
12913
12914     i = backwardMostMove;
12915     linelen = 0;
12916     newblock = TRUE;
12917
12918     while (i < forwardMostMove) {
12919         /* Print comments preceding this move */
12920         if (commentList[i] != NULL) {
12921             if (linelen > 0) fprintf(f, "\n");
12922             fprintf(f, "%s", commentList[i]);
12923             linelen = 0;
12924             newblock = TRUE;
12925         }
12926
12927         /* Format move number */
12928         if ((i % 2) == 0)
12929           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12930         else
12931           if (newblock)
12932             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12933           else
12934             numtext[0] = NULLCHAR;
12935
12936         numlen = strlen(numtext);
12937         newblock = FALSE;
12938
12939         /* Print move number */
12940         blank = linelen > 0 && numlen > 0;
12941         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12942             fprintf(f, "\n");
12943             linelen = 0;
12944             blank = 0;
12945         }
12946         if (blank) {
12947             fprintf(f, " ");
12948             linelen++;
12949         }
12950         fprintf(f, "%s", numtext);
12951         linelen += numlen;
12952
12953         /* Get move */
12954         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12955         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12956
12957         /* Print move */
12958         blank = linelen > 0 && movelen > 0;
12959         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12960             fprintf(f, "\n");
12961             linelen = 0;
12962             blank = 0;
12963         }
12964         if (blank) {
12965             fprintf(f, " ");
12966             linelen++;
12967         }
12968         fprintf(f, "%s", move_buffer);
12969         linelen += movelen;
12970
12971         /* [AS] Add PV info if present */
12972         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12973             /* [HGM] add time */
12974             char buf[MSG_SIZ]; int seconds;
12975
12976             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12977
12978             if( seconds <= 0)
12979               buf[0] = 0;
12980             else
12981               if( seconds < 30 )
12982                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12983               else
12984                 {
12985                   seconds = (seconds + 4)/10; // round to full seconds
12986                   if( seconds < 60 )
12987                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12988                   else
12989                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12990                 }
12991
12992             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12993                       pvInfoList[i].score >= 0 ? "+" : "",
12994                       pvInfoList[i].score / 100.0,
12995                       pvInfoList[i].depth,
12996                       buf );
12997
12998             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12999
13000             /* Print score/depth */
13001             blank = linelen > 0 && movelen > 0;
13002             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13003                 fprintf(f, "\n");
13004                 linelen = 0;
13005                 blank = 0;
13006             }
13007             if (blank) {
13008                 fprintf(f, " ");
13009                 linelen++;
13010             }
13011             fprintf(f, "%s", move_buffer);
13012             linelen += movelen;
13013         }
13014
13015         i++;
13016     }
13017
13018     /* Start a new line */
13019     if (linelen > 0) fprintf(f, "\n");
13020
13021     /* Print comments after last move */
13022     if (commentList[i] != NULL) {
13023         fprintf(f, "%s\n", commentList[i]);
13024     }
13025
13026     /* Print result */
13027     if (gameInfo.resultDetails != NULL &&
13028         gameInfo.resultDetails[0] != NULLCHAR) {
13029         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
13030                 PGNResult(gameInfo.result));
13031     } else {
13032         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13033     }
13034
13035     fclose(f);
13036     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13037     return TRUE;
13038 }
13039
13040 /* Save game in old style and close the file */
13041 int
13042 SaveGameOldStyle (FILE *f)
13043 {
13044     int i, offset;
13045     time_t tm;
13046
13047     tm = time((time_t *) NULL);
13048
13049     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13050     PrintOpponents(f);
13051
13052     if (backwardMostMove > 0 || startedFromSetupPosition) {
13053         fprintf(f, "\n[--------------\n");
13054         PrintPosition(f, backwardMostMove);
13055         fprintf(f, "--------------]\n");
13056     } else {
13057         fprintf(f, "\n");
13058     }
13059
13060     i = backwardMostMove;
13061     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13062
13063     while (i < forwardMostMove) {
13064         if (commentList[i] != NULL) {
13065             fprintf(f, "[%s]\n", commentList[i]);
13066         }
13067
13068         if ((i % 2) == 1) {
13069             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13070             i++;
13071         } else {
13072             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13073             i++;
13074             if (commentList[i] != NULL) {
13075                 fprintf(f, "\n");
13076                 continue;
13077             }
13078             if (i >= forwardMostMove) {
13079                 fprintf(f, "\n");
13080                 break;
13081             }
13082             fprintf(f, "%s\n", parseList[i]);
13083             i++;
13084         }
13085     }
13086
13087     if (commentList[i] != NULL) {
13088         fprintf(f, "[%s]\n", commentList[i]);
13089     }
13090
13091     /* This isn't really the old style, but it's close enough */
13092     if (gameInfo.resultDetails != NULL &&
13093         gameInfo.resultDetails[0] != NULLCHAR) {
13094         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13095                 gameInfo.resultDetails);
13096     } else {
13097         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13098     }
13099
13100     fclose(f);
13101     return TRUE;
13102 }
13103
13104 /* Save the current game to open file f and close the file */
13105 int
13106 SaveGame (FILE *f, int dummy, char *dummy2)
13107 {
13108     if (gameMode == EditPosition) EditPositionDone(TRUE);
13109     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13110     if (appData.oldSaveStyle)
13111       return SaveGameOldStyle(f);
13112     else
13113       return SaveGamePGN(f);
13114 }
13115
13116 /* Save the current position to the given file */
13117 int
13118 SavePositionToFile (char *filename)
13119 {
13120     FILE *f;
13121     char buf[MSG_SIZ];
13122
13123     if (strcmp(filename, "-") == 0) {
13124         return SavePosition(stdout, 0, NULL);
13125     } else {
13126         f = fopen(filename, "a");
13127         if (f == NULL) {
13128             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13129             DisplayError(buf, errno);
13130             return FALSE;
13131         } else {
13132             safeStrCpy(buf, lastMsg, MSG_SIZ);
13133             DisplayMessage(_("Waiting for access to save file"), "");
13134             flock(fileno(f), LOCK_EX); // [HGM] lock
13135             DisplayMessage(_("Saving position"), "");
13136             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13137             SavePosition(f, 0, NULL);
13138             DisplayMessage(buf, "");
13139             return TRUE;
13140         }
13141     }
13142 }
13143
13144 /* Save the current position to the given open file and close the file */
13145 int
13146 SavePosition (FILE *f, int dummy, char *dummy2)
13147 {
13148     time_t tm;
13149     char *fen;
13150
13151     if (gameMode == EditPosition) EditPositionDone(TRUE);
13152     if (appData.oldSaveStyle) {
13153         tm = time((time_t *) NULL);
13154
13155         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13156         PrintOpponents(f);
13157         fprintf(f, "[--------------\n");
13158         PrintPosition(f, currentMove);
13159         fprintf(f, "--------------]\n");
13160     } else {
13161         fen = PositionToFEN(currentMove, NULL, 1);
13162         fprintf(f, "%s\n", fen);
13163         free(fen);
13164     }
13165     fclose(f);
13166     return TRUE;
13167 }
13168
13169 void
13170 ReloadCmailMsgEvent (int unregister)
13171 {
13172 #if !WIN32
13173     static char *inFilename = NULL;
13174     static char *outFilename;
13175     int i;
13176     struct stat inbuf, outbuf;
13177     int status;
13178
13179     /* Any registered moves are unregistered if unregister is set, */
13180     /* i.e. invoked by the signal handler */
13181     if (unregister) {
13182         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13183             cmailMoveRegistered[i] = FALSE;
13184             if (cmailCommentList[i] != NULL) {
13185                 free(cmailCommentList[i]);
13186                 cmailCommentList[i] = NULL;
13187             }
13188         }
13189         nCmailMovesRegistered = 0;
13190     }
13191
13192     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13193         cmailResult[i] = CMAIL_NOT_RESULT;
13194     }
13195     nCmailResults = 0;
13196
13197     if (inFilename == NULL) {
13198         /* Because the filenames are static they only get malloced once  */
13199         /* and they never get freed                                      */
13200         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13201         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13202
13203         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13204         sprintf(outFilename, "%s.out", appData.cmailGameName);
13205     }
13206
13207     status = stat(outFilename, &outbuf);
13208     if (status < 0) {
13209         cmailMailedMove = FALSE;
13210     } else {
13211         status = stat(inFilename, &inbuf);
13212         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13213     }
13214
13215     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13216        counts the games, notes how each one terminated, etc.
13217
13218        It would be nice to remove this kludge and instead gather all
13219        the information while building the game list.  (And to keep it
13220        in the game list nodes instead of having a bunch of fixed-size
13221        parallel arrays.)  Note this will require getting each game's
13222        termination from the PGN tags, as the game list builder does
13223        not process the game moves.  --mann
13224        */
13225     cmailMsgLoaded = TRUE;
13226     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13227
13228     /* Load first game in the file or popup game menu */
13229     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13230
13231 #endif /* !WIN32 */
13232     return;
13233 }
13234
13235 int
13236 RegisterMove ()
13237 {
13238     FILE *f;
13239     char string[MSG_SIZ];
13240
13241     if (   cmailMailedMove
13242         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13243         return TRUE;            /* Allow free viewing  */
13244     }
13245
13246     /* Unregister move to ensure that we don't leave RegisterMove        */
13247     /* with the move registered when the conditions for registering no   */
13248     /* longer hold                                                       */
13249     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13250         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13251         nCmailMovesRegistered --;
13252
13253         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13254           {
13255               free(cmailCommentList[lastLoadGameNumber - 1]);
13256               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13257           }
13258     }
13259
13260     if (cmailOldMove == -1) {
13261         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13262         return FALSE;
13263     }
13264
13265     if (currentMove > cmailOldMove + 1) {
13266         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13267         return FALSE;
13268     }
13269
13270     if (currentMove < cmailOldMove) {
13271         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13272         return FALSE;
13273     }
13274
13275     if (forwardMostMove > currentMove) {
13276         /* Silently truncate extra moves */
13277         TruncateGame();
13278     }
13279
13280     if (   (currentMove == cmailOldMove + 1)
13281         || (   (currentMove == cmailOldMove)
13282             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13283                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13284         if (gameInfo.result != GameUnfinished) {
13285             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13286         }
13287
13288         if (commentList[currentMove] != NULL) {
13289             cmailCommentList[lastLoadGameNumber - 1]
13290               = StrSave(commentList[currentMove]);
13291         }
13292         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13293
13294         if (appData.debugMode)
13295           fprintf(debugFP, "Saving %s for game %d\n",
13296                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13297
13298         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13299
13300         f = fopen(string, "w");
13301         if (appData.oldSaveStyle) {
13302             SaveGameOldStyle(f); /* also closes the file */
13303
13304             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13305             f = fopen(string, "w");
13306             SavePosition(f, 0, NULL); /* also closes the file */
13307         } else {
13308             fprintf(f, "{--------------\n");
13309             PrintPosition(f, currentMove);
13310             fprintf(f, "--------------}\n\n");
13311
13312             SaveGame(f, 0, NULL); /* also closes the file*/
13313         }
13314
13315         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13316         nCmailMovesRegistered ++;
13317     } else if (nCmailGames == 1) {
13318         DisplayError(_("You have not made a move yet"), 0);
13319         return FALSE;
13320     }
13321
13322     return TRUE;
13323 }
13324
13325 void
13326 MailMoveEvent ()
13327 {
13328 #if !WIN32
13329     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13330     FILE *commandOutput;
13331     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13332     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13333     int nBuffers;
13334     int i;
13335     int archived;
13336     char *arcDir;
13337
13338     if (! cmailMsgLoaded) {
13339         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13340         return;
13341     }
13342
13343     if (nCmailGames == nCmailResults) {
13344         DisplayError(_("No unfinished games"), 0);
13345         return;
13346     }
13347
13348 #if CMAIL_PROHIBIT_REMAIL
13349     if (cmailMailedMove) {
13350       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);
13351         DisplayError(msg, 0);
13352         return;
13353     }
13354 #endif
13355
13356     if (! (cmailMailedMove || RegisterMove())) return;
13357
13358     if (   cmailMailedMove
13359         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13360       snprintf(string, MSG_SIZ, partCommandString,
13361                appData.debugMode ? " -v" : "", appData.cmailGameName);
13362         commandOutput = popen(string, "r");
13363
13364         if (commandOutput == NULL) {
13365             DisplayError(_("Failed to invoke cmail"), 0);
13366         } else {
13367             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13368                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13369             }
13370             if (nBuffers > 1) {
13371                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13372                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13373                 nBytes = MSG_SIZ - 1;
13374             } else {
13375                 (void) memcpy(msg, buffer, nBytes);
13376             }
13377             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13378
13379             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13380                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13381
13382                 archived = TRUE;
13383                 for (i = 0; i < nCmailGames; i ++) {
13384                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13385                         archived = FALSE;
13386                     }
13387                 }
13388                 if (   archived
13389                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13390                         != NULL)) {
13391                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13392                            arcDir,
13393                            appData.cmailGameName,
13394                            gameInfo.date);
13395                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13396                     cmailMsgLoaded = FALSE;
13397                 }
13398             }
13399
13400             DisplayInformation(msg);
13401             pclose(commandOutput);
13402         }
13403     } else {
13404         if ((*cmailMsg) != '\0') {
13405             DisplayInformation(cmailMsg);
13406         }
13407     }
13408
13409     return;
13410 #endif /* !WIN32 */
13411 }
13412
13413 char *
13414 CmailMsg ()
13415 {
13416 #if WIN32
13417     return NULL;
13418 #else
13419     int  prependComma = 0;
13420     char number[5];
13421     char string[MSG_SIZ];       /* Space for game-list */
13422     int  i;
13423
13424     if (!cmailMsgLoaded) return "";
13425
13426     if (cmailMailedMove) {
13427       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13428     } else {
13429         /* Create a list of games left */
13430       snprintf(string, MSG_SIZ, "[");
13431         for (i = 0; i < nCmailGames; i ++) {
13432             if (! (   cmailMoveRegistered[i]
13433                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13434                 if (prependComma) {
13435                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13436                 } else {
13437                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13438                     prependComma = 1;
13439                 }
13440
13441                 strcat(string, number);
13442             }
13443         }
13444         strcat(string, "]");
13445
13446         if (nCmailMovesRegistered + nCmailResults == 0) {
13447             switch (nCmailGames) {
13448               case 1:
13449                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13450                 break;
13451
13452               case 2:
13453                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13454                 break;
13455
13456               default:
13457                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13458                          nCmailGames);
13459                 break;
13460             }
13461         } else {
13462             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13463               case 1:
13464                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13465                          string);
13466                 break;
13467
13468               case 0:
13469                 if (nCmailResults == nCmailGames) {
13470                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13471                 } else {
13472                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13473                 }
13474                 break;
13475
13476               default:
13477                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13478                          string);
13479             }
13480         }
13481     }
13482     return cmailMsg;
13483 #endif /* WIN32 */
13484 }
13485
13486 void
13487 ResetGameEvent ()
13488 {
13489     if (gameMode == Training)
13490       SetTrainingModeOff();
13491
13492     Reset(TRUE, TRUE);
13493     cmailMsgLoaded = FALSE;
13494     if (appData.icsActive) {
13495       SendToICS(ics_prefix);
13496       SendToICS("refresh\n");
13497     }
13498 }
13499
13500 void
13501 ExitEvent (int status)
13502 {
13503     exiting++;
13504     if (exiting > 2) {
13505       /* Give up on clean exit */
13506       exit(status);
13507     }
13508     if (exiting > 1) {
13509       /* Keep trying for clean exit */
13510       return;
13511     }
13512
13513     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13514
13515     if (telnetISR != NULL) {
13516       RemoveInputSource(telnetISR);
13517     }
13518     if (icsPR != NoProc) {
13519       DestroyChildProcess(icsPR, TRUE);
13520     }
13521
13522     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13523     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13524
13525     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13526     /* make sure this other one finishes before killing it!                  */
13527     if(endingGame) { int count = 0;
13528         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13529         while(endingGame && count++ < 10) DoSleep(1);
13530         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13531     }
13532
13533     /* Kill off chess programs */
13534     if (first.pr != NoProc) {
13535         ExitAnalyzeMode();
13536
13537         DoSleep( appData.delayBeforeQuit );
13538         SendToProgram("quit\n", &first);
13539         DoSleep( appData.delayAfterQuit );
13540         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13541     }
13542     if (second.pr != NoProc) {
13543         DoSleep( appData.delayBeforeQuit );
13544         SendToProgram("quit\n", &second);
13545         DoSleep( appData.delayAfterQuit );
13546         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13547     }
13548     if (first.isr != NULL) {
13549         RemoveInputSource(first.isr);
13550     }
13551     if (second.isr != NULL) {
13552         RemoveInputSource(second.isr);
13553     }
13554
13555     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13556     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13557
13558     ShutDownFrontEnd();
13559     exit(status);
13560 }
13561
13562 void
13563 PauseEngine (ChessProgramState *cps)
13564 {
13565     SendToProgram("pause\n", cps);
13566     cps->pause = 2;
13567 }
13568
13569 void
13570 UnPauseEngine (ChessProgramState *cps)
13571 {
13572     SendToProgram("resume\n", cps);
13573     cps->pause = 1;
13574 }
13575
13576 void
13577 PauseEvent ()
13578 {
13579     if (appData.debugMode)
13580         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13581     if (pausing) {
13582         pausing = FALSE;
13583         ModeHighlight();
13584         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13585             StartClocks();
13586             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13587                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13588                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13589             }
13590             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13591             HandleMachineMove(stashedInputMove, stalledEngine);
13592             stalledEngine = NULL;
13593             return;
13594         }
13595         if (gameMode == MachinePlaysWhite ||
13596             gameMode == TwoMachinesPlay   ||
13597             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13598             if(first.pause)  UnPauseEngine(&first);
13599             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13600             if(second.pause) UnPauseEngine(&second);
13601             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13602             StartClocks();
13603         } else {
13604             DisplayBothClocks();
13605         }
13606         if (gameMode == PlayFromGameFile) {
13607             if (appData.timeDelay >= 0)
13608                 AutoPlayGameLoop();
13609         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13610             Reset(FALSE, TRUE);
13611             SendToICS(ics_prefix);
13612             SendToICS("refresh\n");
13613         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13614             ForwardInner(forwardMostMove);
13615         }
13616         pauseExamInvalid = FALSE;
13617     } else {
13618         switch (gameMode) {
13619           default:
13620             return;
13621           case IcsExamining:
13622             pauseExamForwardMostMove = forwardMostMove;
13623             pauseExamInvalid = FALSE;
13624             /* fall through */
13625           case IcsObserving:
13626           case IcsPlayingWhite:
13627           case IcsPlayingBlack:
13628             pausing = TRUE;
13629             ModeHighlight();
13630             return;
13631           case PlayFromGameFile:
13632             (void) StopLoadGameTimer();
13633             pausing = TRUE;
13634             ModeHighlight();
13635             break;
13636           case BeginningOfGame:
13637             if (appData.icsActive) return;
13638             /* else fall through */
13639           case MachinePlaysWhite:
13640           case MachinePlaysBlack:
13641           case TwoMachinesPlay:
13642             if (forwardMostMove == 0)
13643               return;           /* don't pause if no one has moved */
13644             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13645                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13646                 if(onMove->pause) {           // thinking engine can be paused
13647                     PauseEngine(onMove);      // do it
13648                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13649                         PauseEngine(onMove->other);
13650                     else
13651                         SendToProgram("easy\n", onMove->other);
13652                     StopClocks();
13653                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13654             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13655                 if(first.pause) {
13656                     PauseEngine(&first);
13657                     StopClocks();
13658                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13659             } else { // human on move, pause pondering by either method
13660                 if(first.pause)
13661                     PauseEngine(&first);
13662                 else if(appData.ponderNextMove)
13663                     SendToProgram("easy\n", &first);
13664                 StopClocks();
13665             }
13666             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13667           case AnalyzeMode:
13668             pausing = TRUE;
13669             ModeHighlight();
13670             break;
13671         }
13672     }
13673 }
13674
13675 void
13676 EditCommentEvent ()
13677 {
13678     char title[MSG_SIZ];
13679
13680     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13681       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13682     } else {
13683       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13684                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13685                parseList[currentMove - 1]);
13686     }
13687
13688     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13689 }
13690
13691
13692 void
13693 EditTagsEvent ()
13694 {
13695     char *tags = PGNTags(&gameInfo);
13696     bookUp = FALSE;
13697     EditTagsPopUp(tags, NULL);
13698     free(tags);
13699 }
13700
13701 void
13702 ToggleSecond ()
13703 {
13704   if(second.analyzing) {
13705     SendToProgram("exit\n", &second);
13706     second.analyzing = FALSE;
13707   } else {
13708     if (second.pr == NoProc) StartChessProgram(&second);
13709     InitChessProgram(&second, FALSE);
13710     FeedMovesToProgram(&second, currentMove);
13711
13712     SendToProgram("analyze\n", &second);
13713     second.analyzing = TRUE;
13714   }
13715 }
13716
13717 /* Toggle ShowThinking */
13718 void
13719 ToggleShowThinking()
13720 {
13721   appData.showThinking = !appData.showThinking;
13722   ShowThinkingEvent();
13723 }
13724
13725 int
13726 AnalyzeModeEvent ()
13727 {
13728     char buf[MSG_SIZ];
13729
13730     if (!first.analysisSupport) {
13731       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13732       DisplayError(buf, 0);
13733       return 0;
13734     }
13735     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13736     if (appData.icsActive) {
13737         if (gameMode != IcsObserving) {
13738           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13739             DisplayError(buf, 0);
13740             /* secure check */
13741             if (appData.icsEngineAnalyze) {
13742                 if (appData.debugMode)
13743                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13744                 ExitAnalyzeMode();
13745                 ModeHighlight();
13746             }
13747             return 0;
13748         }
13749         /* if enable, user wants to disable icsEngineAnalyze */
13750         if (appData.icsEngineAnalyze) {
13751                 ExitAnalyzeMode();
13752                 ModeHighlight();
13753                 return 0;
13754         }
13755         appData.icsEngineAnalyze = TRUE;
13756         if (appData.debugMode)
13757             fprintf(debugFP, "ICS engine analyze starting... \n");
13758     }
13759
13760     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13761     if (appData.noChessProgram || gameMode == AnalyzeMode)
13762       return 0;
13763
13764     if (gameMode != AnalyzeFile) {
13765         if (!appData.icsEngineAnalyze) {
13766                EditGameEvent();
13767                if (gameMode != EditGame) return 0;
13768         }
13769         if (!appData.showThinking) ToggleShowThinking();
13770         ResurrectChessProgram();
13771         SendToProgram("analyze\n", &first);
13772         first.analyzing = TRUE;
13773         /*first.maybeThinking = TRUE;*/
13774         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13775         EngineOutputPopUp();
13776     }
13777     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13778     pausing = FALSE;
13779     ModeHighlight();
13780     SetGameInfo();
13781
13782     StartAnalysisClock();
13783     GetTimeMark(&lastNodeCountTime);
13784     lastNodeCount = 0;
13785     return 1;
13786 }
13787
13788 void
13789 AnalyzeFileEvent ()
13790 {
13791     if (appData.noChessProgram || gameMode == AnalyzeFile)
13792       return;
13793
13794     if (!first.analysisSupport) {
13795       char buf[MSG_SIZ];
13796       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13797       DisplayError(buf, 0);
13798       return;
13799     }
13800
13801     if (gameMode != AnalyzeMode) {
13802         keepInfo = 1; // mere annotating should not alter PGN tags
13803         EditGameEvent();
13804         keepInfo = 0;
13805         if (gameMode != EditGame) return;
13806         if (!appData.showThinking) ToggleShowThinking();
13807         ResurrectChessProgram();
13808         SendToProgram("analyze\n", &first);
13809         first.analyzing = TRUE;
13810         /*first.maybeThinking = TRUE;*/
13811         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13812         EngineOutputPopUp();
13813     }
13814     gameMode = AnalyzeFile;
13815     pausing = FALSE;
13816     ModeHighlight();
13817
13818     StartAnalysisClock();
13819     GetTimeMark(&lastNodeCountTime);
13820     lastNodeCount = 0;
13821     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13822     AnalysisPeriodicEvent(1);
13823 }
13824
13825 void
13826 MachineWhiteEvent ()
13827 {
13828     char buf[MSG_SIZ];
13829     char *bookHit = NULL;
13830
13831     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13832       return;
13833
13834
13835     if (gameMode == PlayFromGameFile ||
13836         gameMode == TwoMachinesPlay  ||
13837         gameMode == Training         ||
13838         gameMode == AnalyzeMode      ||
13839         gameMode == EndOfGame)
13840         EditGameEvent();
13841
13842     if (gameMode == EditPosition)
13843         EditPositionDone(TRUE);
13844
13845     if (!WhiteOnMove(currentMove)) {
13846         DisplayError(_("It is not White's turn"), 0);
13847         return;
13848     }
13849
13850     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13851       ExitAnalyzeMode();
13852
13853     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13854         gameMode == AnalyzeFile)
13855         TruncateGame();
13856
13857     ResurrectChessProgram();    /* in case it isn't running */
13858     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13859         gameMode = MachinePlaysWhite;
13860         ResetClocks();
13861     } else
13862     gameMode = MachinePlaysWhite;
13863     pausing = FALSE;
13864     ModeHighlight();
13865     SetGameInfo();
13866     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13867     DisplayTitle(buf);
13868     if (first.sendName) {
13869       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13870       SendToProgram(buf, &first);
13871     }
13872     if (first.sendTime) {
13873       if (first.useColors) {
13874         SendToProgram("black\n", &first); /*gnu kludge*/
13875       }
13876       SendTimeRemaining(&first, TRUE);
13877     }
13878     if (first.useColors) {
13879       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13880     }
13881     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13882     SetMachineThinkingEnables();
13883     first.maybeThinking = TRUE;
13884     StartClocks();
13885     firstMove = FALSE;
13886
13887     if (appData.autoFlipView && !flipView) {
13888       flipView = !flipView;
13889       DrawPosition(FALSE, NULL);
13890       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13891     }
13892
13893     if(bookHit) { // [HGM] book: simulate book reply
13894         static char bookMove[MSG_SIZ]; // a bit generous?
13895
13896         programStats.nodes = programStats.depth = programStats.time =
13897         programStats.score = programStats.got_only_move = 0;
13898         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13899
13900         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13901         strcat(bookMove, bookHit);
13902         HandleMachineMove(bookMove, &first);
13903     }
13904 }
13905
13906 void
13907 MachineBlackEvent ()
13908 {
13909   char buf[MSG_SIZ];
13910   char *bookHit = NULL;
13911
13912     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13913         return;
13914
13915
13916     if (gameMode == PlayFromGameFile ||
13917         gameMode == TwoMachinesPlay  ||
13918         gameMode == Training         ||
13919         gameMode == AnalyzeMode      ||
13920         gameMode == EndOfGame)
13921         EditGameEvent();
13922
13923     if (gameMode == EditPosition)
13924         EditPositionDone(TRUE);
13925
13926     if (WhiteOnMove(currentMove)) {
13927         DisplayError(_("It is not Black's turn"), 0);
13928         return;
13929     }
13930
13931     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13932       ExitAnalyzeMode();
13933
13934     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13935         gameMode == AnalyzeFile)
13936         TruncateGame();
13937
13938     ResurrectChessProgram();    /* in case it isn't running */
13939     gameMode = MachinePlaysBlack;
13940     pausing = FALSE;
13941     ModeHighlight();
13942     SetGameInfo();
13943     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13944     DisplayTitle(buf);
13945     if (first.sendName) {
13946       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13947       SendToProgram(buf, &first);
13948     }
13949     if (first.sendTime) {
13950       if (first.useColors) {
13951         SendToProgram("white\n", &first); /*gnu kludge*/
13952       }
13953       SendTimeRemaining(&first, FALSE);
13954     }
13955     if (first.useColors) {
13956       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13957     }
13958     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13959     SetMachineThinkingEnables();
13960     first.maybeThinking = TRUE;
13961     StartClocks();
13962
13963     if (appData.autoFlipView && flipView) {
13964       flipView = !flipView;
13965       DrawPosition(FALSE, NULL);
13966       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13967     }
13968     if(bookHit) { // [HGM] book: simulate book reply
13969         static char bookMove[MSG_SIZ]; // a bit generous?
13970
13971         programStats.nodes = programStats.depth = programStats.time =
13972         programStats.score = programStats.got_only_move = 0;
13973         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13974
13975         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13976         strcat(bookMove, bookHit);
13977         HandleMachineMove(bookMove, &first);
13978     }
13979 }
13980
13981
13982 void
13983 DisplayTwoMachinesTitle ()
13984 {
13985     char buf[MSG_SIZ];
13986     if (appData.matchGames > 0) {
13987         if(appData.tourneyFile[0]) {
13988           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13989                    gameInfo.white, _("vs."), gameInfo.black,
13990                    nextGame+1, appData.matchGames+1,
13991                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13992         } else
13993         if (first.twoMachinesColor[0] == 'w') {
13994           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13995                    gameInfo.white, _("vs."),  gameInfo.black,
13996                    first.matchWins, second.matchWins,
13997                    matchGame - 1 - (first.matchWins + second.matchWins));
13998         } else {
13999           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14000                    gameInfo.white, _("vs."), gameInfo.black,
14001                    second.matchWins, first.matchWins,
14002                    matchGame - 1 - (first.matchWins + second.matchWins));
14003         }
14004     } else {
14005       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14006     }
14007     DisplayTitle(buf);
14008 }
14009
14010 void
14011 SettingsMenuIfReady ()
14012 {
14013   if (second.lastPing != second.lastPong) {
14014     DisplayMessage("", _("Waiting for second chess program"));
14015     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14016     return;
14017   }
14018   ThawUI();
14019   DisplayMessage("", "");
14020   SettingsPopUp(&second);
14021 }
14022
14023 int
14024 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14025 {
14026     char buf[MSG_SIZ];
14027     if (cps->pr == NoProc) {
14028         StartChessProgram(cps);
14029         if (cps->protocolVersion == 1) {
14030           retry();
14031           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14032         } else {
14033           /* kludge: allow timeout for initial "feature" command */
14034           if(retry != TwoMachinesEventIfReady) FreezeUI();
14035           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14036           DisplayMessage("", buf);
14037           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14038         }
14039         return 1;
14040     }
14041     return 0;
14042 }
14043
14044 void
14045 TwoMachinesEvent P((void))
14046 {
14047     int i;
14048     char buf[MSG_SIZ];
14049     ChessProgramState *onmove;
14050     char *bookHit = NULL;
14051     static int stalling = 0;
14052     TimeMark now;
14053     long wait;
14054
14055     if (appData.noChessProgram) return;
14056
14057     switch (gameMode) {
14058       case TwoMachinesPlay:
14059         return;
14060       case MachinePlaysWhite:
14061       case MachinePlaysBlack:
14062         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14063             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14064             return;
14065         }
14066         /* fall through */
14067       case BeginningOfGame:
14068       case PlayFromGameFile:
14069       case EndOfGame:
14070         EditGameEvent();
14071         if (gameMode != EditGame) return;
14072         break;
14073       case EditPosition:
14074         EditPositionDone(TRUE);
14075         break;
14076       case AnalyzeMode:
14077       case AnalyzeFile:
14078         ExitAnalyzeMode();
14079         break;
14080       case EditGame:
14081       default:
14082         break;
14083     }
14084
14085 //    forwardMostMove = currentMove;
14086     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14087     startingEngine = TRUE;
14088
14089     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14090
14091     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14092     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14093       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14094       return;
14095     }
14096     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14097
14098     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14099         startingEngine = FALSE;
14100         DisplayError("second engine does not play this", 0);
14101         return;
14102     }
14103
14104     if(!stalling) {
14105       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14106       SendToProgram("force\n", &second);
14107       stalling = 1;
14108       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14109       return;
14110     }
14111     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14112     if(appData.matchPause>10000 || appData.matchPause<10)
14113                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14114     wait = SubtractTimeMarks(&now, &pauseStart);
14115     if(wait < appData.matchPause) {
14116         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14117         return;
14118     }
14119     // we are now committed to starting the game
14120     stalling = 0;
14121     DisplayMessage("", "");
14122     if (startedFromSetupPosition) {
14123         SendBoard(&second, backwardMostMove);
14124     if (appData.debugMode) {
14125         fprintf(debugFP, "Two Machines\n");
14126     }
14127     }
14128     for (i = backwardMostMove; i < forwardMostMove; i++) {
14129         SendMoveToProgram(i, &second);
14130     }
14131
14132     gameMode = TwoMachinesPlay;
14133     pausing = startingEngine = FALSE;
14134     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14135     SetGameInfo();
14136     DisplayTwoMachinesTitle();
14137     firstMove = TRUE;
14138     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14139         onmove = &first;
14140     } else {
14141         onmove = &second;
14142     }
14143     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14144     SendToProgram(first.computerString, &first);
14145     if (first.sendName) {
14146       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14147       SendToProgram(buf, &first);
14148     }
14149     SendToProgram(second.computerString, &second);
14150     if (second.sendName) {
14151       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14152       SendToProgram(buf, &second);
14153     }
14154
14155     ResetClocks();
14156     if (!first.sendTime || !second.sendTime) {
14157         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14158         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14159     }
14160     if (onmove->sendTime) {
14161       if (onmove->useColors) {
14162         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14163       }
14164       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14165     }
14166     if (onmove->useColors) {
14167       SendToProgram(onmove->twoMachinesColor, onmove);
14168     }
14169     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14170 //    SendToProgram("go\n", onmove);
14171     onmove->maybeThinking = TRUE;
14172     SetMachineThinkingEnables();
14173
14174     StartClocks();
14175
14176     if(bookHit) { // [HGM] book: simulate book reply
14177         static char bookMove[MSG_SIZ]; // a bit generous?
14178
14179         programStats.nodes = programStats.depth = programStats.time =
14180         programStats.score = programStats.got_only_move = 0;
14181         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14182
14183         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14184         strcat(bookMove, bookHit);
14185         savedMessage = bookMove; // args for deferred call
14186         savedState = onmove;
14187         ScheduleDelayedEvent(DeferredBookMove, 1);
14188     }
14189 }
14190
14191 void
14192 TrainingEvent ()
14193 {
14194     if (gameMode == Training) {
14195       SetTrainingModeOff();
14196       gameMode = PlayFromGameFile;
14197       DisplayMessage("", _("Training mode off"));
14198     } else {
14199       gameMode = Training;
14200       animateTraining = appData.animate;
14201
14202       /* make sure we are not already at the end of the game */
14203       if (currentMove < forwardMostMove) {
14204         SetTrainingModeOn();
14205         DisplayMessage("", _("Training mode on"));
14206       } else {
14207         gameMode = PlayFromGameFile;
14208         DisplayError(_("Already at end of game"), 0);
14209       }
14210     }
14211     ModeHighlight();
14212 }
14213
14214 void
14215 IcsClientEvent ()
14216 {
14217     if (!appData.icsActive) return;
14218     switch (gameMode) {
14219       case IcsPlayingWhite:
14220       case IcsPlayingBlack:
14221       case IcsObserving:
14222       case IcsIdle:
14223       case BeginningOfGame:
14224       case IcsExamining:
14225         return;
14226
14227       case EditGame:
14228         break;
14229
14230       case EditPosition:
14231         EditPositionDone(TRUE);
14232         break;
14233
14234       case AnalyzeMode:
14235       case AnalyzeFile:
14236         ExitAnalyzeMode();
14237         break;
14238
14239       default:
14240         EditGameEvent();
14241         break;
14242     }
14243
14244     gameMode = IcsIdle;
14245     ModeHighlight();
14246     return;
14247 }
14248
14249 void
14250 EditGameEvent ()
14251 {
14252     int i;
14253
14254     switch (gameMode) {
14255       case Training:
14256         SetTrainingModeOff();
14257         break;
14258       case MachinePlaysWhite:
14259       case MachinePlaysBlack:
14260       case BeginningOfGame:
14261         SendToProgram("force\n", &first);
14262         SetUserThinkingEnables();
14263         break;
14264       case PlayFromGameFile:
14265         (void) StopLoadGameTimer();
14266         if (gameFileFP != NULL) {
14267             gameFileFP = NULL;
14268         }
14269         break;
14270       case EditPosition:
14271         EditPositionDone(TRUE);
14272         break;
14273       case AnalyzeMode:
14274       case AnalyzeFile:
14275         ExitAnalyzeMode();
14276         SendToProgram("force\n", &first);
14277         break;
14278       case TwoMachinesPlay:
14279         GameEnds(EndOfFile, NULL, GE_PLAYER);
14280         ResurrectChessProgram();
14281         SetUserThinkingEnables();
14282         break;
14283       case EndOfGame:
14284         ResurrectChessProgram();
14285         break;
14286       case IcsPlayingBlack:
14287       case IcsPlayingWhite:
14288         DisplayError(_("Warning: You are still playing a game"), 0);
14289         break;
14290       case IcsObserving:
14291         DisplayError(_("Warning: You are still observing a game"), 0);
14292         break;
14293       case IcsExamining:
14294         DisplayError(_("Warning: You are still examining a game"), 0);
14295         break;
14296       case IcsIdle:
14297         break;
14298       case EditGame:
14299       default:
14300         return;
14301     }
14302
14303     pausing = FALSE;
14304     StopClocks();
14305     first.offeredDraw = second.offeredDraw = 0;
14306
14307     if (gameMode == PlayFromGameFile) {
14308         whiteTimeRemaining = timeRemaining[0][currentMove];
14309         blackTimeRemaining = timeRemaining[1][currentMove];
14310         DisplayTitle("");
14311     }
14312
14313     if (gameMode == MachinePlaysWhite ||
14314         gameMode == MachinePlaysBlack ||
14315         gameMode == TwoMachinesPlay ||
14316         gameMode == EndOfGame) {
14317         i = forwardMostMove;
14318         while (i > currentMove) {
14319             SendToProgram("undo\n", &first);
14320             i--;
14321         }
14322         if(!adjustedClock) {
14323         whiteTimeRemaining = timeRemaining[0][currentMove];
14324         blackTimeRemaining = timeRemaining[1][currentMove];
14325         DisplayBothClocks();
14326         }
14327         if (whiteFlag || blackFlag) {
14328             whiteFlag = blackFlag = 0;
14329         }
14330         DisplayTitle("");
14331     }
14332
14333     gameMode = EditGame;
14334     ModeHighlight();
14335     SetGameInfo();
14336 }
14337
14338
14339 void
14340 EditPositionEvent ()
14341 {
14342     if (gameMode == EditPosition) {
14343         EditGameEvent();
14344         return;
14345     }
14346
14347     EditGameEvent();
14348     if (gameMode != EditGame) return;
14349
14350     gameMode = EditPosition;
14351     ModeHighlight();
14352     SetGameInfo();
14353     if (currentMove > 0)
14354       CopyBoard(boards[0], boards[currentMove]);
14355
14356     blackPlaysFirst = !WhiteOnMove(currentMove);
14357     ResetClocks();
14358     currentMove = forwardMostMove = backwardMostMove = 0;
14359     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14360     DisplayMove(-1);
14361     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14362 }
14363
14364 void
14365 ExitAnalyzeMode ()
14366 {
14367     /* [DM] icsEngineAnalyze - possible call from other functions */
14368     if (appData.icsEngineAnalyze) {
14369         appData.icsEngineAnalyze = FALSE;
14370
14371         DisplayMessage("",_("Close ICS engine analyze..."));
14372     }
14373     if (first.analysisSupport && first.analyzing) {
14374       SendToBoth("exit\n");
14375       first.analyzing = second.analyzing = FALSE;
14376     }
14377     thinkOutput[0] = NULLCHAR;
14378 }
14379
14380 void
14381 EditPositionDone (Boolean fakeRights)
14382 {
14383     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14384
14385     startedFromSetupPosition = TRUE;
14386     InitChessProgram(&first, FALSE);
14387     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14388       boards[0][EP_STATUS] = EP_NONE;
14389       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14390       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14391         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14392         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14393       } else boards[0][CASTLING][2] = NoRights;
14394       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14395         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14396         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14397       } else boards[0][CASTLING][5] = NoRights;
14398       if(gameInfo.variant == VariantSChess) {
14399         int i;
14400         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14401           boards[0][VIRGIN][i] = 0;
14402           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14403           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14404         }
14405       }
14406     }
14407     SendToProgram("force\n", &first);
14408     if (blackPlaysFirst) {
14409         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14410         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14411         currentMove = forwardMostMove = backwardMostMove = 1;
14412         CopyBoard(boards[1], boards[0]);
14413     } else {
14414         currentMove = forwardMostMove = backwardMostMove = 0;
14415     }
14416     SendBoard(&first, forwardMostMove);
14417     if (appData.debugMode) {
14418         fprintf(debugFP, "EditPosDone\n");
14419     }
14420     DisplayTitle("");
14421     DisplayMessage("", "");
14422     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14423     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14424     gameMode = EditGame;
14425     ModeHighlight();
14426     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14427     ClearHighlights(); /* [AS] */
14428 }
14429
14430 /* Pause for `ms' milliseconds */
14431 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14432 void
14433 TimeDelay (long ms)
14434 {
14435     TimeMark m1, m2;
14436
14437     GetTimeMark(&m1);
14438     do {
14439         GetTimeMark(&m2);
14440     } while (SubtractTimeMarks(&m2, &m1) < ms);
14441 }
14442
14443 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14444 void
14445 SendMultiLineToICS (char *buf)
14446 {
14447     char temp[MSG_SIZ+1], *p;
14448     int len;
14449
14450     len = strlen(buf);
14451     if (len > MSG_SIZ)
14452       len = MSG_SIZ;
14453
14454     strncpy(temp, buf, len);
14455     temp[len] = 0;
14456
14457     p = temp;
14458     while (*p) {
14459         if (*p == '\n' || *p == '\r')
14460           *p = ' ';
14461         ++p;
14462     }
14463
14464     strcat(temp, "\n");
14465     SendToICS(temp);
14466     SendToPlayer(temp, strlen(temp));
14467 }
14468
14469 void
14470 SetWhiteToPlayEvent ()
14471 {
14472     if (gameMode == EditPosition) {
14473         blackPlaysFirst = FALSE;
14474         DisplayBothClocks();    /* works because currentMove is 0 */
14475     } else if (gameMode == IcsExamining) {
14476         SendToICS(ics_prefix);
14477         SendToICS("tomove white\n");
14478     }
14479 }
14480
14481 void
14482 SetBlackToPlayEvent ()
14483 {
14484     if (gameMode == EditPosition) {
14485         blackPlaysFirst = TRUE;
14486         currentMove = 1;        /* kludge */
14487         DisplayBothClocks();
14488         currentMove = 0;
14489     } else if (gameMode == IcsExamining) {
14490         SendToICS(ics_prefix);
14491         SendToICS("tomove black\n");
14492     }
14493 }
14494
14495 void
14496 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14497 {
14498     char buf[MSG_SIZ];
14499     ChessSquare piece = boards[0][y][x];
14500
14501     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14502
14503     switch (selection) {
14504       case ClearBoard:
14505         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14506             SendToICS(ics_prefix);
14507             SendToICS("bsetup clear\n");
14508         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14509             SendToICS(ics_prefix);
14510             SendToICS("clearboard\n");
14511         } else {
14512             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14513                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14514                 for (y = 0; y < BOARD_HEIGHT; y++) {
14515                     if (gameMode == IcsExamining) {
14516                         if (boards[currentMove][y][x] != EmptySquare) {
14517                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14518                                     AAA + x, ONE + y);
14519                             SendToICS(buf);
14520                         }
14521                     } else {
14522                         boards[0][y][x] = p;
14523                     }
14524                 }
14525             }
14526         }
14527         if (gameMode == EditPosition) {
14528             DrawPosition(FALSE, boards[0]);
14529         }
14530         break;
14531
14532       case WhitePlay:
14533         SetWhiteToPlayEvent();
14534         break;
14535
14536       case BlackPlay:
14537         SetBlackToPlayEvent();
14538         break;
14539
14540       case EmptySquare:
14541         if (gameMode == IcsExamining) {
14542             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14543             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14544             SendToICS(buf);
14545         } else {
14546             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14547                 if(x == BOARD_LEFT-2) {
14548                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14549                     boards[0][y][1] = 0;
14550                 } else
14551                 if(x == BOARD_RGHT+1) {
14552                     if(y >= gameInfo.holdingsSize) break;
14553                     boards[0][y][BOARD_WIDTH-2] = 0;
14554                 } else break;
14555             }
14556             boards[0][y][x] = EmptySquare;
14557             DrawPosition(FALSE, boards[0]);
14558         }
14559         break;
14560
14561       case PromotePiece:
14562         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14563            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14564             selection = (ChessSquare) (PROMOTED piece);
14565         } else if(piece == EmptySquare) selection = WhiteSilver;
14566         else selection = (ChessSquare)((int)piece - 1);
14567         goto defaultlabel;
14568
14569       case DemotePiece:
14570         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14571            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14572             selection = (ChessSquare) (DEMOTED piece);
14573         } else if(piece == EmptySquare) selection = BlackSilver;
14574         else selection = (ChessSquare)((int)piece + 1);
14575         goto defaultlabel;
14576
14577       case WhiteQueen:
14578       case BlackQueen:
14579         if(gameInfo.variant == VariantShatranj ||
14580            gameInfo.variant == VariantXiangqi  ||
14581            gameInfo.variant == VariantCourier  ||
14582            gameInfo.variant == VariantASEAN    ||
14583            gameInfo.variant == VariantMakruk     )
14584             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14585         goto defaultlabel;
14586
14587       case WhiteKing:
14588       case BlackKing:
14589         if(gameInfo.variant == VariantXiangqi)
14590             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14591         if(gameInfo.variant == VariantKnightmate)
14592             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14593       default:
14594         defaultlabel:
14595         if (gameMode == IcsExamining) {
14596             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14597             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14598                      PieceToChar(selection), AAA + x, ONE + y);
14599             SendToICS(buf);
14600         } else {
14601             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14602                 int n;
14603                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14604                     n = PieceToNumber(selection - BlackPawn);
14605                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14606                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14607                     boards[0][BOARD_HEIGHT-1-n][1]++;
14608                 } else
14609                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14610                     n = PieceToNumber(selection);
14611                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14612                     boards[0][n][BOARD_WIDTH-1] = selection;
14613                     boards[0][n][BOARD_WIDTH-2]++;
14614                 }
14615             } else
14616             boards[0][y][x] = selection;
14617             DrawPosition(TRUE, boards[0]);
14618             ClearHighlights();
14619             fromX = fromY = -1;
14620         }
14621         break;
14622     }
14623 }
14624
14625
14626 void
14627 DropMenuEvent (ChessSquare selection, int x, int y)
14628 {
14629     ChessMove moveType;
14630
14631     switch (gameMode) {
14632       case IcsPlayingWhite:
14633       case MachinePlaysBlack:
14634         if (!WhiteOnMove(currentMove)) {
14635             DisplayMoveError(_("It is Black's turn"));
14636             return;
14637         }
14638         moveType = WhiteDrop;
14639         break;
14640       case IcsPlayingBlack:
14641       case MachinePlaysWhite:
14642         if (WhiteOnMove(currentMove)) {
14643             DisplayMoveError(_("It is White's turn"));
14644             return;
14645         }
14646         moveType = BlackDrop;
14647         break;
14648       case EditGame:
14649         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14650         break;
14651       default:
14652         return;
14653     }
14654
14655     if (moveType == BlackDrop && selection < BlackPawn) {
14656       selection = (ChessSquare) ((int) selection
14657                                  + (int) BlackPawn - (int) WhitePawn);
14658     }
14659     if (boards[currentMove][y][x] != EmptySquare) {
14660         DisplayMoveError(_("That square is occupied"));
14661         return;
14662     }
14663
14664     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14665 }
14666
14667 void
14668 AcceptEvent ()
14669 {
14670     /* Accept a pending offer of any kind from opponent */
14671
14672     if (appData.icsActive) {
14673         SendToICS(ics_prefix);
14674         SendToICS("accept\n");
14675     } else if (cmailMsgLoaded) {
14676         if (currentMove == cmailOldMove &&
14677             commentList[cmailOldMove] != NULL &&
14678             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14679                    "Black offers a draw" : "White offers a draw")) {
14680             TruncateGame();
14681             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14682             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14683         } else {
14684             DisplayError(_("There is no pending offer on this move"), 0);
14685             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14686         }
14687     } else {
14688         /* Not used for offers from chess program */
14689     }
14690 }
14691
14692 void
14693 DeclineEvent ()
14694 {
14695     /* Decline a pending offer of any kind from opponent */
14696
14697     if (appData.icsActive) {
14698         SendToICS(ics_prefix);
14699         SendToICS("decline\n");
14700     } else if (cmailMsgLoaded) {
14701         if (currentMove == cmailOldMove &&
14702             commentList[cmailOldMove] != NULL &&
14703             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14704                    "Black offers a draw" : "White offers a draw")) {
14705 #ifdef NOTDEF
14706             AppendComment(cmailOldMove, "Draw declined", TRUE);
14707             DisplayComment(cmailOldMove - 1, "Draw declined");
14708 #endif /*NOTDEF*/
14709         } else {
14710             DisplayError(_("There is no pending offer on this move"), 0);
14711         }
14712     } else {
14713         /* Not used for offers from chess program */
14714     }
14715 }
14716
14717 void
14718 RematchEvent ()
14719 {
14720     /* Issue ICS rematch command */
14721     if (appData.icsActive) {
14722         SendToICS(ics_prefix);
14723         SendToICS("rematch\n");
14724     }
14725 }
14726
14727 void
14728 CallFlagEvent ()
14729 {
14730     /* Call your opponent's flag (claim a win on time) */
14731     if (appData.icsActive) {
14732         SendToICS(ics_prefix);
14733         SendToICS("flag\n");
14734     } else {
14735         switch (gameMode) {
14736           default:
14737             return;
14738           case MachinePlaysWhite:
14739             if (whiteFlag) {
14740                 if (blackFlag)
14741                   GameEnds(GameIsDrawn, "Both players ran out of time",
14742                            GE_PLAYER);
14743                 else
14744                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14745             } else {
14746                 DisplayError(_("Your opponent is not out of time"), 0);
14747             }
14748             break;
14749           case MachinePlaysBlack:
14750             if (blackFlag) {
14751                 if (whiteFlag)
14752                   GameEnds(GameIsDrawn, "Both players ran out of time",
14753                            GE_PLAYER);
14754                 else
14755                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14756             } else {
14757                 DisplayError(_("Your opponent is not out of time"), 0);
14758             }
14759             break;
14760         }
14761     }
14762 }
14763
14764 void
14765 ClockClick (int which)
14766 {       // [HGM] code moved to back-end from winboard.c
14767         if(which) { // black clock
14768           if (gameMode == EditPosition || gameMode == IcsExamining) {
14769             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14770             SetBlackToPlayEvent();
14771           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14772           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14773           } else if (shiftKey) {
14774             AdjustClock(which, -1);
14775           } else if (gameMode == IcsPlayingWhite ||
14776                      gameMode == MachinePlaysBlack) {
14777             CallFlagEvent();
14778           }
14779         } else { // white clock
14780           if (gameMode == EditPosition || gameMode == IcsExamining) {
14781             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14782             SetWhiteToPlayEvent();
14783           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14784           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14785           } else if (shiftKey) {
14786             AdjustClock(which, -1);
14787           } else if (gameMode == IcsPlayingBlack ||
14788                    gameMode == MachinePlaysWhite) {
14789             CallFlagEvent();
14790           }
14791         }
14792 }
14793
14794 void
14795 DrawEvent ()
14796 {
14797     /* Offer draw or accept pending draw offer from opponent */
14798
14799     if (appData.icsActive) {
14800         /* Note: tournament rules require draw offers to be
14801            made after you make your move but before you punch
14802            your clock.  Currently ICS doesn't let you do that;
14803            instead, you immediately punch your clock after making
14804            a move, but you can offer a draw at any time. */
14805
14806         SendToICS(ics_prefix);
14807         SendToICS("draw\n");
14808         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14809     } else if (cmailMsgLoaded) {
14810         if (currentMove == cmailOldMove &&
14811             commentList[cmailOldMove] != NULL &&
14812             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14813                    "Black offers a draw" : "White offers a draw")) {
14814             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14815             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14816         } else if (currentMove == cmailOldMove + 1) {
14817             char *offer = WhiteOnMove(cmailOldMove) ?
14818               "White offers a draw" : "Black offers a draw";
14819             AppendComment(currentMove, offer, TRUE);
14820             DisplayComment(currentMove - 1, offer);
14821             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14822         } else {
14823             DisplayError(_("You must make your move before offering a draw"), 0);
14824             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14825         }
14826     } else if (first.offeredDraw) {
14827         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14828     } else {
14829         if (first.sendDrawOffers) {
14830             SendToProgram("draw\n", &first);
14831             userOfferedDraw = TRUE;
14832         }
14833     }
14834 }
14835
14836 void
14837 AdjournEvent ()
14838 {
14839     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14840
14841     if (appData.icsActive) {
14842         SendToICS(ics_prefix);
14843         SendToICS("adjourn\n");
14844     } else {
14845         /* Currently GNU Chess doesn't offer or accept Adjourns */
14846     }
14847 }
14848
14849
14850 void
14851 AbortEvent ()
14852 {
14853     /* Offer Abort or accept pending Abort offer from opponent */
14854
14855     if (appData.icsActive) {
14856         SendToICS(ics_prefix);
14857         SendToICS("abort\n");
14858     } else {
14859         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14860     }
14861 }
14862
14863 void
14864 ResignEvent ()
14865 {
14866     /* Resign.  You can do this even if it's not your turn. */
14867
14868     if (appData.icsActive) {
14869         SendToICS(ics_prefix);
14870         SendToICS("resign\n");
14871     } else {
14872         switch (gameMode) {
14873           case MachinePlaysWhite:
14874             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14875             break;
14876           case MachinePlaysBlack:
14877             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14878             break;
14879           case EditGame:
14880             if (cmailMsgLoaded) {
14881                 TruncateGame();
14882                 if (WhiteOnMove(cmailOldMove)) {
14883                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14884                 } else {
14885                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14886                 }
14887                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14888             }
14889             break;
14890           default:
14891             break;
14892         }
14893     }
14894 }
14895
14896
14897 void
14898 StopObservingEvent ()
14899 {
14900     /* Stop observing current games */
14901     SendToICS(ics_prefix);
14902     SendToICS("unobserve\n");
14903 }
14904
14905 void
14906 StopExaminingEvent ()
14907 {
14908     /* Stop observing current game */
14909     SendToICS(ics_prefix);
14910     SendToICS("unexamine\n");
14911 }
14912
14913 void
14914 ForwardInner (int target)
14915 {
14916     int limit; int oldSeekGraphUp = seekGraphUp;
14917
14918     if (appData.debugMode)
14919         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14920                 target, currentMove, forwardMostMove);
14921
14922     if (gameMode == EditPosition)
14923       return;
14924
14925     seekGraphUp = FALSE;
14926     MarkTargetSquares(1);
14927
14928     if (gameMode == PlayFromGameFile && !pausing)
14929       PauseEvent();
14930
14931     if (gameMode == IcsExamining && pausing)
14932       limit = pauseExamForwardMostMove;
14933     else
14934       limit = forwardMostMove;
14935
14936     if (target > limit) target = limit;
14937
14938     if (target > 0 && moveList[target - 1][0]) {
14939         int fromX, fromY, toX, toY;
14940         toX = moveList[target - 1][2] - AAA;
14941         toY = moveList[target - 1][3] - ONE;
14942         if (moveList[target - 1][1] == '@') {
14943             if (appData.highlightLastMove) {
14944                 SetHighlights(-1, -1, toX, toY);
14945             }
14946         } else {
14947             fromX = moveList[target - 1][0] - AAA;
14948             fromY = moveList[target - 1][1] - ONE;
14949             if (target == currentMove + 1) {
14950                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14951             }
14952             if (appData.highlightLastMove) {
14953                 SetHighlights(fromX, fromY, toX, toY);
14954             }
14955         }
14956     }
14957     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14958         gameMode == Training || gameMode == PlayFromGameFile ||
14959         gameMode == AnalyzeFile) {
14960         while (currentMove < target) {
14961             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14962             SendMoveToProgram(currentMove++, &first);
14963         }
14964     } else {
14965         currentMove = target;
14966     }
14967
14968     if (gameMode == EditGame || gameMode == EndOfGame) {
14969         whiteTimeRemaining = timeRemaining[0][currentMove];
14970         blackTimeRemaining = timeRemaining[1][currentMove];
14971     }
14972     DisplayBothClocks();
14973     DisplayMove(currentMove - 1);
14974     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14975     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14976     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14977         DisplayComment(currentMove - 1, commentList[currentMove]);
14978     }
14979     ClearMap(); // [HGM] exclude: invalidate map
14980 }
14981
14982
14983 void
14984 ForwardEvent ()
14985 {
14986     if (gameMode == IcsExamining && !pausing) {
14987         SendToICS(ics_prefix);
14988         SendToICS("forward\n");
14989     } else {
14990         ForwardInner(currentMove + 1);
14991     }
14992 }
14993
14994 void
14995 ToEndEvent ()
14996 {
14997     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14998         /* to optimze, we temporarily turn off analysis mode while we feed
14999          * the remaining moves to the engine. Otherwise we get analysis output
15000          * after each move.
15001          */
15002         if (first.analysisSupport) {
15003           SendToProgram("exit\nforce\n", &first);
15004           first.analyzing = FALSE;
15005         }
15006     }
15007
15008     if (gameMode == IcsExamining && !pausing) {
15009         SendToICS(ics_prefix);
15010         SendToICS("forward 999999\n");
15011     } else {
15012         ForwardInner(forwardMostMove);
15013     }
15014
15015     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15016         /* we have fed all the moves, so reactivate analysis mode */
15017         SendToProgram("analyze\n", &first);
15018         first.analyzing = TRUE;
15019         /*first.maybeThinking = TRUE;*/
15020         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15021     }
15022 }
15023
15024 void
15025 BackwardInner (int target)
15026 {
15027     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15028
15029     if (appData.debugMode)
15030         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15031                 target, currentMove, forwardMostMove);
15032
15033     if (gameMode == EditPosition) return;
15034     seekGraphUp = FALSE;
15035     MarkTargetSquares(1);
15036     if (currentMove <= backwardMostMove) {
15037         ClearHighlights();
15038         DrawPosition(full_redraw, boards[currentMove]);
15039         return;
15040     }
15041     if (gameMode == PlayFromGameFile && !pausing)
15042       PauseEvent();
15043
15044     if (moveList[target][0]) {
15045         int fromX, fromY, toX, toY;
15046         toX = moveList[target][2] - AAA;
15047         toY = moveList[target][3] - ONE;
15048         if (moveList[target][1] == '@') {
15049             if (appData.highlightLastMove) {
15050                 SetHighlights(-1, -1, toX, toY);
15051             }
15052         } else {
15053             fromX = moveList[target][0] - AAA;
15054             fromY = moveList[target][1] - ONE;
15055             if (target == currentMove - 1) {
15056                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15057             }
15058             if (appData.highlightLastMove) {
15059                 SetHighlights(fromX, fromY, toX, toY);
15060             }
15061         }
15062     }
15063     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15064         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15065         while (currentMove > target) {
15066             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15067                 // null move cannot be undone. Reload program with move history before it.
15068                 int i;
15069                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15070                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15071                 }
15072                 SendBoard(&first, i);
15073               if(second.analyzing) SendBoard(&second, i);
15074                 for(currentMove=i; currentMove<target; currentMove++) {
15075                     SendMoveToProgram(currentMove, &first);
15076                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15077                 }
15078                 break;
15079             }
15080             SendToBoth("undo\n");
15081             currentMove--;
15082         }
15083     } else {
15084         currentMove = target;
15085     }
15086
15087     if (gameMode == EditGame || gameMode == EndOfGame) {
15088         whiteTimeRemaining = timeRemaining[0][currentMove];
15089         blackTimeRemaining = timeRemaining[1][currentMove];
15090     }
15091     DisplayBothClocks();
15092     DisplayMove(currentMove - 1);
15093     DrawPosition(full_redraw, boards[currentMove]);
15094     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15095     // [HGM] PV info: routine tests if comment empty
15096     DisplayComment(currentMove - 1, commentList[currentMove]);
15097     ClearMap(); // [HGM] exclude: invalidate map
15098 }
15099
15100 void
15101 BackwardEvent ()
15102 {
15103     if (gameMode == IcsExamining && !pausing) {
15104         SendToICS(ics_prefix);
15105         SendToICS("backward\n");
15106     } else {
15107         BackwardInner(currentMove - 1);
15108     }
15109 }
15110
15111 void
15112 ToStartEvent ()
15113 {
15114     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15115         /* to optimize, we temporarily turn off analysis mode while we undo
15116          * all the moves. Otherwise we get analysis output after each undo.
15117          */
15118         if (first.analysisSupport) {
15119           SendToProgram("exit\nforce\n", &first);
15120           first.analyzing = FALSE;
15121         }
15122     }
15123
15124     if (gameMode == IcsExamining && !pausing) {
15125         SendToICS(ics_prefix);
15126         SendToICS("backward 999999\n");
15127     } else {
15128         BackwardInner(backwardMostMove);
15129     }
15130
15131     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15132         /* we have fed all the moves, so reactivate analysis mode */
15133         SendToProgram("analyze\n", &first);
15134         first.analyzing = TRUE;
15135         /*first.maybeThinking = TRUE;*/
15136         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15137     }
15138 }
15139
15140 void
15141 ToNrEvent (int to)
15142 {
15143   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15144   if (to >= forwardMostMove) to = forwardMostMove;
15145   if (to <= backwardMostMove) to = backwardMostMove;
15146   if (to < currentMove) {
15147     BackwardInner(to);
15148   } else {
15149     ForwardInner(to);
15150   }
15151 }
15152
15153 void
15154 RevertEvent (Boolean annotate)
15155 {
15156     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15157         return;
15158     }
15159     if (gameMode != IcsExamining) {
15160         DisplayError(_("You are not examining a game"), 0);
15161         return;
15162     }
15163     if (pausing) {
15164         DisplayError(_("You can't revert while pausing"), 0);
15165         return;
15166     }
15167     SendToICS(ics_prefix);
15168     SendToICS("revert\n");
15169 }
15170
15171 void
15172 RetractMoveEvent ()
15173 {
15174     switch (gameMode) {
15175       case MachinePlaysWhite:
15176       case MachinePlaysBlack:
15177         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15178             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15179             return;
15180         }
15181         if (forwardMostMove < 2) return;
15182         currentMove = forwardMostMove = forwardMostMove - 2;
15183         whiteTimeRemaining = timeRemaining[0][currentMove];
15184         blackTimeRemaining = timeRemaining[1][currentMove];
15185         DisplayBothClocks();
15186         DisplayMove(currentMove - 1);
15187         ClearHighlights();/*!! could figure this out*/
15188         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15189         SendToProgram("remove\n", &first);
15190         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15191         break;
15192
15193       case BeginningOfGame:
15194       default:
15195         break;
15196
15197       case IcsPlayingWhite:
15198       case IcsPlayingBlack:
15199         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15200             SendToICS(ics_prefix);
15201             SendToICS("takeback 2\n");
15202         } else {
15203             SendToICS(ics_prefix);
15204             SendToICS("takeback 1\n");
15205         }
15206         break;
15207     }
15208 }
15209
15210 void
15211 MoveNowEvent ()
15212 {
15213     ChessProgramState *cps;
15214
15215     switch (gameMode) {
15216       case MachinePlaysWhite:
15217         if (!WhiteOnMove(forwardMostMove)) {
15218             DisplayError(_("It is your turn"), 0);
15219             return;
15220         }
15221         cps = &first;
15222         break;
15223       case MachinePlaysBlack:
15224         if (WhiteOnMove(forwardMostMove)) {
15225             DisplayError(_("It is your turn"), 0);
15226             return;
15227         }
15228         cps = &first;
15229         break;
15230       case TwoMachinesPlay:
15231         if (WhiteOnMove(forwardMostMove) ==
15232             (first.twoMachinesColor[0] == 'w')) {
15233             cps = &first;
15234         } else {
15235             cps = &second;
15236         }
15237         break;
15238       case BeginningOfGame:
15239       default:
15240         return;
15241     }
15242     SendToProgram("?\n", cps);
15243 }
15244
15245 void
15246 TruncateGameEvent ()
15247 {
15248     EditGameEvent();
15249     if (gameMode != EditGame) return;
15250     TruncateGame();
15251 }
15252
15253 void
15254 TruncateGame ()
15255 {
15256     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15257     if (forwardMostMove > currentMove) {
15258         if (gameInfo.resultDetails != NULL) {
15259             free(gameInfo.resultDetails);
15260             gameInfo.resultDetails = NULL;
15261             gameInfo.result = GameUnfinished;
15262         }
15263         forwardMostMove = currentMove;
15264         HistorySet(parseList, backwardMostMove, forwardMostMove,
15265                    currentMove-1);
15266     }
15267 }
15268
15269 void
15270 HintEvent ()
15271 {
15272     if (appData.noChessProgram) return;
15273     switch (gameMode) {
15274       case MachinePlaysWhite:
15275         if (WhiteOnMove(forwardMostMove)) {
15276             DisplayError(_("Wait until your turn"), 0);
15277             return;
15278         }
15279         break;
15280       case BeginningOfGame:
15281       case MachinePlaysBlack:
15282         if (!WhiteOnMove(forwardMostMove)) {
15283             DisplayError(_("Wait until your turn"), 0);
15284             return;
15285         }
15286         break;
15287       default:
15288         DisplayError(_("No hint available"), 0);
15289         return;
15290     }
15291     SendToProgram("hint\n", &first);
15292     hintRequested = TRUE;
15293 }
15294
15295 void
15296 CreateBookEvent ()
15297 {
15298     ListGame * lg = (ListGame *) gameList.head;
15299     FILE *f, *g;
15300     int nItem;
15301     static int secondTime = FALSE;
15302
15303     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15304         DisplayError(_("Game list not loaded or empty"), 0);
15305         return;
15306     }
15307
15308     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15309         fclose(g);
15310         secondTime++;
15311         DisplayNote(_("Book file exists! Try again for overwrite."));
15312         return;
15313     }
15314
15315     creatingBook = TRUE;
15316     secondTime = FALSE;
15317
15318     /* Get list size */
15319     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15320         LoadGame(f, nItem, "", TRUE);
15321         AddGameToBook(TRUE);
15322         lg = (ListGame *) lg->node.succ;
15323     }
15324
15325     creatingBook = FALSE;
15326     FlushBook();
15327 }
15328
15329 void
15330 BookEvent ()
15331 {
15332     if (appData.noChessProgram) return;
15333     switch (gameMode) {
15334       case MachinePlaysWhite:
15335         if (WhiteOnMove(forwardMostMove)) {
15336             DisplayError(_("Wait until your turn"), 0);
15337             return;
15338         }
15339         break;
15340       case BeginningOfGame:
15341       case MachinePlaysBlack:
15342         if (!WhiteOnMove(forwardMostMove)) {
15343             DisplayError(_("Wait until your turn"), 0);
15344             return;
15345         }
15346         break;
15347       case EditPosition:
15348         EditPositionDone(TRUE);
15349         break;
15350       case TwoMachinesPlay:
15351         return;
15352       default:
15353         break;
15354     }
15355     SendToProgram("bk\n", &first);
15356     bookOutput[0] = NULLCHAR;
15357     bookRequested = TRUE;
15358 }
15359
15360 void
15361 AboutGameEvent ()
15362 {
15363     char *tags = PGNTags(&gameInfo);
15364     TagsPopUp(tags, CmailMsg());
15365     free(tags);
15366 }
15367
15368 /* end button procedures */
15369
15370 void
15371 PrintPosition (FILE *fp, int move)
15372 {
15373     int i, j;
15374
15375     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15376         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15377             char c = PieceToChar(boards[move][i][j]);
15378             fputc(c == 'x' ? '.' : c, fp);
15379             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15380         }
15381     }
15382     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15383       fprintf(fp, "white to play\n");
15384     else
15385       fprintf(fp, "black to play\n");
15386 }
15387
15388 void
15389 PrintOpponents (FILE *fp)
15390 {
15391     if (gameInfo.white != NULL) {
15392         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15393     } else {
15394         fprintf(fp, "\n");
15395     }
15396 }
15397
15398 /* Find last component of program's own name, using some heuristics */
15399 void
15400 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15401 {
15402     char *p, *q, c;
15403     int local = (strcmp(host, "localhost") == 0);
15404     while (!local && (p = strchr(prog, ';')) != NULL) {
15405         p++;
15406         while (*p == ' ') p++;
15407         prog = p;
15408     }
15409     if (*prog == '"' || *prog == '\'') {
15410         q = strchr(prog + 1, *prog);
15411     } else {
15412         q = strchr(prog, ' ');
15413     }
15414     if (q == NULL) q = prog + strlen(prog);
15415     p = q;
15416     while (p >= prog && *p != '/' && *p != '\\') p--;
15417     p++;
15418     if(p == prog && *p == '"') p++;
15419     c = *q; *q = 0;
15420     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15421     memcpy(buf, p, q - p);
15422     buf[q - p] = NULLCHAR;
15423     if (!local) {
15424         strcat(buf, "@");
15425         strcat(buf, host);
15426     }
15427 }
15428
15429 char *
15430 TimeControlTagValue ()
15431 {
15432     char buf[MSG_SIZ];
15433     if (!appData.clockMode) {
15434       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15435     } else if (movesPerSession > 0) {
15436       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15437     } else if (timeIncrement == 0) {
15438       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15439     } else {
15440       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15441     }
15442     return StrSave(buf);
15443 }
15444
15445 void
15446 SetGameInfo ()
15447 {
15448     /* This routine is used only for certain modes */
15449     VariantClass v = gameInfo.variant;
15450     ChessMove r = GameUnfinished;
15451     char *p = NULL;
15452
15453     if(keepInfo) return;
15454
15455     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15456         r = gameInfo.result;
15457         p = gameInfo.resultDetails;
15458         gameInfo.resultDetails = NULL;
15459     }
15460     ClearGameInfo(&gameInfo);
15461     gameInfo.variant = v;
15462
15463     switch (gameMode) {
15464       case MachinePlaysWhite:
15465         gameInfo.event = StrSave( appData.pgnEventHeader );
15466         gameInfo.site = StrSave(HostName());
15467         gameInfo.date = PGNDate();
15468         gameInfo.round = StrSave("-");
15469         gameInfo.white = StrSave(first.tidy);
15470         gameInfo.black = StrSave(UserName());
15471         gameInfo.timeControl = TimeControlTagValue();
15472         break;
15473
15474       case MachinePlaysBlack:
15475         gameInfo.event = StrSave( appData.pgnEventHeader );
15476         gameInfo.site = StrSave(HostName());
15477         gameInfo.date = PGNDate();
15478         gameInfo.round = StrSave("-");
15479         gameInfo.white = StrSave(UserName());
15480         gameInfo.black = StrSave(first.tidy);
15481         gameInfo.timeControl = TimeControlTagValue();
15482         break;
15483
15484       case TwoMachinesPlay:
15485         gameInfo.event = StrSave( appData.pgnEventHeader );
15486         gameInfo.site = StrSave(HostName());
15487         gameInfo.date = PGNDate();
15488         if (roundNr > 0) {
15489             char buf[MSG_SIZ];
15490             snprintf(buf, MSG_SIZ, "%d", roundNr);
15491             gameInfo.round = StrSave(buf);
15492         } else {
15493             gameInfo.round = StrSave("-");
15494         }
15495         if (first.twoMachinesColor[0] == 'w') {
15496             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15497             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15498         } else {
15499             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15500             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15501         }
15502         gameInfo.timeControl = TimeControlTagValue();
15503         break;
15504
15505       case EditGame:
15506         gameInfo.event = StrSave("Edited game");
15507         gameInfo.site = StrSave(HostName());
15508         gameInfo.date = PGNDate();
15509         gameInfo.round = StrSave("-");
15510         gameInfo.white = StrSave("-");
15511         gameInfo.black = StrSave("-");
15512         gameInfo.result = r;
15513         gameInfo.resultDetails = p;
15514         break;
15515
15516       case EditPosition:
15517         gameInfo.event = StrSave("Edited position");
15518         gameInfo.site = StrSave(HostName());
15519         gameInfo.date = PGNDate();
15520         gameInfo.round = StrSave("-");
15521         gameInfo.white = StrSave("-");
15522         gameInfo.black = StrSave("-");
15523         break;
15524
15525       case IcsPlayingWhite:
15526       case IcsPlayingBlack:
15527       case IcsObserving:
15528       case IcsExamining:
15529         break;
15530
15531       case PlayFromGameFile:
15532         gameInfo.event = StrSave("Game from non-PGN file");
15533         gameInfo.site = StrSave(HostName());
15534         gameInfo.date = PGNDate();
15535         gameInfo.round = StrSave("-");
15536         gameInfo.white = StrSave("?");
15537         gameInfo.black = StrSave("?");
15538         break;
15539
15540       default:
15541         break;
15542     }
15543 }
15544
15545 void
15546 ReplaceComment (int index, char *text)
15547 {
15548     int len;
15549     char *p;
15550     float score;
15551
15552     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15553        pvInfoList[index-1].depth == len &&
15554        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15555        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15556     while (*text == '\n') text++;
15557     len = strlen(text);
15558     while (len > 0 && text[len - 1] == '\n') len--;
15559
15560     if (commentList[index] != NULL)
15561       free(commentList[index]);
15562
15563     if (len == 0) {
15564         commentList[index] = NULL;
15565         return;
15566     }
15567   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15568       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15569       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15570     commentList[index] = (char *) malloc(len + 2);
15571     strncpy(commentList[index], text, len);
15572     commentList[index][len] = '\n';
15573     commentList[index][len + 1] = NULLCHAR;
15574   } else {
15575     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15576     char *p;
15577     commentList[index] = (char *) malloc(len + 7);
15578     safeStrCpy(commentList[index], "{\n", 3);
15579     safeStrCpy(commentList[index]+2, text, len+1);
15580     commentList[index][len+2] = NULLCHAR;
15581     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15582     strcat(commentList[index], "\n}\n");
15583   }
15584 }
15585
15586 void
15587 CrushCRs (char *text)
15588 {
15589   char *p = text;
15590   char *q = text;
15591   char ch;
15592
15593   do {
15594     ch = *p++;
15595     if (ch == '\r') continue;
15596     *q++ = ch;
15597   } while (ch != '\0');
15598 }
15599
15600 void
15601 AppendComment (int index, char *text, Boolean addBraces)
15602 /* addBraces  tells if we should add {} */
15603 {
15604     int oldlen, len;
15605     char *old;
15606
15607 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15608     if(addBraces == 3) addBraces = 0; else // force appending literally
15609     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15610
15611     CrushCRs(text);
15612     while (*text == '\n') text++;
15613     len = strlen(text);
15614     while (len > 0 && text[len - 1] == '\n') len--;
15615     text[len] = NULLCHAR;
15616
15617     if (len == 0) return;
15618
15619     if (commentList[index] != NULL) {
15620       Boolean addClosingBrace = addBraces;
15621         old = commentList[index];
15622         oldlen = strlen(old);
15623         while(commentList[index][oldlen-1] ==  '\n')
15624           commentList[index][--oldlen] = NULLCHAR;
15625         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15626         safeStrCpy(commentList[index], old, oldlen + len + 6);
15627         free(old);
15628         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15629         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15630           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15631           while (*text == '\n') { text++; len--; }
15632           commentList[index][--oldlen] = NULLCHAR;
15633       }
15634         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15635         else          strcat(commentList[index], "\n");
15636         strcat(commentList[index], text);
15637         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15638         else          strcat(commentList[index], "\n");
15639     } else {
15640         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15641         if(addBraces)
15642           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15643         else commentList[index][0] = NULLCHAR;
15644         strcat(commentList[index], text);
15645         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15646         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15647     }
15648 }
15649
15650 static char *
15651 FindStr (char * text, char * sub_text)
15652 {
15653     char * result = strstr( text, sub_text );
15654
15655     if( result != NULL ) {
15656         result += strlen( sub_text );
15657     }
15658
15659     return result;
15660 }
15661
15662 /* [AS] Try to extract PV info from PGN comment */
15663 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15664 char *
15665 GetInfoFromComment (int index, char * text)
15666 {
15667     char * sep = text, *p;
15668
15669     if( text != NULL && index > 0 ) {
15670         int score = 0;
15671         int depth = 0;
15672         int time = -1, sec = 0, deci;
15673         char * s_eval = FindStr( text, "[%eval " );
15674         char * s_emt = FindStr( text, "[%emt " );
15675 #if 0
15676         if( s_eval != NULL || s_emt != NULL ) {
15677 #else
15678         if(0) { // [HGM] this code is not finished, and could actually be detrimental
15679 #endif
15680             /* New style */
15681             char delim;
15682
15683             if( s_eval != NULL ) {
15684                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15685                     return text;
15686                 }
15687
15688                 if( delim != ']' ) {
15689                     return text;
15690                 }
15691             }
15692
15693             if( s_emt != NULL ) {
15694             }
15695                 return text;
15696         }
15697         else {
15698             /* We expect something like: [+|-]nnn.nn/dd */
15699             int score_lo = 0;
15700
15701             if(*text != '{') return text; // [HGM] braces: must be normal comment
15702
15703             sep = strchr( text, '/' );
15704             if( sep == NULL || sep < (text+4) ) {
15705                 return text;
15706             }
15707
15708             p = text;
15709             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15710             if(p[1] == '(') { // comment starts with PV
15711                p = strchr(p, ')'); // locate end of PV
15712                if(p == NULL || sep < p+5) return text;
15713                // at this point we have something like "{(.*) +0.23/6 ..."
15714                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15715                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15716                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15717             }
15718             time = -1; sec = -1; deci = -1;
15719             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15720                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15721                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15722                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15723                 return text;
15724             }
15725
15726             if( score_lo < 0 || score_lo >= 100 ) {
15727                 return text;
15728             }
15729
15730             if(sec >= 0) time = 600*time + 10*sec; else
15731             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15732
15733             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
15734
15735             /* [HGM] PV time: now locate end of PV info */
15736             while( *++sep >= '0' && *sep <= '9'); // strip depth
15737             if(time >= 0)
15738             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15739             if(sec >= 0)
15740             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15741             if(deci >= 0)
15742             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15743             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15744         }
15745
15746         if( depth <= 0 ) {
15747             return text;
15748         }
15749
15750         if( time < 0 ) {
15751             time = -1;
15752         }
15753
15754         pvInfoList[index-1].depth = depth;
15755         pvInfoList[index-1].score = score;
15756         pvInfoList[index-1].time  = 10*time; // centi-sec
15757         if(*sep == '}') *sep = 0; else *--sep = '{';
15758         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15759     }
15760     return sep;
15761 }
15762
15763 void
15764 SendToProgram (char *message, ChessProgramState *cps)
15765 {
15766     int count, outCount, error;
15767     char buf[MSG_SIZ];
15768
15769     if (cps->pr == NoProc) return;
15770     Attention(cps);
15771
15772     if (appData.debugMode) {
15773         TimeMark now;
15774         GetTimeMark(&now);
15775         fprintf(debugFP, "%ld >%-6s: %s",
15776                 SubtractTimeMarks(&now, &programStartTime),
15777                 cps->which, message);
15778         if(serverFP)
15779             fprintf(serverFP, "%ld >%-6s: %s",
15780                 SubtractTimeMarks(&now, &programStartTime),
15781                 cps->which, message), fflush(serverFP);
15782     }
15783
15784     count = strlen(message);
15785     outCount = OutputToProcess(cps->pr, message, count, &error);
15786     if (outCount < count && !exiting
15787                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15788       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15789       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15790         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15791             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15792                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15793                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15794                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15795             } else {
15796                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15797                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15798                 gameInfo.result = res;
15799             }
15800             gameInfo.resultDetails = StrSave(buf);
15801         }
15802         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15803         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15804     }
15805 }
15806
15807 void
15808 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15809 {
15810     char *end_str;
15811     char buf[MSG_SIZ];
15812     ChessProgramState *cps = (ChessProgramState *)closure;
15813
15814     if (isr != cps->isr) return; /* Killed intentionally */
15815     if (count <= 0) {
15816         if (count == 0) {
15817             RemoveInputSource(cps->isr);
15818             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15819                     _(cps->which), cps->program);
15820             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15821             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15822                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15823                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15824                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15825                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15826                 } else {
15827                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15828                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15829                     gameInfo.result = res;
15830                 }
15831                 gameInfo.resultDetails = StrSave(buf);
15832             }
15833             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15834             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15835         } else {
15836             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15837                     _(cps->which), cps->program);
15838             RemoveInputSource(cps->isr);
15839
15840             /* [AS] Program is misbehaving badly... kill it */
15841             if( count == -2 ) {
15842                 DestroyChildProcess( cps->pr, 9 );
15843                 cps->pr = NoProc;
15844             }
15845
15846             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15847         }
15848         return;
15849     }
15850
15851     if ((end_str = strchr(message, '\r')) != NULL)
15852       *end_str = NULLCHAR;
15853     if ((end_str = strchr(message, '\n')) != NULL)
15854       *end_str = NULLCHAR;
15855
15856     if (appData.debugMode) {
15857         TimeMark now; int print = 1;
15858         char *quote = ""; char c; int i;
15859
15860         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15861                 char start = message[0];
15862                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15863                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15864                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15865                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15866                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15867                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15868                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15869                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15870                    sscanf(message, "hint: %c", &c)!=1 &&
15871                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15872                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15873                     print = (appData.engineComments >= 2);
15874                 }
15875                 message[0] = start; // restore original message
15876         }
15877         if(print) {
15878                 GetTimeMark(&now);
15879                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15880                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15881                         quote,
15882                         message);
15883                 if(serverFP)
15884                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15885                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15886                         quote,
15887                         message), fflush(serverFP);
15888         }
15889     }
15890
15891     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15892     if (appData.icsEngineAnalyze) {
15893         if (strstr(message, "whisper") != NULL ||
15894              strstr(message, "kibitz") != NULL ||
15895             strstr(message, "tellics") != NULL) return;
15896     }
15897
15898     HandleMachineMove(message, cps);
15899 }
15900
15901
15902 void
15903 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15904 {
15905     char buf[MSG_SIZ];
15906     int seconds;
15907
15908     if( timeControl_2 > 0 ) {
15909         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15910             tc = timeControl_2;
15911         }
15912     }
15913     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15914     inc /= cps->timeOdds;
15915     st  /= cps->timeOdds;
15916
15917     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15918
15919     if (st > 0) {
15920       /* Set exact time per move, normally using st command */
15921       if (cps->stKludge) {
15922         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15923         seconds = st % 60;
15924         if (seconds == 0) {
15925           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15926         } else {
15927           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15928         }
15929       } else {
15930         snprintf(buf, MSG_SIZ, "st %d\n", st);
15931       }
15932     } else {
15933       /* Set conventional or incremental time control, using level command */
15934       if (seconds == 0) {
15935         /* Note old gnuchess bug -- minutes:seconds used to not work.
15936            Fixed in later versions, but still avoid :seconds
15937            when seconds is 0. */
15938         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15939       } else {
15940         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15941                  seconds, inc/1000.);
15942       }
15943     }
15944     SendToProgram(buf, cps);
15945
15946     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15947     /* Orthogonally, limit search to given depth */
15948     if (sd > 0) {
15949       if (cps->sdKludge) {
15950         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15951       } else {
15952         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15953       }
15954       SendToProgram(buf, cps);
15955     }
15956
15957     if(cps->nps >= 0) { /* [HGM] nps */
15958         if(cps->supportsNPS == FALSE)
15959           cps->nps = -1; // don't use if engine explicitly says not supported!
15960         else {
15961           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15962           SendToProgram(buf, cps);
15963         }
15964     }
15965 }
15966
15967 ChessProgramState *
15968 WhitePlayer ()
15969 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15970 {
15971     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15972        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15973         return &second;
15974     return &first;
15975 }
15976
15977 void
15978 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15979 {
15980     char message[MSG_SIZ];
15981     long time, otime;
15982
15983     /* Note: this routine must be called when the clocks are stopped
15984        or when they have *just* been set or switched; otherwise
15985        it will be off by the time since the current tick started.
15986     */
15987     if (machineWhite) {
15988         time = whiteTimeRemaining / 10;
15989         otime = blackTimeRemaining / 10;
15990     } else {
15991         time = blackTimeRemaining / 10;
15992         otime = whiteTimeRemaining / 10;
15993     }
15994     /* [HGM] translate opponent's time by time-odds factor */
15995     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15996
15997     if (time <= 0) time = 1;
15998     if (otime <= 0) otime = 1;
15999
16000     snprintf(message, MSG_SIZ, "time %ld\n", time);
16001     SendToProgram(message, cps);
16002
16003     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16004     SendToProgram(message, cps);
16005 }
16006
16007 int
16008 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16009 {
16010   char buf[MSG_SIZ];
16011   int len = strlen(name);
16012   int val;
16013
16014   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16015     (*p) += len + 1;
16016     sscanf(*p, "%d", &val);
16017     *loc = (val != 0);
16018     while (**p && **p != ' ')
16019       (*p)++;
16020     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16021     SendToProgram(buf, cps);
16022     return TRUE;
16023   }
16024   return FALSE;
16025 }
16026
16027 int
16028 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16029 {
16030   char buf[MSG_SIZ];
16031   int len = strlen(name);
16032   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16033     (*p) += len + 1;
16034     sscanf(*p, "%d", loc);
16035     while (**p && **p != ' ') (*p)++;
16036     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16037     SendToProgram(buf, cps);
16038     return TRUE;
16039   }
16040   return FALSE;
16041 }
16042
16043 int
16044 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16045 {
16046   char buf[MSG_SIZ];
16047   int len = strlen(name);
16048   if (strncmp((*p), name, len) == 0
16049       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16050     (*p) += len + 2;
16051     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16052     sscanf(*p, "%[^\"]", *loc);
16053     while (**p && **p != '\"') (*p)++;
16054     if (**p == '\"') (*p)++;
16055     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16056     SendToProgram(buf, cps);
16057     return TRUE;
16058   }
16059   return FALSE;
16060 }
16061
16062 int
16063 ParseOption (Option *opt, ChessProgramState *cps)
16064 // [HGM] options: process the string that defines an engine option, and determine
16065 // name, type, default value, and allowed value range
16066 {
16067         char *p, *q, buf[MSG_SIZ];
16068         int n, min = (-1)<<31, max = 1<<31, def;
16069
16070         if(p = strstr(opt->name, " -spin ")) {
16071             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16072             if(max < min) max = min; // enforce consistency
16073             if(def < min) def = min;
16074             if(def > max) def = max;
16075             opt->value = def;
16076             opt->min = min;
16077             opt->max = max;
16078             opt->type = Spin;
16079         } else if((p = strstr(opt->name, " -slider "))) {
16080             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16081             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16082             if(max < min) max = min; // enforce consistency
16083             if(def < min) def = min;
16084             if(def > max) def = max;
16085             opt->value = def;
16086             opt->min = min;
16087             opt->max = max;
16088             opt->type = Spin; // Slider;
16089         } else if((p = strstr(opt->name, " -string "))) {
16090             opt->textValue = p+9;
16091             opt->type = TextBox;
16092         } else if((p = strstr(opt->name, " -file "))) {
16093             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16094             opt->textValue = p+7;
16095             opt->type = FileName; // FileName;
16096         } else if((p = strstr(opt->name, " -path "))) {
16097             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16098             opt->textValue = p+7;
16099             opt->type = PathName; // PathName;
16100         } else if(p = strstr(opt->name, " -check ")) {
16101             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16102             opt->value = (def != 0);
16103             opt->type = CheckBox;
16104         } else if(p = strstr(opt->name, " -combo ")) {
16105             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16106             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16107             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16108             opt->value = n = 0;
16109             while(q = StrStr(q, " /// ")) {
16110                 n++; *q = 0;    // count choices, and null-terminate each of them
16111                 q += 5;
16112                 if(*q == '*') { // remember default, which is marked with * prefix
16113                     q++;
16114                     opt->value = n;
16115                 }
16116                 cps->comboList[cps->comboCnt++] = q;
16117             }
16118             cps->comboList[cps->comboCnt++] = NULL;
16119             opt->max = n + 1;
16120             opt->type = ComboBox;
16121         } else if(p = strstr(opt->name, " -button")) {
16122             opt->type = Button;
16123         } else if(p = strstr(opt->name, " -save")) {
16124             opt->type = SaveButton;
16125         } else return FALSE;
16126         *p = 0; // terminate option name
16127         // now look if the command-line options define a setting for this engine option.
16128         if(cps->optionSettings && cps->optionSettings[0])
16129             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16130         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16131           snprintf(buf, MSG_SIZ, "option %s", p);
16132                 if(p = strstr(buf, ",")) *p = 0;
16133                 if(q = strchr(buf, '=')) switch(opt->type) {
16134                     case ComboBox:
16135                         for(n=0; n<opt->max; n++)
16136                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16137                         break;
16138                     case TextBox:
16139                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16140                         break;
16141                     case Spin:
16142                     case CheckBox:
16143                         opt->value = atoi(q+1);
16144                     default:
16145                         break;
16146                 }
16147                 strcat(buf, "\n");
16148                 SendToProgram(buf, cps);
16149         }
16150         return TRUE;
16151 }
16152
16153 void
16154 FeatureDone (ChessProgramState *cps, int val)
16155 {
16156   DelayedEventCallback cb = GetDelayedEvent();
16157   if ((cb == InitBackEnd3 && cps == &first) ||
16158       (cb == SettingsMenuIfReady && cps == &second) ||
16159       (cb == LoadEngine) ||
16160       (cb == TwoMachinesEventIfReady)) {
16161     CancelDelayedEvent();
16162     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16163   }
16164   cps->initDone = val;
16165   if(val) cps->reload = FALSE;
16166 }
16167
16168 /* Parse feature command from engine */
16169 void
16170 ParseFeatures (char *args, ChessProgramState *cps)
16171 {
16172   char *p = args;
16173   char *q = NULL;
16174   int val;
16175   char buf[MSG_SIZ];
16176
16177   for (;;) {
16178     while (*p == ' ') p++;
16179     if (*p == NULLCHAR) return;
16180
16181     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16182     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16183     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16184     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16185     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16186     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16187     if (BoolFeature(&p, "reuse", &val, cps)) {
16188       /* Engine can disable reuse, but can't enable it if user said no */
16189       if (!val) cps->reuse = FALSE;
16190       continue;
16191     }
16192     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16193     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16194       if (gameMode == TwoMachinesPlay) {
16195         DisplayTwoMachinesTitle();
16196       } else {
16197         DisplayTitle("");
16198       }
16199       continue;
16200     }
16201     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16202     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16203     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16204     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16205     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16206     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16207     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16208     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16209     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16210     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16211     if (IntFeature(&p, "done", &val, cps)) {
16212       FeatureDone(cps, val);
16213       continue;
16214     }
16215     /* Added by Tord: */
16216     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16217     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16218     /* End of additions by Tord */
16219
16220     /* [HGM] added features: */
16221     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16222     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16223     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16224     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16225     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16226     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16227     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16228     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16229         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16230         FREE(cps->option[cps->nrOptions].name);
16231         cps->option[cps->nrOptions].name = q; q = NULL;
16232         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16233           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16234             SendToProgram(buf, cps);
16235             continue;
16236         }
16237         if(cps->nrOptions >= MAX_OPTIONS) {
16238             cps->nrOptions--;
16239             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16240             DisplayError(buf, 0);
16241         }
16242         continue;
16243     }
16244     /* End of additions by HGM */
16245
16246     /* unknown feature: complain and skip */
16247     q = p;
16248     while (*q && *q != '=') q++;
16249     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16250     SendToProgram(buf, cps);
16251     p = q;
16252     if (*p == '=') {
16253       p++;
16254       if (*p == '\"') {
16255         p++;
16256         while (*p && *p != '\"') p++;
16257         if (*p == '\"') p++;
16258       } else {
16259         while (*p && *p != ' ') p++;
16260       }
16261     }
16262   }
16263
16264 }
16265
16266 void
16267 PeriodicUpdatesEvent (int newState)
16268 {
16269     if (newState == appData.periodicUpdates)
16270       return;
16271
16272     appData.periodicUpdates=newState;
16273
16274     /* Display type changes, so update it now */
16275 //    DisplayAnalysis();
16276
16277     /* Get the ball rolling again... */
16278     if (newState) {
16279         AnalysisPeriodicEvent(1);
16280         StartAnalysisClock();
16281     }
16282 }
16283
16284 void
16285 PonderNextMoveEvent (int newState)
16286 {
16287     if (newState == appData.ponderNextMove) return;
16288     if (gameMode == EditPosition) EditPositionDone(TRUE);
16289     if (newState) {
16290         SendToProgram("hard\n", &first);
16291         if (gameMode == TwoMachinesPlay) {
16292             SendToProgram("hard\n", &second);
16293         }
16294     } else {
16295         SendToProgram("easy\n", &first);
16296         thinkOutput[0] = NULLCHAR;
16297         if (gameMode == TwoMachinesPlay) {
16298             SendToProgram("easy\n", &second);
16299         }
16300     }
16301     appData.ponderNextMove = newState;
16302 }
16303
16304 void
16305 NewSettingEvent (int option, int *feature, char *command, int value)
16306 {
16307     char buf[MSG_SIZ];
16308
16309     if (gameMode == EditPosition) EditPositionDone(TRUE);
16310     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16311     if(feature == NULL || *feature) SendToProgram(buf, &first);
16312     if (gameMode == TwoMachinesPlay) {
16313         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16314     }
16315 }
16316
16317 void
16318 ShowThinkingEvent ()
16319 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16320 {
16321     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16322     int newState = appData.showThinking
16323         // [HGM] thinking: other features now need thinking output as well
16324         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16325
16326     if (oldState == newState) return;
16327     oldState = newState;
16328     if (gameMode == EditPosition) EditPositionDone(TRUE);
16329     if (oldState) {
16330         SendToProgram("post\n", &first);
16331         if (gameMode == TwoMachinesPlay) {
16332             SendToProgram("post\n", &second);
16333         }
16334     } else {
16335         SendToProgram("nopost\n", &first);
16336         thinkOutput[0] = NULLCHAR;
16337         if (gameMode == TwoMachinesPlay) {
16338             SendToProgram("nopost\n", &second);
16339         }
16340     }
16341 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16342 }
16343
16344 void
16345 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16346 {
16347   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16348   if (pr == NoProc) return;
16349   AskQuestion(title, question, replyPrefix, pr);
16350 }
16351
16352 void
16353 TypeInEvent (char firstChar)
16354 {
16355     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16356         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16357         gameMode == AnalyzeMode || gameMode == EditGame ||
16358         gameMode == EditPosition || gameMode == IcsExamining ||
16359         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16360         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16361                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16362                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16363         gameMode == Training) PopUpMoveDialog(firstChar);
16364 }
16365
16366 void
16367 TypeInDoneEvent (char *move)
16368 {
16369         Board board;
16370         int n, fromX, fromY, toX, toY;
16371         char promoChar;
16372         ChessMove moveType;
16373
16374         // [HGM] FENedit
16375         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16376                 EditPositionPasteFEN(move);
16377                 return;
16378         }
16379         // [HGM] movenum: allow move number to be typed in any mode
16380         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16381           ToNrEvent(2*n-1);
16382           return;
16383         }
16384         // undocumented kludge: allow command-line option to be typed in!
16385         // (potentially fatal, and does not implement the effect of the option.)
16386         // should only be used for options that are values on which future decisions will be made,
16387         // and definitely not on options that would be used during initialization.
16388         if(strstr(move, "!!! -") == move) {
16389             ParseArgsFromString(move+4);
16390             return;
16391         }
16392
16393       if (gameMode != EditGame && currentMove != forwardMostMove &&
16394         gameMode != Training) {
16395         DisplayMoveError(_("Displayed move is not current"));
16396       } else {
16397         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16398           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16399         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16400         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16401           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16402           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16403         } else {
16404           DisplayMoveError(_("Could not parse move"));
16405         }
16406       }
16407 }
16408
16409 void
16410 DisplayMove (int moveNumber)
16411 {
16412     char message[MSG_SIZ];
16413     char res[MSG_SIZ];
16414     char cpThinkOutput[MSG_SIZ];
16415
16416     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16417
16418     if (moveNumber == forwardMostMove - 1 ||
16419         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16420
16421         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16422
16423         if (strchr(cpThinkOutput, '\n')) {
16424             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16425         }
16426     } else {
16427         *cpThinkOutput = NULLCHAR;
16428     }
16429
16430     /* [AS] Hide thinking from human user */
16431     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16432         *cpThinkOutput = NULLCHAR;
16433         if( thinkOutput[0] != NULLCHAR ) {
16434             int i;
16435
16436             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16437                 cpThinkOutput[i] = '.';
16438             }
16439             cpThinkOutput[i] = NULLCHAR;
16440             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16441         }
16442     }
16443
16444     if (moveNumber == forwardMostMove - 1 &&
16445         gameInfo.resultDetails != NULL) {
16446         if (gameInfo.resultDetails[0] == NULLCHAR) {
16447           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16448         } else {
16449           snprintf(res, MSG_SIZ, " {%s} %s",
16450                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16451         }
16452     } else {
16453         res[0] = NULLCHAR;
16454     }
16455
16456     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16457         DisplayMessage(res, cpThinkOutput);
16458     } else {
16459       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16460                 WhiteOnMove(moveNumber) ? " " : ".. ",
16461                 parseList[moveNumber], res);
16462         DisplayMessage(message, cpThinkOutput);
16463     }
16464 }
16465
16466 void
16467 DisplayComment (int moveNumber, char *text)
16468 {
16469     char title[MSG_SIZ];
16470
16471     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16472       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16473     } else {
16474       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16475               WhiteOnMove(moveNumber) ? " " : ".. ",
16476               parseList[moveNumber]);
16477     }
16478     if (text != NULL && (appData.autoDisplayComment || commentUp))
16479         CommentPopUp(title, text);
16480 }
16481
16482 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16483  * might be busy thinking or pondering.  It can be omitted if your
16484  * gnuchess is configured to stop thinking immediately on any user
16485  * input.  However, that gnuchess feature depends on the FIONREAD
16486  * ioctl, which does not work properly on some flavors of Unix.
16487  */
16488 void
16489 Attention (ChessProgramState *cps)
16490 {
16491 #if ATTENTION
16492     if (!cps->useSigint) return;
16493     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16494     switch (gameMode) {
16495       case MachinePlaysWhite:
16496       case MachinePlaysBlack:
16497       case TwoMachinesPlay:
16498       case IcsPlayingWhite:
16499       case IcsPlayingBlack:
16500       case AnalyzeMode:
16501       case AnalyzeFile:
16502         /* Skip if we know it isn't thinking */
16503         if (!cps->maybeThinking) return;
16504         if (appData.debugMode)
16505           fprintf(debugFP, "Interrupting %s\n", cps->which);
16506         InterruptChildProcess(cps->pr);
16507         cps->maybeThinking = FALSE;
16508         break;
16509       default:
16510         break;
16511     }
16512 #endif /*ATTENTION*/
16513 }
16514
16515 int
16516 CheckFlags ()
16517 {
16518     if (whiteTimeRemaining <= 0) {
16519         if (!whiteFlag) {
16520             whiteFlag = TRUE;
16521             if (appData.icsActive) {
16522                 if (appData.autoCallFlag &&
16523                     gameMode == IcsPlayingBlack && !blackFlag) {
16524                   SendToICS(ics_prefix);
16525                   SendToICS("flag\n");
16526                 }
16527             } else {
16528                 if (blackFlag) {
16529                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16530                 } else {
16531                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16532                     if (appData.autoCallFlag) {
16533                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16534                         return TRUE;
16535                     }
16536                 }
16537             }
16538         }
16539     }
16540     if (blackTimeRemaining <= 0) {
16541         if (!blackFlag) {
16542             blackFlag = TRUE;
16543             if (appData.icsActive) {
16544                 if (appData.autoCallFlag &&
16545                     gameMode == IcsPlayingWhite && !whiteFlag) {
16546                   SendToICS(ics_prefix);
16547                   SendToICS("flag\n");
16548                 }
16549             } else {
16550                 if (whiteFlag) {
16551                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16552                 } else {
16553                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16554                     if (appData.autoCallFlag) {
16555                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16556                         return TRUE;
16557                     }
16558                 }
16559             }
16560         }
16561     }
16562     return FALSE;
16563 }
16564
16565 void
16566 CheckTimeControl ()
16567 {
16568     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16569         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16570
16571     /*
16572      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16573      */
16574     if ( !WhiteOnMove(forwardMostMove) ) {
16575         /* White made time control */
16576         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16577         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16578         /* [HGM] time odds: correct new time quota for time odds! */
16579                                             / WhitePlayer()->timeOdds;
16580         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16581     } else {
16582         lastBlack -= blackTimeRemaining;
16583         /* Black made time control */
16584         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16585                                             / WhitePlayer()->other->timeOdds;
16586         lastWhite = whiteTimeRemaining;
16587     }
16588 }
16589
16590 void
16591 DisplayBothClocks ()
16592 {
16593     int wom = gameMode == EditPosition ?
16594       !blackPlaysFirst : WhiteOnMove(currentMove);
16595     DisplayWhiteClock(whiteTimeRemaining, wom);
16596     DisplayBlackClock(blackTimeRemaining, !wom);
16597 }
16598
16599
16600 /* Timekeeping seems to be a portability nightmare.  I think everyone
16601    has ftime(), but I'm really not sure, so I'm including some ifdefs
16602    to use other calls if you don't.  Clocks will be less accurate if
16603    you have neither ftime nor gettimeofday.
16604 */
16605
16606 /* VS 2008 requires the #include outside of the function */
16607 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16608 #include <sys/timeb.h>
16609 #endif
16610
16611 /* Get the current time as a TimeMark */
16612 void
16613 GetTimeMark (TimeMark *tm)
16614 {
16615 #if HAVE_GETTIMEOFDAY
16616
16617     struct timeval timeVal;
16618     struct timezone timeZone;
16619
16620     gettimeofday(&timeVal, &timeZone);
16621     tm->sec = (long) timeVal.tv_sec;
16622     tm->ms = (int) (timeVal.tv_usec / 1000L);
16623
16624 #else /*!HAVE_GETTIMEOFDAY*/
16625 #if HAVE_FTIME
16626
16627 // include <sys/timeb.h> / moved to just above start of function
16628     struct timeb timeB;
16629
16630     ftime(&timeB);
16631     tm->sec = (long) timeB.time;
16632     tm->ms = (int) timeB.millitm;
16633
16634 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16635     tm->sec = (long) time(NULL);
16636     tm->ms = 0;
16637 #endif
16638 #endif
16639 }
16640
16641 /* Return the difference in milliseconds between two
16642    time marks.  We assume the difference will fit in a long!
16643 */
16644 long
16645 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16646 {
16647     return 1000L*(tm2->sec - tm1->sec) +
16648            (long) (tm2->ms - tm1->ms);
16649 }
16650
16651
16652 /*
16653  * Code to manage the game clocks.
16654  *
16655  * In tournament play, black starts the clock and then white makes a move.
16656  * We give the human user a slight advantage if he is playing white---the
16657  * clocks don't run until he makes his first move, so it takes zero time.
16658  * Also, we don't account for network lag, so we could get out of sync
16659  * with GNU Chess's clock -- but then, referees are always right.
16660  */
16661
16662 static TimeMark tickStartTM;
16663 static long intendedTickLength;
16664
16665 long
16666 NextTickLength (long timeRemaining)
16667 {
16668     long nominalTickLength, nextTickLength;
16669
16670     if (timeRemaining > 0L && timeRemaining <= 10000L)
16671       nominalTickLength = 100L;
16672     else
16673       nominalTickLength = 1000L;
16674     nextTickLength = timeRemaining % nominalTickLength;
16675     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16676
16677     return nextTickLength;
16678 }
16679
16680 /* Adjust clock one minute up or down */
16681 void
16682 AdjustClock (Boolean which, int dir)
16683 {
16684     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16685     if(which) blackTimeRemaining += 60000*dir;
16686     else      whiteTimeRemaining += 60000*dir;
16687     DisplayBothClocks();
16688     adjustedClock = TRUE;
16689 }
16690
16691 /* Stop clocks and reset to a fresh time control */
16692 void
16693 ResetClocks ()
16694 {
16695     (void) StopClockTimer();
16696     if (appData.icsActive) {
16697         whiteTimeRemaining = blackTimeRemaining = 0;
16698     } else if (searchTime) {
16699         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16700         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16701     } else { /* [HGM] correct new time quote for time odds */
16702         whiteTC = blackTC = fullTimeControlString;
16703         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16704         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16705     }
16706     if (whiteFlag || blackFlag) {
16707         DisplayTitle("");
16708         whiteFlag = blackFlag = FALSE;
16709     }
16710     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16711     DisplayBothClocks();
16712     adjustedClock = FALSE;
16713 }
16714
16715 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16716
16717 /* Decrement running clock by amount of time that has passed */
16718 void
16719 DecrementClocks ()
16720 {
16721     long timeRemaining;
16722     long lastTickLength, fudge;
16723     TimeMark now;
16724
16725     if (!appData.clockMode) return;
16726     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16727
16728     GetTimeMark(&now);
16729
16730     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16731
16732     /* Fudge if we woke up a little too soon */
16733     fudge = intendedTickLength - lastTickLength;
16734     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16735
16736     if (WhiteOnMove(forwardMostMove)) {
16737         if(whiteNPS >= 0) lastTickLength = 0;
16738         timeRemaining = whiteTimeRemaining -= lastTickLength;
16739         if(timeRemaining < 0 && !appData.icsActive) {
16740             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16741             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16742                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16743                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16744             }
16745         }
16746         DisplayWhiteClock(whiteTimeRemaining - fudge,
16747                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16748     } else {
16749         if(blackNPS >= 0) lastTickLength = 0;
16750         timeRemaining = blackTimeRemaining -= lastTickLength;
16751         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16752             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16753             if(suddenDeath) {
16754                 blackStartMove = forwardMostMove;
16755                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16756             }
16757         }
16758         DisplayBlackClock(blackTimeRemaining - fudge,
16759                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16760     }
16761     if (CheckFlags()) return;
16762
16763     if(twoBoards) { // count down secondary board's clocks as well
16764         activePartnerTime -= lastTickLength;
16765         partnerUp = 1;
16766         if(activePartner == 'W')
16767             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16768         else
16769             DisplayBlackClock(activePartnerTime, TRUE);
16770         partnerUp = 0;
16771     }
16772
16773     tickStartTM = now;
16774     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16775     StartClockTimer(intendedTickLength);
16776
16777     /* if the time remaining has fallen below the alarm threshold, sound the
16778      * alarm. if the alarm has sounded and (due to a takeback or time control
16779      * with increment) the time remaining has increased to a level above the
16780      * threshold, reset the alarm so it can sound again.
16781      */
16782
16783     if (appData.icsActive && appData.icsAlarm) {
16784
16785         /* make sure we are dealing with the user's clock */
16786         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16787                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16788            )) return;
16789
16790         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16791             alarmSounded = FALSE;
16792         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16793             PlayAlarmSound();
16794             alarmSounded = TRUE;
16795         }
16796     }
16797 }
16798
16799
16800 /* A player has just moved, so stop the previously running
16801    clock and (if in clock mode) start the other one.
16802    We redisplay both clocks in case we're in ICS mode, because
16803    ICS gives us an update to both clocks after every move.
16804    Note that this routine is called *after* forwardMostMove
16805    is updated, so the last fractional tick must be subtracted
16806    from the color that is *not* on move now.
16807 */
16808 void
16809 SwitchClocks (int newMoveNr)
16810 {
16811     long lastTickLength;
16812     TimeMark now;
16813     int flagged = FALSE;
16814
16815     GetTimeMark(&now);
16816
16817     if (StopClockTimer() && appData.clockMode) {
16818         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16819         if (!WhiteOnMove(forwardMostMove)) {
16820             if(blackNPS >= 0) lastTickLength = 0;
16821             blackTimeRemaining -= lastTickLength;
16822            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16823 //         if(pvInfoList[forwardMostMove].time == -1)
16824                  pvInfoList[forwardMostMove].time =               // use GUI time
16825                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16826         } else {
16827            if(whiteNPS >= 0) lastTickLength = 0;
16828            whiteTimeRemaining -= lastTickLength;
16829            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16830 //         if(pvInfoList[forwardMostMove].time == -1)
16831                  pvInfoList[forwardMostMove].time =
16832                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16833         }
16834         flagged = CheckFlags();
16835     }
16836     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16837     CheckTimeControl();
16838
16839     if (flagged || !appData.clockMode) return;
16840
16841     switch (gameMode) {
16842       case MachinePlaysBlack:
16843       case MachinePlaysWhite:
16844       case BeginningOfGame:
16845         if (pausing) return;
16846         break;
16847
16848       case EditGame:
16849       case PlayFromGameFile:
16850       case IcsExamining:
16851         return;
16852
16853       default:
16854         break;
16855     }
16856
16857     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16858         if(WhiteOnMove(forwardMostMove))
16859              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16860         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16861     }
16862
16863     tickStartTM = now;
16864     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16865       whiteTimeRemaining : blackTimeRemaining);
16866     StartClockTimer(intendedTickLength);
16867 }
16868
16869
16870 /* Stop both clocks */
16871 void
16872 StopClocks ()
16873 {
16874     long lastTickLength;
16875     TimeMark now;
16876
16877     if (!StopClockTimer()) return;
16878     if (!appData.clockMode) return;
16879
16880     GetTimeMark(&now);
16881
16882     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16883     if (WhiteOnMove(forwardMostMove)) {
16884         if(whiteNPS >= 0) lastTickLength = 0;
16885         whiteTimeRemaining -= lastTickLength;
16886         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16887     } else {
16888         if(blackNPS >= 0) lastTickLength = 0;
16889         blackTimeRemaining -= lastTickLength;
16890         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16891     }
16892     CheckFlags();
16893 }
16894
16895 /* Start clock of player on move.  Time may have been reset, so
16896    if clock is already running, stop and restart it. */
16897 void
16898 StartClocks ()
16899 {
16900     (void) StopClockTimer(); /* in case it was running already */
16901     DisplayBothClocks();
16902     if (CheckFlags()) return;
16903
16904     if (!appData.clockMode) return;
16905     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16906
16907     GetTimeMark(&tickStartTM);
16908     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16909       whiteTimeRemaining : blackTimeRemaining);
16910
16911    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16912     whiteNPS = blackNPS = -1;
16913     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16914        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16915         whiteNPS = first.nps;
16916     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16917        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16918         blackNPS = first.nps;
16919     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16920         whiteNPS = second.nps;
16921     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16922         blackNPS = second.nps;
16923     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16924
16925     StartClockTimer(intendedTickLength);
16926 }
16927
16928 char *
16929 TimeString (long ms)
16930 {
16931     long second, minute, hour, day;
16932     char *sign = "";
16933     static char buf[32];
16934
16935     if (ms > 0 && ms <= 9900) {
16936       /* convert milliseconds to tenths, rounding up */
16937       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16938
16939       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16940       return buf;
16941     }
16942
16943     /* convert milliseconds to seconds, rounding up */
16944     /* use floating point to avoid strangeness of integer division
16945        with negative dividends on many machines */
16946     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16947
16948     if (second < 0) {
16949         sign = "-";
16950         second = -second;
16951     }
16952
16953     day = second / (60 * 60 * 24);
16954     second = second % (60 * 60 * 24);
16955     hour = second / (60 * 60);
16956     second = second % (60 * 60);
16957     minute = second / 60;
16958     second = second % 60;
16959
16960     if (day > 0)
16961       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16962               sign, day, hour, minute, second);
16963     else if (hour > 0)
16964       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16965     else
16966       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16967
16968     return buf;
16969 }
16970
16971
16972 /*
16973  * This is necessary because some C libraries aren't ANSI C compliant yet.
16974  */
16975 char *
16976 StrStr (char *string, char *match)
16977 {
16978     int i, length;
16979
16980     length = strlen(match);
16981
16982     for (i = strlen(string) - length; i >= 0; i--, string++)
16983       if (!strncmp(match, string, length))
16984         return string;
16985
16986     return NULL;
16987 }
16988
16989 char *
16990 StrCaseStr (char *string, char *match)
16991 {
16992     int i, j, length;
16993
16994     length = strlen(match);
16995
16996     for (i = strlen(string) - length; i >= 0; i--, string++) {
16997         for (j = 0; j < length; j++) {
16998             if (ToLower(match[j]) != ToLower(string[j]))
16999               break;
17000         }
17001         if (j == length) return string;
17002     }
17003
17004     return NULL;
17005 }
17006
17007 #ifndef _amigados
17008 int
17009 StrCaseCmp (char *s1, char *s2)
17010 {
17011     char c1, c2;
17012
17013     for (;;) {
17014         c1 = ToLower(*s1++);
17015         c2 = ToLower(*s2++);
17016         if (c1 > c2) return 1;
17017         if (c1 < c2) return -1;
17018         if (c1 == NULLCHAR) return 0;
17019     }
17020 }
17021
17022
17023 int
17024 ToLower (int c)
17025 {
17026     return isupper(c) ? tolower(c) : c;
17027 }
17028
17029
17030 int
17031 ToUpper (int c)
17032 {
17033     return islower(c) ? toupper(c) : c;
17034 }
17035 #endif /* !_amigados    */
17036
17037 char *
17038 StrSave (char *s)
17039 {
17040   char *ret;
17041
17042   if ((ret = (char *) malloc(strlen(s) + 1)))
17043     {
17044       safeStrCpy(ret, s, strlen(s)+1);
17045     }
17046   return ret;
17047 }
17048
17049 char *
17050 StrSavePtr (char *s, char **savePtr)
17051 {
17052     if (*savePtr) {
17053         free(*savePtr);
17054     }
17055     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17056       safeStrCpy(*savePtr, s, strlen(s)+1);
17057     }
17058     return(*savePtr);
17059 }
17060
17061 char *
17062 PGNDate ()
17063 {
17064     time_t clock;
17065     struct tm *tm;
17066     char buf[MSG_SIZ];
17067
17068     clock = time((time_t *)NULL);
17069     tm = localtime(&clock);
17070     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17071             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17072     return StrSave(buf);
17073 }
17074
17075
17076 char *
17077 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17078 {
17079     int i, j, fromX, fromY, toX, toY;
17080     int whiteToPlay;
17081     char buf[MSG_SIZ];
17082     char *p, *q;
17083     int emptycount;
17084     ChessSquare piece;
17085
17086     whiteToPlay = (gameMode == EditPosition) ?
17087       !blackPlaysFirst : (move % 2 == 0);
17088     p = buf;
17089
17090     /* Piece placement data */
17091     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17092         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17093         emptycount = 0;
17094         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17095             if (boards[move][i][j] == EmptySquare) {
17096                 emptycount++;
17097             } else { ChessSquare piece = boards[move][i][j];
17098                 if (emptycount > 0) {
17099                     if(emptycount<10) /* [HGM] can be >= 10 */
17100                         *p++ = '0' + emptycount;
17101                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17102                     emptycount = 0;
17103                 }
17104                 if(PieceToChar(piece) == '+') {
17105                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17106                     *p++ = '+';
17107                     piece = (ChessSquare)(DEMOTED piece);
17108                 }
17109                 *p++ = PieceToChar(piece);
17110                 if(p[-1] == '~') {
17111                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17112                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17113                     *p++ = '~';
17114                 }
17115             }
17116         }
17117         if (emptycount > 0) {
17118             if(emptycount<10) /* [HGM] can be >= 10 */
17119                 *p++ = '0' + emptycount;
17120             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17121             emptycount = 0;
17122         }
17123         *p++ = '/';
17124     }
17125     *(p - 1) = ' ';
17126
17127     /* [HGM] print Crazyhouse or Shogi holdings */
17128     if( gameInfo.holdingsWidth ) {
17129         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17130         q = p;
17131         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17132             piece = boards[move][i][BOARD_WIDTH-1];
17133             if( piece != EmptySquare )
17134               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17135                   *p++ = PieceToChar(piece);
17136         }
17137         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17138             piece = boards[move][BOARD_HEIGHT-i-1][0];
17139             if( piece != EmptySquare )
17140               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17141                   *p++ = PieceToChar(piece);
17142         }
17143
17144         if( q == p ) *p++ = '-';
17145         *p++ = ']';
17146         *p++ = ' ';
17147     }
17148
17149     /* Active color */
17150     *p++ = whiteToPlay ? 'w' : 'b';
17151     *p++ = ' ';
17152
17153   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17154     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17155   } else {
17156   if(nrCastlingRights) {
17157      q = p;
17158      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17159        /* [HGM] write directly from rights */
17160            if(boards[move][CASTLING][2] != NoRights &&
17161               boards[move][CASTLING][0] != NoRights   )
17162                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17163            if(boards[move][CASTLING][2] != NoRights &&
17164               boards[move][CASTLING][1] != NoRights   )
17165                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17166            if(boards[move][CASTLING][5] != NoRights &&
17167               boards[move][CASTLING][3] != NoRights   )
17168                 *p++ = boards[move][CASTLING][3] + AAA;
17169            if(boards[move][CASTLING][5] != NoRights &&
17170               boards[move][CASTLING][4] != NoRights   )
17171                 *p++ = boards[move][CASTLING][4] + AAA;
17172      } else {
17173
17174         /* [HGM] write true castling rights */
17175         if( nrCastlingRights == 6 ) {
17176             int q, k=0;
17177             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17178                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17179             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17180                  boards[move][CASTLING][2] != NoRights  );
17181             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17182                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17183                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17184                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17185                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17186             }
17187             if(q) *p++ = 'Q';
17188             k = 0;
17189             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17190                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17191             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17192                  boards[move][CASTLING][5] != NoRights  );
17193             if(gameInfo.variant == VariantSChess) {
17194                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17195                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17196                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17197                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17198             }
17199             if(q) *p++ = 'q';
17200         }
17201      }
17202      if (q == p) *p++ = '-'; /* No castling rights */
17203      *p++ = ' ';
17204   }
17205
17206   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17207      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17208      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17209     /* En passant target square */
17210     if (move > backwardMostMove) {
17211         fromX = moveList[move - 1][0] - AAA;
17212         fromY = moveList[move - 1][1] - ONE;
17213         toX = moveList[move - 1][2] - AAA;
17214         toY = moveList[move - 1][3] - ONE;
17215         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17216             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17217             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17218             fromX == toX) {
17219             /* 2-square pawn move just happened */
17220             *p++ = toX + AAA;
17221             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17222         } else {
17223             *p++ = '-';
17224         }
17225     } else if(move == backwardMostMove) {
17226         // [HGM] perhaps we should always do it like this, and forget the above?
17227         if((signed char)boards[move][EP_STATUS] >= 0) {
17228             *p++ = boards[move][EP_STATUS] + AAA;
17229             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17230         } else {
17231             *p++ = '-';
17232         }
17233     } else {
17234         *p++ = '-';
17235     }
17236     *p++ = ' ';
17237   }
17238   }
17239
17240     if(moveCounts)
17241     {   int i = 0, j=move;
17242
17243         /* [HGM] find reversible plies */
17244         if (appData.debugMode) { int k;
17245             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17246             for(k=backwardMostMove; k<=forwardMostMove; k++)
17247                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17248
17249         }
17250
17251         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17252         if( j == backwardMostMove ) i += initialRulePlies;
17253         sprintf(p, "%d ", i);
17254         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17255
17256         /* Fullmove number */
17257         sprintf(p, "%d", (move / 2) + 1);
17258     } else *--p = NULLCHAR;
17259
17260     return StrSave(buf);
17261 }
17262
17263 Boolean
17264 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17265 {
17266     int i, j;
17267     char *p, c;
17268     int emptycount, virgin[BOARD_FILES];
17269     ChessSquare piece;
17270
17271     p = fen;
17272
17273     /* [HGM] by default clear Crazyhouse holdings, if present */
17274     if(gameInfo.holdingsWidth) {
17275        for(i=0; i<BOARD_HEIGHT; i++) {
17276            board[i][0]             = EmptySquare; /* black holdings */
17277            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17278            board[i][1]             = (ChessSquare) 0; /* black counts */
17279            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17280        }
17281     }
17282
17283     /* Piece placement data */
17284     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17285         j = 0;
17286         for (;;) {
17287             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17288                 if (*p == '/') p++;
17289                 emptycount = gameInfo.boardWidth - j;
17290                 while (emptycount--)
17291                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17292                 break;
17293 #if(BOARD_FILES >= 10)
17294             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17295                 p++; emptycount=10;
17296                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17297                 while (emptycount--)
17298                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17299 #endif
17300             } else if (isdigit(*p)) {
17301                 emptycount = *p++ - '0';
17302                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17303                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17304                 while (emptycount--)
17305                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17306             } else if (*p == '+' || isalpha(*p)) {
17307                 if (j >= gameInfo.boardWidth) return FALSE;
17308                 if(*p=='+') {
17309                     piece = CharToPiece(*++p);
17310                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17311                     piece = (ChessSquare) (PROMOTED piece ); p++;
17312                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17313                 } else piece = CharToPiece(*p++);
17314
17315                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17316                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17317                     piece = (ChessSquare) (PROMOTED piece);
17318                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17319                     p++;
17320                 }
17321                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17322             } else {
17323                 return FALSE;
17324             }
17325         }
17326     }
17327     while (*p == '/' || *p == ' ') p++;
17328
17329     /* [HGM] look for Crazyhouse holdings here */
17330     while(*p==' ') p++;
17331     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17332         if(*p == '[') p++;
17333         if(*p == '-' ) p++; /* empty holdings */ else {
17334             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17335             /* if we would allow FEN reading to set board size, we would   */
17336             /* have to add holdings and shift the board read so far here   */
17337             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17338                 p++;
17339                 if((int) piece >= (int) BlackPawn ) {
17340                     i = (int)piece - (int)BlackPawn;
17341                     i = PieceToNumber((ChessSquare)i);
17342                     if( i >= gameInfo.holdingsSize ) return FALSE;
17343                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17344                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17345                 } else {
17346                     i = (int)piece - (int)WhitePawn;
17347                     i = PieceToNumber((ChessSquare)i);
17348                     if( i >= gameInfo.holdingsSize ) return FALSE;
17349                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17350                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17351                 }
17352             }
17353         }
17354         if(*p == ']') p++;
17355     }
17356
17357     while(*p == ' ') p++;
17358
17359     /* Active color */
17360     c = *p++;
17361     if(appData.colorNickNames) {
17362       if( c == appData.colorNickNames[0] ) c = 'w'; else
17363       if( c == appData.colorNickNames[1] ) c = 'b';
17364     }
17365     switch (c) {
17366       case 'w':
17367         *blackPlaysFirst = FALSE;
17368         break;
17369       case 'b':
17370         *blackPlaysFirst = TRUE;
17371         break;
17372       default:
17373         return FALSE;
17374     }
17375
17376     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17377     /* return the extra info in global variiables             */
17378
17379     /* set defaults in case FEN is incomplete */
17380     board[EP_STATUS] = EP_UNKNOWN;
17381     for(i=0; i<nrCastlingRights; i++ ) {
17382         board[CASTLING][i] =
17383             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17384     }   /* assume possible unless obviously impossible */
17385     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17386     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17387     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17388                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17389     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17390     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17391     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17392                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17393     FENrulePlies = 0;
17394
17395     while(*p==' ') p++;
17396     if(nrCastlingRights) {
17397       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17398       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17399           /* castling indicator present, so default becomes no castlings */
17400           for(i=0; i<nrCastlingRights; i++ ) {
17401                  board[CASTLING][i] = NoRights;
17402           }
17403       }
17404       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17405              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17406              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17407              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17408         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17409
17410         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17411             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17412             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17413         }
17414         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17415             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17416         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17417                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17418         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17419                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17420         switch(c) {
17421           case'K':
17422               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17423               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17424               board[CASTLING][2] = whiteKingFile;
17425               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17426               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17427               break;
17428           case'Q':
17429               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17430               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17431               board[CASTLING][2] = whiteKingFile;
17432               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17433               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17434               break;
17435           case'k':
17436               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17437               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17438               board[CASTLING][5] = blackKingFile;
17439               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17440               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17441               break;
17442           case'q':
17443               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17444               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17445               board[CASTLING][5] = blackKingFile;
17446               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17447               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17448           case '-':
17449               break;
17450           default: /* FRC castlings */
17451               if(c >= 'a') { /* black rights */
17452                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17453                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17454                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17455                   if(i == BOARD_RGHT) break;
17456                   board[CASTLING][5] = i;
17457                   c -= AAA;
17458                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17459                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17460                   if(c > i)
17461                       board[CASTLING][3] = c;
17462                   else
17463                       board[CASTLING][4] = c;
17464               } else { /* white rights */
17465                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17466                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17467                     if(board[0][i] == WhiteKing) break;
17468                   if(i == BOARD_RGHT) break;
17469                   board[CASTLING][2] = i;
17470                   c -= AAA - 'a' + 'A';
17471                   if(board[0][c] >= WhiteKing) break;
17472                   if(c > i)
17473                       board[CASTLING][0] = c;
17474                   else
17475                       board[CASTLING][1] = c;
17476               }
17477         }
17478       }
17479       for(i=0; i<nrCastlingRights; i++)
17480         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17481       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17482     if (appData.debugMode) {
17483         fprintf(debugFP, "FEN castling rights:");
17484         for(i=0; i<nrCastlingRights; i++)
17485         fprintf(debugFP, " %d", board[CASTLING][i]);
17486         fprintf(debugFP, "\n");
17487     }
17488
17489       while(*p==' ') p++;
17490     }
17491
17492     /* read e.p. field in games that know e.p. capture */
17493     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17494        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17495        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17496       if(*p=='-') {
17497         p++; board[EP_STATUS] = EP_NONE;
17498       } else {
17499          char c = *p++ - AAA;
17500
17501          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17502          if(*p >= '0' && *p <='9') p++;
17503          board[EP_STATUS] = c;
17504       }
17505     }
17506
17507
17508     if(sscanf(p, "%d", &i) == 1) {
17509         FENrulePlies = i; /* 50-move ply counter */
17510         /* (The move number is still ignored)    */
17511     }
17512
17513     return TRUE;
17514 }
17515
17516 void
17517 EditPositionPasteFEN (char *fen)
17518 {
17519   if (fen != NULL) {
17520     Board initial_position;
17521
17522     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17523       DisplayError(_("Bad FEN position in clipboard"), 0);
17524       return ;
17525     } else {
17526       int savedBlackPlaysFirst = blackPlaysFirst;
17527       EditPositionEvent();
17528       blackPlaysFirst = savedBlackPlaysFirst;
17529       CopyBoard(boards[0], initial_position);
17530       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17531       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17532       DisplayBothClocks();
17533       DrawPosition(FALSE, boards[currentMove]);
17534     }
17535   }
17536 }
17537
17538 static char cseq[12] = "\\   ";
17539
17540 Boolean
17541 set_cont_sequence (char *new_seq)
17542 {
17543     int len;
17544     Boolean ret;
17545
17546     // handle bad attempts to set the sequence
17547         if (!new_seq)
17548                 return 0; // acceptable error - no debug
17549
17550     len = strlen(new_seq);
17551     ret = (len > 0) && (len < sizeof(cseq));
17552     if (ret)
17553       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17554     else if (appData.debugMode)
17555       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17556     return ret;
17557 }
17558
17559 /*
17560     reformat a source message so words don't cross the width boundary.  internal
17561     newlines are not removed.  returns the wrapped size (no null character unless
17562     included in source message).  If dest is NULL, only calculate the size required
17563     for the dest buffer.  lp argument indicats line position upon entry, and it's
17564     passed back upon exit.
17565 */
17566 int
17567 wrap (char *dest, char *src, int count, int width, int *lp)
17568 {
17569     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17570
17571     cseq_len = strlen(cseq);
17572     old_line = line = *lp;
17573     ansi = len = clen = 0;
17574
17575     for (i=0; i < count; i++)
17576     {
17577         if (src[i] == '\033')
17578             ansi = 1;
17579
17580         // if we hit the width, back up
17581         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17582         {
17583             // store i & len in case the word is too long
17584             old_i = i, old_len = len;
17585
17586             // find the end of the last word
17587             while (i && src[i] != ' ' && src[i] != '\n')
17588             {
17589                 i--;
17590                 len--;
17591             }
17592
17593             // word too long?  restore i & len before splitting it
17594             if ((old_i-i+clen) >= width)
17595             {
17596                 i = old_i;
17597                 len = old_len;
17598             }
17599
17600             // extra space?
17601             if (i && src[i-1] == ' ')
17602                 len--;
17603
17604             if (src[i] != ' ' && src[i] != '\n')
17605             {
17606                 i--;
17607                 if (len)
17608                     len--;
17609             }
17610
17611             // now append the newline and continuation sequence
17612             if (dest)
17613                 dest[len] = '\n';
17614             len++;
17615             if (dest)
17616                 strncpy(dest+len, cseq, cseq_len);
17617             len += cseq_len;
17618             line = cseq_len;
17619             clen = cseq_len;
17620             continue;
17621         }
17622
17623         if (dest)
17624             dest[len] = src[i];
17625         len++;
17626         if (!ansi)
17627             line++;
17628         if (src[i] == '\n')
17629             line = 0;
17630         if (src[i] == 'm')
17631             ansi = 0;
17632     }
17633     if (dest && appData.debugMode)
17634     {
17635         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17636             count, width, line, len, *lp);
17637         show_bytes(debugFP, src, count);
17638         fprintf(debugFP, "\ndest: ");
17639         show_bytes(debugFP, dest, len);
17640         fprintf(debugFP, "\n");
17641     }
17642     *lp = dest ? line : old_line;
17643
17644     return len;
17645 }
17646
17647 // [HGM] vari: routines for shelving variations
17648 Boolean modeRestore = FALSE;
17649
17650 void
17651 PushInner (int firstMove, int lastMove)
17652 {
17653         int i, j, nrMoves = lastMove - firstMove;
17654
17655         // push current tail of game on stack
17656         savedResult[storedGames] = gameInfo.result;
17657         savedDetails[storedGames] = gameInfo.resultDetails;
17658         gameInfo.resultDetails = NULL;
17659         savedFirst[storedGames] = firstMove;
17660         savedLast [storedGames] = lastMove;
17661         savedFramePtr[storedGames] = framePtr;
17662         framePtr -= nrMoves; // reserve space for the boards
17663         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17664             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17665             for(j=0; j<MOVE_LEN; j++)
17666                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17667             for(j=0; j<2*MOVE_LEN; j++)
17668                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17669             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17670             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17671             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17672             pvInfoList[firstMove+i-1].depth = 0;
17673             commentList[framePtr+i] = commentList[firstMove+i];
17674             commentList[firstMove+i] = NULL;
17675         }
17676
17677         storedGames++;
17678         forwardMostMove = firstMove; // truncate game so we can start variation
17679 }
17680
17681 void
17682 PushTail (int firstMove, int lastMove)
17683 {
17684         if(appData.icsActive) { // only in local mode
17685                 forwardMostMove = currentMove; // mimic old ICS behavior
17686                 return;
17687         }
17688         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17689
17690         PushInner(firstMove, lastMove);
17691         if(storedGames == 1) GreyRevert(FALSE);
17692         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17693 }
17694
17695 void
17696 PopInner (Boolean annotate)
17697 {
17698         int i, j, nrMoves;
17699         char buf[8000], moveBuf[20];
17700
17701         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17702         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17703         nrMoves = savedLast[storedGames] - currentMove;
17704         if(annotate) {
17705                 int cnt = 10;
17706                 if(!WhiteOnMove(currentMove))
17707                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17708                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17709                 for(i=currentMove; i<forwardMostMove; i++) {
17710                         if(WhiteOnMove(i))
17711                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17712                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17713                         strcat(buf, moveBuf);
17714                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17715                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17716                 }
17717                 strcat(buf, ")");
17718         }
17719         for(i=1; i<=nrMoves; i++) { // copy last variation back
17720             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17721             for(j=0; j<MOVE_LEN; j++)
17722                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17723             for(j=0; j<2*MOVE_LEN; j++)
17724                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17725             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17726             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17727             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17728             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17729             commentList[currentMove+i] = commentList[framePtr+i];
17730             commentList[framePtr+i] = NULL;
17731         }
17732         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17733         framePtr = savedFramePtr[storedGames];
17734         gameInfo.result = savedResult[storedGames];
17735         if(gameInfo.resultDetails != NULL) {
17736             free(gameInfo.resultDetails);
17737       }
17738         gameInfo.resultDetails = savedDetails[storedGames];
17739         forwardMostMove = currentMove + nrMoves;
17740 }
17741
17742 Boolean
17743 PopTail (Boolean annotate)
17744 {
17745         if(appData.icsActive) return FALSE; // only in local mode
17746         if(!storedGames) return FALSE; // sanity
17747         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17748
17749         PopInner(annotate);
17750         if(currentMove < forwardMostMove) ForwardEvent(); else
17751         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17752
17753         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17754         return TRUE;
17755 }
17756
17757 void
17758 CleanupTail ()
17759 {       // remove all shelved variations
17760         int i;
17761         for(i=0; i<storedGames; i++) {
17762             if(savedDetails[i])
17763                 free(savedDetails[i]);
17764             savedDetails[i] = NULL;
17765         }
17766         for(i=framePtr; i<MAX_MOVES; i++) {
17767                 if(commentList[i]) free(commentList[i]);
17768                 commentList[i] = NULL;
17769         }
17770         framePtr = MAX_MOVES-1;
17771         storedGames = 0;
17772 }
17773
17774 void
17775 LoadVariation (int index, char *text)
17776 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17777         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17778         int level = 0, move;
17779
17780         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17781         // first find outermost bracketing variation
17782         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17783             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17784                 if(*p == '{') wait = '}'; else
17785                 if(*p == '[') wait = ']'; else
17786                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17787                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17788             }
17789             if(*p == wait) wait = NULLCHAR; // closing ]} found
17790             p++;
17791         }
17792         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17793         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17794         end[1] = NULLCHAR; // clip off comment beyond variation
17795         ToNrEvent(currentMove-1);
17796         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17797         // kludge: use ParsePV() to append variation to game
17798         move = currentMove;
17799         ParsePV(start, TRUE, TRUE);
17800         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17801         ClearPremoveHighlights();
17802         CommentPopDown();
17803         ToNrEvent(currentMove+1);
17804 }
17805
17806 void
17807 LoadTheme ()
17808 {
17809     char *p, *q, buf[MSG_SIZ];
17810     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17811         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17812         ParseArgsFromString(buf);
17813         ActivateTheme(TRUE); // also redo colors
17814         return;
17815     }
17816     p = nickName;
17817     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17818     {
17819         int len;
17820         q = appData.themeNames;
17821         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17822       if(appData.useBitmaps) {
17823         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17824                 appData.liteBackTextureFile, appData.darkBackTextureFile,
17825                 appData.liteBackTextureMode,
17826                 appData.darkBackTextureMode );
17827       } else {
17828         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17829                 Col2Text(2),   // lightSquareColor
17830                 Col2Text(3) ); // darkSquareColor
17831       }
17832       if(appData.useBorder) {
17833         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17834                 appData.border);
17835       } else {
17836         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17837       }
17838       if(appData.useFont) {
17839         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17840                 appData.renderPiecesWithFont,
17841                 appData.fontToPieceTable,
17842                 Col2Text(9),    // appData.fontBackColorWhite
17843                 Col2Text(10) ); // appData.fontForeColorBlack
17844       } else {
17845         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17846                 appData.pieceDirectory);
17847         if(!appData.pieceDirectory[0])
17848           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17849                 Col2Text(0),   // whitePieceColor
17850                 Col2Text(1) ); // blackPieceColor
17851       }
17852       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17853                 Col2Text(4),   // highlightSquareColor
17854                 Col2Text(5) ); // premoveHighlightColor
17855         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17856         if(insert != q) insert[-1] = NULLCHAR;
17857         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17858         if(q)   free(q);
17859     }
17860     ActivateTheme(FALSE);
17861 }