12c847d65be95752d8d7c46280f989c402f0c5ce
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "evalgraph.h"
133 #include "gettext.h"
134
135 #ifdef ENABLE_NLS
136 # define _(s) gettext (s)
137 # define N_(s) gettext_noop (s)
138 # define T_(s) gettext(s)
139 #else
140 # ifdef WIN32
141 #   define _(s) T_(s)
142 #   define N_(s) s
143 # else
144 #   define _(s) (s)
145 #   define N_(s) s
146 #   define T_(s) s
147 # endif
148 #endif
149
150
151 int establish P((void));
152 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
153                          char *buf, int count, int error));
154 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
155                       char *buf, int count, int error));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226 void ToggleSecond P((void));
227 void PauseEngine P((ChessProgramState *cps));
228 static int NonStandardBoardSize P((void));
229
230 #ifdef WIN32
231        extern void ConsoleCreate();
232 #endif
233
234 ChessProgramState *WhitePlayer();
235 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
236 int VerifyDisplayMode P(());
237
238 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
239 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
240 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
241 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
242 void ics_update_width P((int new_width));
243 extern char installDir[MSG_SIZ];
244 VariantClass startVariant; /* [HGM] nicks: initial variant */
245 Boolean abortMatch;
246
247 extern int tinyLayout, smallLayout;
248 ChessProgramStats programStats;
249 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
250 int endPV = -1;
251 static int exiting = 0; /* [HGM] moved to top */
252 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
253 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
254 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
255 int partnerHighlight[2];
256 Boolean partnerBoardValid = 0;
257 char partnerStatus[MSG_SIZ];
258 Boolean partnerUp;
259 Boolean originalFlip;
260 Boolean twoBoards = 0;
261 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
262 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
263 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
264 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
265 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
266 int opponentKibitzes;
267 int lastSavedGame; /* [HGM] save: ID of game */
268 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
269 extern int chatCount;
270 int chattingPartner;
271 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
272 char lastMsg[MSG_SIZ];
273 ChessSquare pieceSweep = EmptySquare;
274 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
275 int promoDefaultAltered;
276 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
277
278 /* States for ics_getting_history */
279 #define H_FALSE 0
280 #define H_REQUESTED 1
281 #define H_GOT_REQ_HEADER 2
282 #define H_GOT_UNREQ_HEADER 3
283 #define H_GETTING_MOVES 4
284 #define H_GOT_UNWANTED_HEADER 5
285
286 /* whosays values for GameEnds */
287 #define GE_ICS 0
288 #define GE_ENGINE 1
289 #define GE_PLAYER 2
290 #define GE_FILE 3
291 #define GE_XBOARD 4
292 #define GE_ENGINE1 5
293 #define GE_ENGINE2 6
294
295 /* Maximum number of games in a cmail message */
296 #define CMAIL_MAX_GAMES 20
297
298 /* Different types of move when calling RegisterMove */
299 #define CMAIL_MOVE   0
300 #define CMAIL_RESIGN 1
301 #define CMAIL_DRAW   2
302 #define CMAIL_ACCEPT 3
303
304 /* Different types of result to remember for each game */
305 #define CMAIL_NOT_RESULT 0
306 #define CMAIL_OLD_RESULT 1
307 #define CMAIL_NEW_RESULT 2
308
309 /* Telnet protocol constants */
310 #define TN_WILL 0373
311 #define TN_WONT 0374
312 #define TN_DO   0375
313 #define TN_DONT 0376
314 #define TN_IAC  0377
315 #define TN_ECHO 0001
316 #define TN_SGA  0003
317 #define TN_PORT 23
318
319 char*
320 safeStrCpy (char *dst, const char *src, size_t count)
321 { // [HGM] made safe
322   int i;
323   assert( dst != NULL );
324   assert( src != NULL );
325   assert( count > 0 );
326
327   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
328   if(  i == count && dst[count-1] != NULLCHAR)
329     {
330       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
331       if(appData.debugMode)
332         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
333     }
334
335   return dst;
336 }
337
338 /* Some compiler can't cast u64 to double
339  * This function do the job for us:
340
341  * We use the highest bit for cast, this only
342  * works if the highest bit is not
343  * in use (This should not happen)
344  *
345  * We used this for all compiler
346  */
347 double
348 u64ToDouble (u64 value)
349 {
350   double r;
351   u64 tmp = value & u64Const(0x7fffffffffffffff);
352   r = (double)(s64)tmp;
353   if (value & u64Const(0x8000000000000000))
354        r +=  9.2233720368547758080e18; /* 2^63 */
355  return r;
356 }
357
358 /* Fake up flags for now, as we aren't keeping track of castling
359    availability yet. [HGM] Change of logic: the flag now only
360    indicates the type of castlings allowed by the rule of the game.
361    The actual rights themselves are maintained in the array
362    castlingRights, as part of the game history, and are not probed
363    by this function.
364  */
365 int
366 PosFlags (index)
367 {
368   int flags = F_ALL_CASTLE_OK;
369   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
370   switch (gameInfo.variant) {
371   case VariantSuicide:
372     flags &= ~F_ALL_CASTLE_OK;
373   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
374     flags |= F_IGNORE_CHECK;
375   case VariantLosers:
376     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
377     break;
378   case VariantAtomic:
379     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
380     break;
381   case VariantKriegspiel:
382     flags |= F_KRIEGSPIEL_CAPTURE;
383     break;
384   case VariantCapaRandom:
385   case VariantFischeRandom:
386     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
387   case VariantNoCastle:
388   case VariantShatranj:
389   case VariantCourier:
390   case VariantMakruk:
391   case VariantASEAN:
392   case VariantGrand:
393     flags &= ~F_ALL_CASTLE_OK;
394     break;
395   default:
396     break;
397   }
398   return flags;
399 }
400
401 FILE *gameFileFP, *debugFP, *serverFP;
402 char *currentDebugFile; // [HGM] debug split: to remember name
403
404 /*
405     [AS] Note: sometimes, the sscanf() function is used to parse the input
406     into a fixed-size buffer. Because of this, we must be prepared to
407     receive strings as long as the size of the input buffer, which is currently
408     set to 4K for Windows and 8K for the rest.
409     So, we must either allocate sufficiently large buffers here, or
410     reduce the size of the input buffer in the input reading part.
411 */
412
413 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
414 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
415 char thinkOutput1[MSG_SIZ*10];
416
417 ChessProgramState first, second, pairing;
418
419 /* premove variables */
420 int premoveToX = 0;
421 int premoveToY = 0;
422 int premoveFromX = 0;
423 int premoveFromY = 0;
424 int premovePromoChar = 0;
425 int gotPremove = 0;
426 Boolean alarmSounded;
427 /* end premove variables */
428
429 char *ics_prefix = "$";
430 enum ICS_TYPE ics_type = ICS_GENERIC;
431
432 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
433 int pauseExamForwardMostMove = 0;
434 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
435 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
436 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
437 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
438 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
439 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
440 int whiteFlag = FALSE, blackFlag = FALSE;
441 int userOfferedDraw = FALSE;
442 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
443 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
444 int cmailMoveType[CMAIL_MAX_GAMES];
445 long ics_clock_paused = 0;
446 ProcRef icsPR = NoProc, cmailPR = NoProc;
447 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
448 GameMode gameMode = BeginningOfGame;
449 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
450 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
451 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
452 int hiddenThinkOutputState = 0; /* [AS] */
453 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
454 int adjudicateLossPlies = 6;
455 char white_holding[64], black_holding[64];
456 TimeMark lastNodeCountTime;
457 long lastNodeCount=0;
458 int shiftKey, controlKey; // [HGM] set by mouse handler
459
460 int have_sent_ICS_logon = 0;
461 int movesPerSession;
462 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
463 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
464 Boolean adjustedClock;
465 long timeControl_2; /* [AS] Allow separate time controls */
466 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
467 long timeRemaining[2][MAX_MOVES];
468 int matchGame = 0, nextGame = 0, roundNr = 0;
469 Boolean waitingForGame = FALSE, startingEngine = FALSE;
470 TimeMark programStartTime, pauseStart;
471 char ics_handle[MSG_SIZ];
472 int have_set_title = 0;
473
474 /* animateTraining preserves the state of appData.animate
475  * when Training mode is activated. This allows the
476  * response to be animated when appData.animate == TRUE and
477  * appData.animateDragging == TRUE.
478  */
479 Boolean animateTraining;
480
481 GameInfo gameInfo;
482
483 AppData appData;
484
485 Board boards[MAX_MOVES];
486 /* [HGM] Following 7 needed for accurate legality tests: */
487 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
488 signed char  initialRights[BOARD_FILES];
489 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
490 int   initialRulePlies, FENrulePlies;
491 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
492 int loadFlag = 0;
493 Boolean shuffleOpenings;
494 int mute; // mute all sounds
495
496 // [HGM] vari: next 12 to save and restore variations
497 #define MAX_VARIATIONS 10
498 int framePtr = MAX_MOVES-1; // points to free stack entry
499 int storedGames = 0;
500 int savedFirst[MAX_VARIATIONS];
501 int savedLast[MAX_VARIATIONS];
502 int savedFramePtr[MAX_VARIATIONS];
503 char *savedDetails[MAX_VARIATIONS];
504 ChessMove savedResult[MAX_VARIATIONS];
505
506 void PushTail P((int firstMove, int lastMove));
507 Boolean PopTail P((Boolean annotate));
508 void PushInner P((int firstMove, int lastMove));
509 void PopInner P((Boolean annotate));
510 void CleanupTail P((void));
511
512 ChessSquare  FIDEArray[2][BOARD_FILES] = {
513     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
514         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
515     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
516         BlackKing, BlackBishop, BlackKnight, BlackRook }
517 };
518
519 ChessSquare twoKingsArray[2][BOARD_FILES] = {
520     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
521         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
522     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
523         BlackKing, BlackKing, BlackKnight, BlackRook }
524 };
525
526 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
527     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
528         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
529     { BlackRook, BlackMan, BlackBishop, BlackQueen,
530         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
531 };
532
533 ChessSquare SpartanArray[2][BOARD_FILES] = {
534     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
535         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
536     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
537         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
538 };
539
540 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
542         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
543     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
544         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
545 };
546
547 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
548     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
549         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
551         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
552 };
553
554 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
555     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
556         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
557     { BlackRook, BlackKnight, BlackMan, BlackFerz,
558         BlackKing, BlackMan, BlackKnight, BlackRook }
559 };
560
561 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
562     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
563         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
564     { BlackRook, BlackKnight, BlackMan, BlackFerz,
565         BlackKing, BlackMan, BlackKnight, BlackRook }
566 };
567
568
569 #if (BOARD_FILES>=10)
570 ChessSquare ShogiArray[2][BOARD_FILES] = {
571     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
572         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
573     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
574         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
575 };
576
577 ChessSquare XiangqiArray[2][BOARD_FILES] = {
578     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
579         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
580     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
581         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
582 };
583
584 ChessSquare CapablancaArray[2][BOARD_FILES] = {
585     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
586         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
587     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
588         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
589 };
590
591 ChessSquare GreatArray[2][BOARD_FILES] = {
592     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
593         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
594     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
595         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
596 };
597
598 ChessSquare JanusArray[2][BOARD_FILES] = {
599     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
600         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
601     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
602         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
603 };
604
605 ChessSquare GrandArray[2][BOARD_FILES] = {
606     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
607         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
608     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
609         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
610 };
611
612 #ifdef GOTHIC
613 ChessSquare GothicArray[2][BOARD_FILES] = {
614     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
615         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
616     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
617         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
618 };
619 #else // !GOTHIC
620 #define GothicArray CapablancaArray
621 #endif // !GOTHIC
622
623 #ifdef FALCON
624 ChessSquare FalconArray[2][BOARD_FILES] = {
625     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
626         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
627     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
628         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
629 };
630 #else // !FALCON
631 #define FalconArray CapablancaArray
632 #endif // !FALCON
633
634 #else // !(BOARD_FILES>=10)
635 #define XiangqiPosition FIDEArray
636 #define CapablancaArray FIDEArray
637 #define GothicArray FIDEArray
638 #define GreatArray FIDEArray
639 #endif // !(BOARD_FILES>=10)
640
641 #if (BOARD_FILES>=12)
642 ChessSquare CourierArray[2][BOARD_FILES] = {
643     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
644         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
645     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
646         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
647 };
648 #else // !(BOARD_FILES>=12)
649 #define CourierArray CapablancaArray
650 #endif // !(BOARD_FILES>=12)
651
652
653 Board initialPosition;
654
655
656 /* Convert str to a rating. Checks for special cases of "----",
657
658    "++++", etc. Also strips ()'s */
659 int
660 string_to_rating (char *str)
661 {
662   while(*str && !isdigit(*str)) ++str;
663   if (!*str)
664     return 0;   /* One of the special "no rating" cases */
665   else
666     return atoi(str);
667 }
668
669 void
670 ClearProgramStats ()
671 {
672     /* Init programStats */
673     programStats.movelist[0] = 0;
674     programStats.depth = 0;
675     programStats.nr_moves = 0;
676     programStats.moves_left = 0;
677     programStats.nodes = 0;
678     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
679     programStats.score = 0;
680     programStats.got_only_move = 0;
681     programStats.got_fail = 0;
682     programStats.line_is_book = 0;
683 }
684
685 void
686 CommonEngineInit ()
687 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
688     if (appData.firstPlaysBlack) {
689         first.twoMachinesColor = "black\n";
690         second.twoMachinesColor = "white\n";
691     } else {
692         first.twoMachinesColor = "white\n";
693         second.twoMachinesColor = "black\n";
694     }
695
696     first.other = &second;
697     second.other = &first;
698
699     { float norm = 1;
700         if(appData.timeOddsMode) {
701             norm = appData.timeOdds[0];
702             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
703         }
704         first.timeOdds  = appData.timeOdds[0]/norm;
705         second.timeOdds = appData.timeOdds[1]/norm;
706     }
707
708     if(programVersion) free(programVersion);
709     if (appData.noChessProgram) {
710         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
711         sprintf(programVersion, "%s", PACKAGE_STRING);
712     } else {
713       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
714       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
715       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
716     }
717 }
718
719 void
720 UnloadEngine (ChessProgramState *cps)
721 {
722         /* Kill off first chess program */
723         if (cps->isr != NULL)
724           RemoveInputSource(cps->isr);
725         cps->isr = NULL;
726
727         if (cps->pr != NoProc) {
728             ExitAnalyzeMode();
729             DoSleep( appData.delayBeforeQuit );
730             SendToProgram("quit\n", cps);
731             DoSleep( appData.delayAfterQuit );
732             DestroyChildProcess(cps->pr, cps->useSigterm);
733         }
734         cps->pr = NoProc;
735         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
736 }
737
738 void
739 ClearOptions (ChessProgramState *cps)
740 {
741     int i;
742     cps->nrOptions = cps->comboCnt = 0;
743     for(i=0; i<MAX_OPTIONS; i++) {
744         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
745         cps->option[i].textValue = 0;
746     }
747 }
748
749 char *engineNames[] = {
750   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
751      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
752 N_("first"),
753   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
754      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
755 N_("second")
756 };
757
758 void
759 InitEngine (ChessProgramState *cps, int n)
760 {   // [HGM] all engine initialiation put in a function that does one engine
761
762     ClearOptions(cps);
763
764     cps->which = engineNames[n];
765     cps->maybeThinking = FALSE;
766     cps->pr = NoProc;
767     cps->isr = NULL;
768     cps->sendTime = 2;
769     cps->sendDrawOffers = 1;
770
771     cps->program = appData.chessProgram[n];
772     cps->host = appData.host[n];
773     cps->dir = appData.directory[n];
774     cps->initString = appData.engInitString[n];
775     cps->computerString = appData.computerString[n];
776     cps->useSigint  = TRUE;
777     cps->useSigterm = TRUE;
778     cps->reuse = appData.reuse[n];
779     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
780     cps->useSetboard = FALSE;
781     cps->useSAN = FALSE;
782     cps->usePing = FALSE;
783     cps->lastPing = 0;
784     cps->lastPong = 0;
785     cps->usePlayother = FALSE;
786     cps->useColors = TRUE;
787     cps->useUsermove = FALSE;
788     cps->sendICS = FALSE;
789     cps->sendName = appData.icsActive;
790     cps->sdKludge = FALSE;
791     cps->stKludge = FALSE;
792     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
793     TidyProgramName(cps->program, cps->host, cps->tidy);
794     cps->matchWins = 0;
795     ASSIGN(cps->variants, appData.variant);
796     cps->analysisSupport = 2; /* detect */
797     cps->analyzing = FALSE;
798     cps->initDone = FALSE;
799     cps->reload = FALSE;
800
801     /* New features added by Tord: */
802     cps->useFEN960 = FALSE;
803     cps->useOOCastle = TRUE;
804     /* End of new features added by Tord. */
805     cps->fenOverride  = appData.fenOverride[n];
806
807     /* [HGM] time odds: set factor for each machine */
808     cps->timeOdds  = appData.timeOdds[n];
809
810     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
811     cps->accumulateTC = appData.accumulateTC[n];
812     cps->maxNrOfSessions = 1;
813
814     /* [HGM] debug */
815     cps->debug = FALSE;
816
817     cps->supportsNPS = UNKNOWN;
818     cps->memSize = FALSE;
819     cps->maxCores = FALSE;
820     ASSIGN(cps->egtFormats, "");
821
822     /* [HGM] options */
823     cps->optionSettings  = appData.engOptions[n];
824
825     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
826     cps->isUCI = appData.isUCI[n]; /* [AS] */
827     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
828
829     if (appData.protocolVersion[n] > PROTOVER
830         || appData.protocolVersion[n] < 1)
831       {
832         char buf[MSG_SIZ];
833         int len;
834
835         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
836                        appData.protocolVersion[n]);
837         if( (len >= MSG_SIZ) && appData.debugMode )
838           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
839
840         DisplayFatalError(buf, 0, 2);
841       }
842     else
843       {
844         cps->protocolVersion = appData.protocolVersion[n];
845       }
846
847     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
848     ParseFeatures(appData.featureDefaults, cps);
849 }
850
851 ChessProgramState *savCps;
852
853 GameMode oldMode;
854
855 void
856 LoadEngine ()
857 {
858     int i;
859     if(WaitForEngine(savCps, LoadEngine)) return;
860     CommonEngineInit(); // recalculate time odds
861     if(gameInfo.variant != StringToVariant(appData.variant)) {
862         // we changed variant when loading the engine; this forces us to reset
863         Reset(TRUE, savCps != &first);
864         oldMode = BeginningOfGame; // to prevent restoring old mode
865     }
866     InitChessProgram(savCps, FALSE);
867     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
868     DisplayMessage("", "");
869     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
870     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
871     ThawUI();
872     SetGNUMode();
873     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
874 }
875
876 void
877 ReplaceEngine (ChessProgramState *cps, int n)
878 {
879     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
880     keepInfo = 1;
881     if(oldMode != BeginningOfGame) EditGameEvent();
882     keepInfo = 0;
883     UnloadEngine(cps);
884     appData.noChessProgram = FALSE;
885     appData.clockMode = TRUE;
886     InitEngine(cps, n);
887     UpdateLogos(TRUE);
888     if(n) return; // only startup first engine immediately; second can wait
889     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
890     LoadEngine();
891 }
892
893 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
894 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
895
896 static char resetOptions[] =
897         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
898         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
899         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
900         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
901
902 void
903 FloatToFront(char **list, char *engineLine)
904 {
905     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
906     int i=0;
907     if(appData.recentEngines <= 0) return;
908     TidyProgramName(engineLine, "localhost", tidy+1);
909     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
910     strncpy(buf+1, *list, MSG_SIZ-50);
911     if(p = strstr(buf, tidy)) { // tidy name appears in list
912         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
913         while(*p++ = *++q); // squeeze out
914     }
915     strcat(tidy, buf+1); // put list behind tidy name
916     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
917     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
918     ASSIGN(*list, tidy+1);
919 }
920
921 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
922
923 void
924 Load (ChessProgramState *cps, int i)
925 {
926     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
927     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
928         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
929         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
930         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
931         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
932         appData.firstProtocolVersion = PROTOVER;
933         ParseArgsFromString(buf);
934         SwapEngines(i);
935         ReplaceEngine(cps, i);
936         FloatToFront(&appData.recentEngineList, engineLine);
937         return;
938     }
939     p = engineName;
940     while(q = strchr(p, SLASH)) p = q+1;
941     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
942     if(engineDir[0] != NULLCHAR) {
943         ASSIGN(appData.directory[i], engineDir); p = engineName;
944     } else if(p != engineName) { // derive directory from engine path, when not given
945         p[-1] = 0;
946         ASSIGN(appData.directory[i], engineName);
947         p[-1] = SLASH;
948         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
949     } else { ASSIGN(appData.directory[i], "."); }
950     if(params[0]) {
951         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
952         snprintf(command, MSG_SIZ, "%s %s", p, params);
953         p = command;
954     }
955     ASSIGN(appData.chessProgram[i], p);
956     appData.isUCI[i] = isUCI;
957     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
958     appData.hasOwnBookUCI[i] = hasBook;
959     if(!nickName[0]) useNick = FALSE;
960     if(useNick) ASSIGN(appData.pgnName[i], nickName);
961     if(addToList) {
962         int len;
963         char quote;
964         q = firstChessProgramNames;
965         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
966         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
967         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
968                         quote, p, quote, appData.directory[i],
969                         useNick ? " -fn \"" : "",
970                         useNick ? nickName : "",
971                         useNick ? "\"" : "",
972                         v1 ? " -firstProtocolVersion 1" : "",
973                         hasBook ? "" : " -fNoOwnBookUCI",
974                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
975                         storeVariant ? " -variant " : "",
976                         storeVariant ? VariantName(gameInfo.variant) : "");
977         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
978         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
979         if(insert != q) insert[-1] = NULLCHAR;
980         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
981         if(q)   free(q);
982         FloatToFront(&appData.recentEngineList, buf);
983     }
984     ReplaceEngine(cps, i);
985 }
986
987 void
988 InitTimeControls ()
989 {
990     int matched, min, sec;
991     /*
992      * Parse timeControl resource
993      */
994     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
995                           appData.movesPerSession)) {
996         char buf[MSG_SIZ];
997         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
998         DisplayFatalError(buf, 0, 2);
999     }
1000
1001     /*
1002      * Parse searchTime resource
1003      */
1004     if (*appData.searchTime != NULLCHAR) {
1005         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1006         if (matched == 1) {
1007             searchTime = min * 60;
1008         } else if (matched == 2) {
1009             searchTime = min * 60 + sec;
1010         } else {
1011             char buf[MSG_SIZ];
1012             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1013             DisplayFatalError(buf, 0, 2);
1014         }
1015     }
1016 }
1017
1018 void
1019 InitBackEnd1 ()
1020 {
1021
1022     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1023     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1024
1025     GetTimeMark(&programStartTime);
1026     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1027     appData.seedBase = random() + (random()<<15);
1028     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1029
1030     ClearProgramStats();
1031     programStats.ok_to_send = 1;
1032     programStats.seen_stat = 0;
1033
1034     /*
1035      * Initialize game list
1036      */
1037     ListNew(&gameList);
1038
1039
1040     /*
1041      * Internet chess server status
1042      */
1043     if (appData.icsActive) {
1044         appData.matchMode = FALSE;
1045         appData.matchGames = 0;
1046 #if ZIPPY
1047         appData.noChessProgram = !appData.zippyPlay;
1048 #else
1049         appData.zippyPlay = FALSE;
1050         appData.zippyTalk = FALSE;
1051         appData.noChessProgram = TRUE;
1052 #endif
1053         if (*appData.icsHelper != NULLCHAR) {
1054             appData.useTelnet = TRUE;
1055             appData.telnetProgram = appData.icsHelper;
1056         }
1057     } else {
1058         appData.zippyTalk = appData.zippyPlay = FALSE;
1059     }
1060
1061     /* [AS] Initialize pv info list [HGM] and game state */
1062     {
1063         int i, j;
1064
1065         for( i=0; i<=framePtr; i++ ) {
1066             pvInfoList[i].depth = -1;
1067             boards[i][EP_STATUS] = EP_NONE;
1068             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1069         }
1070     }
1071
1072     InitTimeControls();
1073
1074     /* [AS] Adjudication threshold */
1075     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1076
1077     InitEngine(&first, 0);
1078     InitEngine(&second, 1);
1079     CommonEngineInit();
1080
1081     pairing.which = "pairing"; // pairing engine
1082     pairing.pr = NoProc;
1083     pairing.isr = NULL;
1084     pairing.program = appData.pairingEngine;
1085     pairing.host = "localhost";
1086     pairing.dir = ".";
1087
1088     if (appData.icsActive) {
1089         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1090     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1091         appData.clockMode = FALSE;
1092         first.sendTime = second.sendTime = 0;
1093     }
1094
1095 #if ZIPPY
1096     /* Override some settings from environment variables, for backward
1097        compatibility.  Unfortunately it's not feasible to have the env
1098        vars just set defaults, at least in xboard.  Ugh.
1099     */
1100     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1101       ZippyInit();
1102     }
1103 #endif
1104
1105     if (!appData.icsActive) {
1106       char buf[MSG_SIZ];
1107       int len;
1108
1109       /* Check for variants that are supported only in ICS mode,
1110          or not at all.  Some that are accepted here nevertheless
1111          have bugs; see comments below.
1112       */
1113       VariantClass variant = StringToVariant(appData.variant);
1114       switch (variant) {
1115       case VariantBughouse:     /* need four players and two boards */
1116       case VariantKriegspiel:   /* need to hide pieces and move details */
1117         /* case VariantFischeRandom: (Fabien: moved below) */
1118         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1119         if( (len >= MSG_SIZ) && appData.debugMode )
1120           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1121
1122         DisplayFatalError(buf, 0, 2);
1123         return;
1124
1125       case VariantUnknown:
1126       case VariantLoadable:
1127       case Variant29:
1128       case Variant30:
1129       case Variant31:
1130       case Variant32:
1131       case Variant33:
1132       case Variant34:
1133       case Variant35:
1134       case Variant36:
1135       default:
1136         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1137         if( (len >= MSG_SIZ) && appData.debugMode )
1138           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1139
1140         DisplayFatalError(buf, 0, 2);
1141         return;
1142
1143       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1144       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1145       case VariantGothic:     /* [HGM] should work */
1146       case VariantCapablanca: /* [HGM] should work */
1147       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1148       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1149       case VariantKnightmate: /* [HGM] should work */
1150       case VariantCylinder:   /* [HGM] untested */
1151       case VariantFalcon:     /* [HGM] untested */
1152       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1153                                  offboard interposition not understood */
1154       case VariantNormal:     /* definitely works! */
1155       case VariantWildCastle: /* pieces not automatically shuffled */
1156       case VariantNoCastle:   /* pieces not automatically shuffled */
1157       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1158       case VariantLosers:     /* should work except for win condition,
1159                                  and doesn't know captures are mandatory */
1160       case VariantSuicide:    /* should work except for win condition,
1161                                  and doesn't know captures are mandatory */
1162       case VariantGiveaway:   /* should work except for win condition,
1163                                  and doesn't know captures are mandatory */
1164       case VariantTwoKings:   /* should work */
1165       case VariantAtomic:     /* should work except for win condition */
1166       case Variant3Check:     /* should work except for win condition */
1167       case VariantShatranj:   /* should work except for all win conditions */
1168       case VariantMakruk:     /* should work except for draw countdown */
1169       case VariantASEAN :     /* should work except for draw countdown */
1170       case VariantBerolina:   /* might work if TestLegality is off */
1171       case VariantCapaRandom: /* should work */
1172       case VariantJanus:      /* should work */
1173       case VariantSuper:      /* experimental */
1174       case VariantGreat:      /* experimental, requires legality testing to be off */
1175       case VariantSChess:     /* S-Chess, should work */
1176       case VariantGrand:      /* should work */
1177       case VariantSpartan:    /* should work */
1178         break;
1179       }
1180     }
1181
1182 }
1183
1184 int
1185 NextIntegerFromString (char ** str, long * value)
1186 {
1187     int result = -1;
1188     char * s = *str;
1189
1190     while( *s == ' ' || *s == '\t' ) {
1191         s++;
1192     }
1193
1194     *value = 0;
1195
1196     if( *s >= '0' && *s <= '9' ) {
1197         while( *s >= '0' && *s <= '9' ) {
1198             *value = *value * 10 + (*s - '0');
1199             s++;
1200         }
1201
1202         result = 0;
1203     }
1204
1205     *str = s;
1206
1207     return result;
1208 }
1209
1210 int
1211 NextTimeControlFromString (char ** str, long * value)
1212 {
1213     long temp;
1214     int result = NextIntegerFromString( str, &temp );
1215
1216     if( result == 0 ) {
1217         *value = temp * 60; /* Minutes */
1218         if( **str == ':' ) {
1219             (*str)++;
1220             result = NextIntegerFromString( str, &temp );
1221             *value += temp; /* Seconds */
1222         }
1223     }
1224
1225     return result;
1226 }
1227
1228 int
1229 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1230 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1231     int result = -1, type = 0; long temp, temp2;
1232
1233     if(**str != ':') return -1; // old params remain in force!
1234     (*str)++;
1235     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1236     if( NextIntegerFromString( str, &temp ) ) return -1;
1237     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1238
1239     if(**str != '/') {
1240         /* time only: incremental or sudden-death time control */
1241         if(**str == '+') { /* increment follows; read it */
1242             (*str)++;
1243             if(**str == '!') type = *(*str)++; // Bronstein TC
1244             if(result = NextIntegerFromString( str, &temp2)) return -1;
1245             *inc = temp2 * 1000;
1246             if(**str == '.') { // read fraction of increment
1247                 char *start = ++(*str);
1248                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1249                 temp2 *= 1000;
1250                 while(start++ < *str) temp2 /= 10;
1251                 *inc += temp2;
1252             }
1253         } else *inc = 0;
1254         *moves = 0; *tc = temp * 1000; *incType = type;
1255         return 0;
1256     }
1257
1258     (*str)++; /* classical time control */
1259     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1260
1261     if(result == 0) {
1262         *moves = temp;
1263         *tc    = temp2 * 1000;
1264         *inc   = 0;
1265         *incType = type;
1266     }
1267     return result;
1268 }
1269
1270 int
1271 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1272 {   /* [HGM] get time to add from the multi-session time-control string */
1273     int incType, moves=1; /* kludge to force reading of first session */
1274     long time, increment;
1275     char *s = tcString;
1276
1277     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1278     do {
1279         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1280         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1281         if(movenr == -1) return time;    /* last move before new session     */
1282         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1283         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1284         if(!moves) return increment;     /* current session is incremental   */
1285         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1286     } while(movenr >= -1);               /* try again for next session       */
1287
1288     return 0; // no new time quota on this move
1289 }
1290
1291 int
1292 ParseTimeControl (char *tc, float ti, int mps)
1293 {
1294   long tc1;
1295   long tc2;
1296   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1297   int min, sec=0;
1298
1299   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1300   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1301       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1302   if(ti > 0) {
1303
1304     if(mps)
1305       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1306     else
1307       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1308   } else {
1309     if(mps)
1310       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1311     else
1312       snprintf(buf, MSG_SIZ, ":%s", mytc);
1313   }
1314   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1315
1316   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1317     return FALSE;
1318   }
1319
1320   if( *tc == '/' ) {
1321     /* Parse second time control */
1322     tc++;
1323
1324     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1325       return FALSE;
1326     }
1327
1328     if( tc2 == 0 ) {
1329       return FALSE;
1330     }
1331
1332     timeControl_2 = tc2 * 1000;
1333   }
1334   else {
1335     timeControl_2 = 0;
1336   }
1337
1338   if( tc1 == 0 ) {
1339     return FALSE;
1340   }
1341
1342   timeControl = tc1 * 1000;
1343
1344   if (ti >= 0) {
1345     timeIncrement = ti * 1000;  /* convert to ms */
1346     movesPerSession = 0;
1347   } else {
1348     timeIncrement = 0;
1349     movesPerSession = mps;
1350   }
1351   return TRUE;
1352 }
1353
1354 void
1355 InitBackEnd2 ()
1356 {
1357     if (appData.debugMode) {
1358 #    ifdef __GIT_VERSION
1359       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1360 #    else
1361       fprintf(debugFP, "Version: %s\n", programVersion);
1362 #    endif
1363     }
1364     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1365
1366     set_cont_sequence(appData.wrapContSeq);
1367     if (appData.matchGames > 0) {
1368         appData.matchMode = TRUE;
1369     } else if (appData.matchMode) {
1370         appData.matchGames = 1;
1371     }
1372     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1373         appData.matchGames = appData.sameColorGames;
1374     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1375         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1376         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1377     }
1378     Reset(TRUE, FALSE);
1379     if (appData.noChessProgram || first.protocolVersion == 1) {
1380       InitBackEnd3();
1381     } else {
1382       /* kludge: allow timeout for initial "feature" commands */
1383       FreezeUI();
1384       DisplayMessage("", _("Starting chess program"));
1385       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1386     }
1387 }
1388
1389 int
1390 CalculateIndex (int index, int gameNr)
1391 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1392     int res;
1393     if(index > 0) return index; // fixed nmber
1394     if(index == 0) return 1;
1395     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1396     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1397     return res;
1398 }
1399
1400 int
1401 LoadGameOrPosition (int gameNr)
1402 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1403     if (*appData.loadGameFile != NULLCHAR) {
1404         if (!LoadGameFromFile(appData.loadGameFile,
1405                 CalculateIndex(appData.loadGameIndex, gameNr),
1406                               appData.loadGameFile, FALSE)) {
1407             DisplayFatalError(_("Bad game file"), 0, 1);
1408             return 0;
1409         }
1410     } else if (*appData.loadPositionFile != NULLCHAR) {
1411         if (!LoadPositionFromFile(appData.loadPositionFile,
1412                 CalculateIndex(appData.loadPositionIndex, gameNr),
1413                                   appData.loadPositionFile)) {
1414             DisplayFatalError(_("Bad position file"), 0, 1);
1415             return 0;
1416         }
1417     }
1418     return 1;
1419 }
1420
1421 void
1422 ReserveGame (int gameNr, char resChar)
1423 {
1424     FILE *tf = fopen(appData.tourneyFile, "r+");
1425     char *p, *q, c, buf[MSG_SIZ];
1426     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1427     safeStrCpy(buf, lastMsg, MSG_SIZ);
1428     DisplayMessage(_("Pick new game"), "");
1429     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1430     ParseArgsFromFile(tf);
1431     p = q = appData.results;
1432     if(appData.debugMode) {
1433       char *r = appData.participants;
1434       fprintf(debugFP, "results = '%s'\n", p);
1435       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1436       fprintf(debugFP, "\n");
1437     }
1438     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1439     nextGame = q - p;
1440     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1441     safeStrCpy(q, p, strlen(p) + 2);
1442     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1443     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1444     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1445         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1446         q[nextGame] = '*';
1447     }
1448     fseek(tf, -(strlen(p)+4), SEEK_END);
1449     c = fgetc(tf);
1450     if(c != '"') // depending on DOS or Unix line endings we can be one off
1451          fseek(tf, -(strlen(p)+2), SEEK_END);
1452     else fseek(tf, -(strlen(p)+3), SEEK_END);
1453     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1454     DisplayMessage(buf, "");
1455     free(p); appData.results = q;
1456     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1457        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1458       int round = appData.defaultMatchGames * appData.tourneyType;
1459       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1460          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1461         UnloadEngine(&first);  // next game belongs to other pairing;
1462         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1463     }
1464     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1465 }
1466
1467 void
1468 MatchEvent (int mode)
1469 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1470         int dummy;
1471         if(matchMode) { // already in match mode: switch it off
1472             abortMatch = TRUE;
1473             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1474             return;
1475         }
1476 //      if(gameMode != BeginningOfGame) {
1477 //          DisplayError(_("You can only start a match from the initial position."), 0);
1478 //          return;
1479 //      }
1480         abortMatch = FALSE;
1481         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1482         /* Set up machine vs. machine match */
1483         nextGame = 0;
1484         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1485         if(appData.tourneyFile[0]) {
1486             ReserveGame(-1, 0);
1487             if(nextGame > appData.matchGames) {
1488                 char buf[MSG_SIZ];
1489                 if(strchr(appData.results, '*') == NULL) {
1490                     FILE *f;
1491                     appData.tourneyCycles++;
1492                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1493                         fclose(f);
1494                         NextTourneyGame(-1, &dummy);
1495                         ReserveGame(-1, 0);
1496                         if(nextGame <= appData.matchGames) {
1497                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1498                             matchMode = mode;
1499                             ScheduleDelayedEvent(NextMatchGame, 10000);
1500                             return;
1501                         }
1502                     }
1503                 }
1504                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1505                 DisplayError(buf, 0);
1506                 appData.tourneyFile[0] = 0;
1507                 return;
1508             }
1509         } else
1510         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1511             DisplayFatalError(_("Can't have a match with no chess programs"),
1512                               0, 2);
1513             return;
1514         }
1515         matchMode = mode;
1516         matchGame = roundNr = 1;
1517         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1518         NextMatchGame();
1519 }
1520
1521 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1522
1523 void
1524 InitBackEnd3 P((void))
1525 {
1526     GameMode initialMode;
1527     char buf[MSG_SIZ];
1528     int err, len;
1529
1530     InitChessProgram(&first, startedFromSetupPosition);
1531
1532     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1533         free(programVersion);
1534         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1535         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1536         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1537     }
1538
1539     if (appData.icsActive) {
1540 #ifdef WIN32
1541         /* [DM] Make a console window if needed [HGM] merged ifs */
1542         ConsoleCreate();
1543 #endif
1544         err = establish();
1545         if (err != 0)
1546           {
1547             if (*appData.icsCommPort != NULLCHAR)
1548               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1549                              appData.icsCommPort);
1550             else
1551               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1552                         appData.icsHost, appData.icsPort);
1553
1554             if( (len >= MSG_SIZ) && appData.debugMode )
1555               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1556
1557             DisplayFatalError(buf, err, 1);
1558             return;
1559         }
1560         SetICSMode();
1561         telnetISR =
1562           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1563         fromUserISR =
1564           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1565         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1566             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1567     } else if (appData.noChessProgram) {
1568         SetNCPMode();
1569     } else {
1570         SetGNUMode();
1571     }
1572
1573     if (*appData.cmailGameName != NULLCHAR) {
1574         SetCmailMode();
1575         OpenLoopback(&cmailPR);
1576         cmailISR =
1577           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1578     }
1579
1580     ThawUI();
1581     DisplayMessage("", "");
1582     if (StrCaseCmp(appData.initialMode, "") == 0) {
1583       initialMode = BeginningOfGame;
1584       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1585         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1586         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1587         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1588         ModeHighlight();
1589       }
1590     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1591       initialMode = TwoMachinesPlay;
1592     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1593       initialMode = AnalyzeFile;
1594     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1595       initialMode = AnalyzeMode;
1596     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1597       initialMode = MachinePlaysWhite;
1598     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1599       initialMode = MachinePlaysBlack;
1600     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1601       initialMode = EditGame;
1602     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1603       initialMode = EditPosition;
1604     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1605       initialMode = Training;
1606     } else {
1607       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1608       if( (len >= MSG_SIZ) && appData.debugMode )
1609         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1610
1611       DisplayFatalError(buf, 0, 2);
1612       return;
1613     }
1614
1615     if (appData.matchMode) {
1616         if(appData.tourneyFile[0]) { // start tourney from command line
1617             FILE *f;
1618             if(f = fopen(appData.tourneyFile, "r")) {
1619                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1620                 fclose(f);
1621                 appData.clockMode = TRUE;
1622                 SetGNUMode();
1623             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1624         }
1625         MatchEvent(TRUE);
1626     } else if (*appData.cmailGameName != NULLCHAR) {
1627         /* Set up cmail mode */
1628         ReloadCmailMsgEvent(TRUE);
1629     } else {
1630         /* Set up other modes */
1631         if (initialMode == AnalyzeFile) {
1632           if (*appData.loadGameFile == NULLCHAR) {
1633             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1634             return;
1635           }
1636         }
1637         if (*appData.loadGameFile != NULLCHAR) {
1638             (void) LoadGameFromFile(appData.loadGameFile,
1639                                     appData.loadGameIndex,
1640                                     appData.loadGameFile, TRUE);
1641         } else if (*appData.loadPositionFile != NULLCHAR) {
1642             (void) LoadPositionFromFile(appData.loadPositionFile,
1643                                         appData.loadPositionIndex,
1644                                         appData.loadPositionFile);
1645             /* [HGM] try to make self-starting even after FEN load */
1646             /* to allow automatic setup of fairy variants with wtm */
1647             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1648                 gameMode = BeginningOfGame;
1649                 setboardSpoiledMachineBlack = 1;
1650             }
1651             /* [HGM] loadPos: make that every new game uses the setup */
1652             /* from file as long as we do not switch variant          */
1653             if(!blackPlaysFirst) {
1654                 startedFromPositionFile = TRUE;
1655                 CopyBoard(filePosition, boards[0]);
1656             }
1657         }
1658         if (initialMode == AnalyzeMode) {
1659           if (appData.noChessProgram) {
1660             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1661             return;
1662           }
1663           if (appData.icsActive) {
1664             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1665             return;
1666           }
1667           AnalyzeModeEvent();
1668         } else if (initialMode == AnalyzeFile) {
1669           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1670           ShowThinkingEvent();
1671           AnalyzeFileEvent();
1672           AnalysisPeriodicEvent(1);
1673         } else if (initialMode == MachinePlaysWhite) {
1674           if (appData.noChessProgram) {
1675             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1676                               0, 2);
1677             return;
1678           }
1679           if (appData.icsActive) {
1680             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1681                               0, 2);
1682             return;
1683           }
1684           MachineWhiteEvent();
1685         } else if (initialMode == MachinePlaysBlack) {
1686           if (appData.noChessProgram) {
1687             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1688                               0, 2);
1689             return;
1690           }
1691           if (appData.icsActive) {
1692             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1693                               0, 2);
1694             return;
1695           }
1696           MachineBlackEvent();
1697         } else if (initialMode == TwoMachinesPlay) {
1698           if (appData.noChessProgram) {
1699             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1700                               0, 2);
1701             return;
1702           }
1703           if (appData.icsActive) {
1704             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1705                               0, 2);
1706             return;
1707           }
1708           TwoMachinesEvent();
1709         } else if (initialMode == EditGame) {
1710           EditGameEvent();
1711         } else if (initialMode == EditPosition) {
1712           EditPositionEvent();
1713         } else if (initialMode == Training) {
1714           if (*appData.loadGameFile == NULLCHAR) {
1715             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1716             return;
1717           }
1718           TrainingEvent();
1719         }
1720     }
1721 }
1722
1723 void
1724 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1725 {
1726     DisplayBook(current+1);
1727
1728     MoveHistorySet( movelist, first, last, current, pvInfoList );
1729
1730     EvalGraphSet( first, last, current, pvInfoList );
1731
1732     MakeEngineOutputTitle();
1733 }
1734
1735 /*
1736  * Establish will establish a contact to a remote host.port.
1737  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1738  *  used to talk to the host.
1739  * Returns 0 if okay, error code if not.
1740  */
1741 int
1742 establish ()
1743 {
1744     char buf[MSG_SIZ];
1745
1746     if (*appData.icsCommPort != NULLCHAR) {
1747         /* Talk to the host through a serial comm port */
1748         return OpenCommPort(appData.icsCommPort, &icsPR);
1749
1750     } else if (*appData.gateway != NULLCHAR) {
1751         if (*appData.remoteShell == NULLCHAR) {
1752             /* Use the rcmd protocol to run telnet program on a gateway host */
1753             snprintf(buf, sizeof(buf), "%s %s %s",
1754                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1755             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1756
1757         } else {
1758             /* Use the rsh program to run telnet program on a gateway host */
1759             if (*appData.remoteUser == NULLCHAR) {
1760                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1761                         appData.gateway, appData.telnetProgram,
1762                         appData.icsHost, appData.icsPort);
1763             } else {
1764                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1765                         appData.remoteShell, appData.gateway,
1766                         appData.remoteUser, appData.telnetProgram,
1767                         appData.icsHost, appData.icsPort);
1768             }
1769             return StartChildProcess(buf, "", &icsPR);
1770
1771         }
1772     } else if (appData.useTelnet) {
1773         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1774
1775     } else {
1776         /* TCP socket interface differs somewhat between
1777            Unix and NT; handle details in the front end.
1778            */
1779         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1780     }
1781 }
1782
1783 void
1784 EscapeExpand (char *p, char *q)
1785 {       // [HGM] initstring: routine to shape up string arguments
1786         while(*p++ = *q++) if(p[-1] == '\\')
1787             switch(*q++) {
1788                 case 'n': p[-1] = '\n'; break;
1789                 case 'r': p[-1] = '\r'; break;
1790                 case 't': p[-1] = '\t'; break;
1791                 case '\\': p[-1] = '\\'; break;
1792                 case 0: *p = 0; return;
1793                 default: p[-1] = q[-1]; break;
1794             }
1795 }
1796
1797 void
1798 show_bytes (FILE *fp, char *buf, int count)
1799 {
1800     while (count--) {
1801         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1802             fprintf(fp, "\\%03o", *buf & 0xff);
1803         } else {
1804             putc(*buf, fp);
1805         }
1806         buf++;
1807     }
1808     fflush(fp);
1809 }
1810
1811 /* Returns an errno value */
1812 int
1813 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1814 {
1815     char buf[8192], *p, *q, *buflim;
1816     int left, newcount, outcount;
1817
1818     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1819         *appData.gateway != NULLCHAR) {
1820         if (appData.debugMode) {
1821             fprintf(debugFP, ">ICS: ");
1822             show_bytes(debugFP, message, count);
1823             fprintf(debugFP, "\n");
1824         }
1825         return OutputToProcess(pr, message, count, outError);
1826     }
1827
1828     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1829     p = message;
1830     q = buf;
1831     left = count;
1832     newcount = 0;
1833     while (left) {
1834         if (q >= buflim) {
1835             if (appData.debugMode) {
1836                 fprintf(debugFP, ">ICS: ");
1837                 show_bytes(debugFP, buf, newcount);
1838                 fprintf(debugFP, "\n");
1839             }
1840             outcount = OutputToProcess(pr, buf, newcount, outError);
1841             if (outcount < newcount) return -1; /* to be sure */
1842             q = buf;
1843             newcount = 0;
1844         }
1845         if (*p == '\n') {
1846             *q++ = '\r';
1847             newcount++;
1848         } else if (((unsigned char) *p) == TN_IAC) {
1849             *q++ = (char) TN_IAC;
1850             newcount ++;
1851         }
1852         *q++ = *p++;
1853         newcount++;
1854         left--;
1855     }
1856     if (appData.debugMode) {
1857         fprintf(debugFP, ">ICS: ");
1858         show_bytes(debugFP, buf, newcount);
1859         fprintf(debugFP, "\n");
1860     }
1861     outcount = OutputToProcess(pr, buf, newcount, outError);
1862     if (outcount < newcount) return -1; /* to be sure */
1863     return count;
1864 }
1865
1866 void
1867 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1868 {
1869     int outError, outCount;
1870     static int gotEof = 0;
1871     static FILE *ini;
1872
1873     /* Pass data read from player on to ICS */
1874     if (count > 0) {
1875         gotEof = 0;
1876         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1877         if (outCount < count) {
1878             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1879         }
1880         if(have_sent_ICS_logon == 2) {
1881           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1882             fprintf(ini, "%s", message);
1883             have_sent_ICS_logon = 3;
1884           } else
1885             have_sent_ICS_logon = 1;
1886         } else if(have_sent_ICS_logon == 3) {
1887             fprintf(ini, "%s", message);
1888             fclose(ini);
1889           have_sent_ICS_logon = 1;
1890         }
1891     } else if (count < 0) {
1892         RemoveInputSource(isr);
1893         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1894     } else if (gotEof++ > 0) {
1895         RemoveInputSource(isr);
1896         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1897     }
1898 }
1899
1900 void
1901 KeepAlive ()
1902 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1903     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1904     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1905     SendToICS("date\n");
1906     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1907 }
1908
1909 /* added routine for printf style output to ics */
1910 void
1911 ics_printf (char *format, ...)
1912 {
1913     char buffer[MSG_SIZ];
1914     va_list args;
1915
1916     va_start(args, format);
1917     vsnprintf(buffer, sizeof(buffer), format, args);
1918     buffer[sizeof(buffer)-1] = '\0';
1919     SendToICS(buffer);
1920     va_end(args);
1921 }
1922
1923 void
1924 SendToICS (char *s)
1925 {
1926     int count, outCount, outError;
1927
1928     if (icsPR == NoProc) return;
1929
1930     count = strlen(s);
1931     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1932     if (outCount < count) {
1933         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1934     }
1935 }
1936
1937 /* This is used for sending logon scripts to the ICS. Sending
1938    without a delay causes problems when using timestamp on ICC
1939    (at least on my machine). */
1940 void
1941 SendToICSDelayed (char *s, long msdelay)
1942 {
1943     int count, outCount, outError;
1944
1945     if (icsPR == NoProc) return;
1946
1947     count = strlen(s);
1948     if (appData.debugMode) {
1949         fprintf(debugFP, ">ICS: ");
1950         show_bytes(debugFP, s, count);
1951         fprintf(debugFP, "\n");
1952     }
1953     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1954                                       msdelay);
1955     if (outCount < count) {
1956         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1957     }
1958 }
1959
1960
1961 /* Remove all highlighting escape sequences in s
1962    Also deletes any suffix starting with '('
1963    */
1964 char *
1965 StripHighlightAndTitle (char *s)
1966 {
1967     static char retbuf[MSG_SIZ];
1968     char *p = retbuf;
1969
1970     while (*s != NULLCHAR) {
1971         while (*s == '\033') {
1972             while (*s != NULLCHAR && !isalpha(*s)) s++;
1973             if (*s != NULLCHAR) s++;
1974         }
1975         while (*s != NULLCHAR && *s != '\033') {
1976             if (*s == '(' || *s == '[') {
1977                 *p = NULLCHAR;
1978                 return retbuf;
1979             }
1980             *p++ = *s++;
1981         }
1982     }
1983     *p = NULLCHAR;
1984     return retbuf;
1985 }
1986
1987 /* Remove all highlighting escape sequences in s */
1988 char *
1989 StripHighlight (char *s)
1990 {
1991     static char retbuf[MSG_SIZ];
1992     char *p = retbuf;
1993
1994     while (*s != NULLCHAR) {
1995         while (*s == '\033') {
1996             while (*s != NULLCHAR && !isalpha(*s)) s++;
1997             if (*s != NULLCHAR) s++;
1998         }
1999         while (*s != NULLCHAR && *s != '\033') {
2000             *p++ = *s++;
2001         }
2002     }
2003     *p = NULLCHAR;
2004     return retbuf;
2005 }
2006
2007 char *variantNames[] = VARIANT_NAMES;
2008 char *
2009 VariantName (VariantClass v)
2010 {
2011     return variantNames[v];
2012 }
2013
2014
2015 /* Identify a variant from the strings the chess servers use or the
2016    PGN Variant tag names we use. */
2017 VariantClass
2018 StringToVariant (char *e)
2019 {
2020     char *p;
2021     int wnum = -1;
2022     VariantClass v = VariantNormal;
2023     int i, found = FALSE;
2024     char buf[MSG_SIZ];
2025     int len;
2026
2027     if (!e) return v;
2028
2029     /* [HGM] skip over optional board-size prefixes */
2030     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2031         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2032         while( *e++ != '_');
2033     }
2034
2035     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2036         v = VariantNormal;
2037         found = TRUE;
2038     } else
2039     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2040       if (StrCaseStr(e, variantNames[i])) {
2041         v = (VariantClass) i;
2042         found = TRUE;
2043         break;
2044       }
2045     }
2046
2047     if (!found) {
2048       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2049           || StrCaseStr(e, "wild/fr")
2050           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2051         v = VariantFischeRandom;
2052       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2053                  (i = 1, p = StrCaseStr(e, "w"))) {
2054         p += i;
2055         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2056         if (isdigit(*p)) {
2057           wnum = atoi(p);
2058         } else {
2059           wnum = -1;
2060         }
2061         switch (wnum) {
2062         case 0: /* FICS only, actually */
2063         case 1:
2064           /* Castling legal even if K starts on d-file */
2065           v = VariantWildCastle;
2066           break;
2067         case 2:
2068         case 3:
2069         case 4:
2070           /* Castling illegal even if K & R happen to start in
2071              normal positions. */
2072           v = VariantNoCastle;
2073           break;
2074         case 5:
2075         case 7:
2076         case 8:
2077         case 10:
2078         case 11:
2079         case 12:
2080         case 13:
2081         case 14:
2082         case 15:
2083         case 18:
2084         case 19:
2085           /* Castling legal iff K & R start in normal positions */
2086           v = VariantNormal;
2087           break;
2088         case 6:
2089         case 20:
2090         case 21:
2091           /* Special wilds for position setup; unclear what to do here */
2092           v = VariantLoadable;
2093           break;
2094         case 9:
2095           /* Bizarre ICC game */
2096           v = VariantTwoKings;
2097           break;
2098         case 16:
2099           v = VariantKriegspiel;
2100           break;
2101         case 17:
2102           v = VariantLosers;
2103           break;
2104         case 22:
2105           v = VariantFischeRandom;
2106           break;
2107         case 23:
2108           v = VariantCrazyhouse;
2109           break;
2110         case 24:
2111           v = VariantBughouse;
2112           break;
2113         case 25:
2114           v = Variant3Check;
2115           break;
2116         case 26:
2117           /* Not quite the same as FICS suicide! */
2118           v = VariantGiveaway;
2119           break;
2120         case 27:
2121           v = VariantAtomic;
2122           break;
2123         case 28:
2124           v = VariantShatranj;
2125           break;
2126
2127         /* Temporary names for future ICC types.  The name *will* change in
2128            the next xboard/WinBoard release after ICC defines it. */
2129         case 29:
2130           v = Variant29;
2131           break;
2132         case 30:
2133           v = Variant30;
2134           break;
2135         case 31:
2136           v = Variant31;
2137           break;
2138         case 32:
2139           v = Variant32;
2140           break;
2141         case 33:
2142           v = Variant33;
2143           break;
2144         case 34:
2145           v = Variant34;
2146           break;
2147         case 35:
2148           v = Variant35;
2149           break;
2150         case 36:
2151           v = Variant36;
2152           break;
2153         case 37:
2154           v = VariantShogi;
2155           break;
2156         case 38:
2157           v = VariantXiangqi;
2158           break;
2159         case 39:
2160           v = VariantCourier;
2161           break;
2162         case 40:
2163           v = VariantGothic;
2164           break;
2165         case 41:
2166           v = VariantCapablanca;
2167           break;
2168         case 42:
2169           v = VariantKnightmate;
2170           break;
2171         case 43:
2172           v = VariantFairy;
2173           break;
2174         case 44:
2175           v = VariantCylinder;
2176           break;
2177         case 45:
2178           v = VariantFalcon;
2179           break;
2180         case 46:
2181           v = VariantCapaRandom;
2182           break;
2183         case 47:
2184           v = VariantBerolina;
2185           break;
2186         case 48:
2187           v = VariantJanus;
2188           break;
2189         case 49:
2190           v = VariantSuper;
2191           break;
2192         case 50:
2193           v = VariantGreat;
2194           break;
2195         case -1:
2196           /* Found "wild" or "w" in the string but no number;
2197              must assume it's normal chess. */
2198           v = VariantNormal;
2199           break;
2200         default:
2201           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2202           if( (len >= MSG_SIZ) && appData.debugMode )
2203             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2204
2205           DisplayError(buf, 0);
2206           v = VariantUnknown;
2207           break;
2208         }
2209       }
2210     }
2211     if (appData.debugMode) {
2212       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2213               e, wnum, VariantName(v));
2214     }
2215     return v;
2216 }
2217
2218 static int leftover_start = 0, leftover_len = 0;
2219 char star_match[STAR_MATCH_N][MSG_SIZ];
2220
2221 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2222    advance *index beyond it, and set leftover_start to the new value of
2223    *index; else return FALSE.  If pattern contains the character '*', it
2224    matches any sequence of characters not containing '\r', '\n', or the
2225    character following the '*' (if any), and the matched sequence(s) are
2226    copied into star_match.
2227    */
2228 int
2229 looking_at ( char *buf, int *index, char *pattern)
2230 {
2231     char *bufp = &buf[*index], *patternp = pattern;
2232     int star_count = 0;
2233     char *matchp = star_match[0];
2234
2235     for (;;) {
2236         if (*patternp == NULLCHAR) {
2237             *index = leftover_start = bufp - buf;
2238             *matchp = NULLCHAR;
2239             return TRUE;
2240         }
2241         if (*bufp == NULLCHAR) return FALSE;
2242         if (*patternp == '*') {
2243             if (*bufp == *(patternp + 1)) {
2244                 *matchp = NULLCHAR;
2245                 matchp = star_match[++star_count];
2246                 patternp += 2;
2247                 bufp++;
2248                 continue;
2249             } else if (*bufp == '\n' || *bufp == '\r') {
2250                 patternp++;
2251                 if (*patternp == NULLCHAR)
2252                   continue;
2253                 else
2254                   return FALSE;
2255             } else {
2256                 *matchp++ = *bufp++;
2257                 continue;
2258             }
2259         }
2260         if (*patternp != *bufp) return FALSE;
2261         patternp++;
2262         bufp++;
2263     }
2264 }
2265
2266 void
2267 SendToPlayer (char *data, int length)
2268 {
2269     int error, outCount;
2270     outCount = OutputToProcess(NoProc, data, length, &error);
2271     if (outCount < length) {
2272         DisplayFatalError(_("Error writing to display"), error, 1);
2273     }
2274 }
2275
2276 void
2277 PackHolding (char packed[], char *holding)
2278 {
2279     char *p = holding;
2280     char *q = packed;
2281     int runlength = 0;
2282     int curr = 9999;
2283     do {
2284         if (*p == curr) {
2285             runlength++;
2286         } else {
2287             switch (runlength) {
2288               case 0:
2289                 break;
2290               case 1:
2291                 *q++ = curr;
2292                 break;
2293               case 2:
2294                 *q++ = curr;
2295                 *q++ = curr;
2296                 break;
2297               default:
2298                 sprintf(q, "%d", runlength);
2299                 while (*q) q++;
2300                 *q++ = curr;
2301                 break;
2302             }
2303             runlength = 1;
2304             curr = *p;
2305         }
2306     } while (*p++);
2307     *q = NULLCHAR;
2308 }
2309
2310 /* Telnet protocol requests from the front end */
2311 void
2312 TelnetRequest (unsigned char ddww, unsigned char option)
2313 {
2314     unsigned char msg[3];
2315     int outCount, outError;
2316
2317     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2318
2319     if (appData.debugMode) {
2320         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2321         switch (ddww) {
2322           case TN_DO:
2323             ddwwStr = "DO";
2324             break;
2325           case TN_DONT:
2326             ddwwStr = "DONT";
2327             break;
2328           case TN_WILL:
2329             ddwwStr = "WILL";
2330             break;
2331           case TN_WONT:
2332             ddwwStr = "WONT";
2333             break;
2334           default:
2335             ddwwStr = buf1;
2336             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2337             break;
2338         }
2339         switch (option) {
2340           case TN_ECHO:
2341             optionStr = "ECHO";
2342             break;
2343           default:
2344             optionStr = buf2;
2345             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2346             break;
2347         }
2348         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2349     }
2350     msg[0] = TN_IAC;
2351     msg[1] = ddww;
2352     msg[2] = option;
2353     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2354     if (outCount < 3) {
2355         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2356     }
2357 }
2358
2359 void
2360 DoEcho ()
2361 {
2362     if (!appData.icsActive) return;
2363     TelnetRequest(TN_DO, TN_ECHO);
2364 }
2365
2366 void
2367 DontEcho ()
2368 {
2369     if (!appData.icsActive) return;
2370     TelnetRequest(TN_DONT, TN_ECHO);
2371 }
2372
2373 void
2374 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2375 {
2376     /* put the holdings sent to us by the server on the board holdings area */
2377     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2378     char p;
2379     ChessSquare piece;
2380
2381     if(gameInfo.holdingsWidth < 2)  return;
2382     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2383         return; // prevent overwriting by pre-board holdings
2384
2385     if( (int)lowestPiece >= BlackPawn ) {
2386         holdingsColumn = 0;
2387         countsColumn = 1;
2388         holdingsStartRow = BOARD_HEIGHT-1;
2389         direction = -1;
2390     } else {
2391         holdingsColumn = BOARD_WIDTH-1;
2392         countsColumn = BOARD_WIDTH-2;
2393         holdingsStartRow = 0;
2394         direction = 1;
2395     }
2396
2397     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2398         board[i][holdingsColumn] = EmptySquare;
2399         board[i][countsColumn]   = (ChessSquare) 0;
2400     }
2401     while( (p=*holdings++) != NULLCHAR ) {
2402         piece = CharToPiece( ToUpper(p) );
2403         if(piece == EmptySquare) continue;
2404         /*j = (int) piece - (int) WhitePawn;*/
2405         j = PieceToNumber(piece);
2406         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2407         if(j < 0) continue;               /* should not happen */
2408         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2409         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2410         board[holdingsStartRow+j*direction][countsColumn]++;
2411     }
2412 }
2413
2414
2415 void
2416 VariantSwitch (Board board, VariantClass newVariant)
2417 {
2418    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2419    static Board oldBoard;
2420
2421    startedFromPositionFile = FALSE;
2422    if(gameInfo.variant == newVariant) return;
2423
2424    /* [HGM] This routine is called each time an assignment is made to
2425     * gameInfo.variant during a game, to make sure the board sizes
2426     * are set to match the new variant. If that means adding or deleting
2427     * holdings, we shift the playing board accordingly
2428     * This kludge is needed because in ICS observe mode, we get boards
2429     * of an ongoing game without knowing the variant, and learn about the
2430     * latter only later. This can be because of the move list we requested,
2431     * in which case the game history is refilled from the beginning anyway,
2432     * but also when receiving holdings of a crazyhouse game. In the latter
2433     * case we want to add those holdings to the already received position.
2434     */
2435
2436
2437    if (appData.debugMode) {
2438      fprintf(debugFP, "Switch board from %s to %s\n",
2439              VariantName(gameInfo.variant), VariantName(newVariant));
2440      setbuf(debugFP, NULL);
2441    }
2442    shuffleOpenings = 0;       /* [HGM] shuffle */
2443    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2444    switch(newVariant)
2445      {
2446      case VariantShogi:
2447        newWidth = 9;  newHeight = 9;
2448        gameInfo.holdingsSize = 7;
2449      case VariantBughouse:
2450      case VariantCrazyhouse:
2451        newHoldingsWidth = 2; break;
2452      case VariantGreat:
2453        newWidth = 10;
2454      case VariantSuper:
2455        newHoldingsWidth = 2;
2456        gameInfo.holdingsSize = 8;
2457        break;
2458      case VariantGothic:
2459      case VariantCapablanca:
2460      case VariantCapaRandom:
2461        newWidth = 10;
2462      default:
2463        newHoldingsWidth = gameInfo.holdingsSize = 0;
2464      };
2465
2466    if(newWidth  != gameInfo.boardWidth  ||
2467       newHeight != gameInfo.boardHeight ||
2468       newHoldingsWidth != gameInfo.holdingsWidth ) {
2469
2470      /* shift position to new playing area, if needed */
2471      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2472        for(i=0; i<BOARD_HEIGHT; i++)
2473          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2474            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2475              board[i][j];
2476        for(i=0; i<newHeight; i++) {
2477          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2478          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2479        }
2480      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2481        for(i=0; i<BOARD_HEIGHT; i++)
2482          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2483            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2484              board[i][j];
2485      }
2486      board[HOLDINGS_SET] = 0;
2487      gameInfo.boardWidth  = newWidth;
2488      gameInfo.boardHeight = newHeight;
2489      gameInfo.holdingsWidth = newHoldingsWidth;
2490      gameInfo.variant = newVariant;
2491      InitDrawingSizes(-2, 0);
2492    } else gameInfo.variant = newVariant;
2493    CopyBoard(oldBoard, board);   // remember correctly formatted board
2494      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2495    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2496 }
2497
2498 static int loggedOn = FALSE;
2499
2500 /*-- Game start info cache: --*/
2501 int gs_gamenum;
2502 char gs_kind[MSG_SIZ];
2503 static char player1Name[128] = "";
2504 static char player2Name[128] = "";
2505 static char cont_seq[] = "\n\\   ";
2506 static int player1Rating = -1;
2507 static int player2Rating = -1;
2508 /*----------------------------*/
2509
2510 ColorClass curColor = ColorNormal;
2511 int suppressKibitz = 0;
2512
2513 // [HGM] seekgraph
2514 Boolean soughtPending = FALSE;
2515 Boolean seekGraphUp;
2516 #define MAX_SEEK_ADS 200
2517 #define SQUARE 0x80
2518 char *seekAdList[MAX_SEEK_ADS];
2519 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2520 float tcList[MAX_SEEK_ADS];
2521 char colorList[MAX_SEEK_ADS];
2522 int nrOfSeekAds = 0;
2523 int minRating = 1010, maxRating = 2800;
2524 int hMargin = 10, vMargin = 20, h, w;
2525 extern int squareSize, lineGap;
2526
2527 void
2528 PlotSeekAd (int i)
2529 {
2530         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2531         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2532         if(r < minRating+100 && r >=0 ) r = minRating+100;
2533         if(r > maxRating) r = maxRating;
2534         if(tc < 1.f) tc = 1.f;
2535         if(tc > 95.f) tc = 95.f;
2536         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2537         y = ((double)r - minRating)/(maxRating - minRating)
2538             * (h-vMargin-squareSize/8-1) + vMargin;
2539         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2540         if(strstr(seekAdList[i], " u ")) color = 1;
2541         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2542            !strstr(seekAdList[i], "bullet") &&
2543            !strstr(seekAdList[i], "blitz") &&
2544            !strstr(seekAdList[i], "standard") ) color = 2;
2545         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2546         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2547 }
2548
2549 void
2550 PlotSingleSeekAd (int i)
2551 {
2552         PlotSeekAd(i);
2553 }
2554
2555 void
2556 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2557 {
2558         char buf[MSG_SIZ], *ext = "";
2559         VariantClass v = StringToVariant(type);
2560         if(strstr(type, "wild")) {
2561             ext = type + 4; // append wild number
2562             if(v == VariantFischeRandom) type = "chess960"; else
2563             if(v == VariantLoadable) type = "setup"; else
2564             type = VariantName(v);
2565         }
2566         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2567         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2568             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2569             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2570             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2571             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2572             seekNrList[nrOfSeekAds] = nr;
2573             zList[nrOfSeekAds] = 0;
2574             seekAdList[nrOfSeekAds++] = StrSave(buf);
2575             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2576         }
2577 }
2578
2579 void
2580 EraseSeekDot (int i)
2581 {
2582     int x = xList[i], y = yList[i], d=squareSize/4, k;
2583     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2584     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2585     // now replot every dot that overlapped
2586     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2587         int xx = xList[k], yy = yList[k];
2588         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2589             DrawSeekDot(xx, yy, colorList[k]);
2590     }
2591 }
2592
2593 void
2594 RemoveSeekAd (int nr)
2595 {
2596         int i;
2597         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2598             EraseSeekDot(i);
2599             if(seekAdList[i]) free(seekAdList[i]);
2600             seekAdList[i] = seekAdList[--nrOfSeekAds];
2601             seekNrList[i] = seekNrList[nrOfSeekAds];
2602             ratingList[i] = ratingList[nrOfSeekAds];
2603             colorList[i]  = colorList[nrOfSeekAds];
2604             tcList[i] = tcList[nrOfSeekAds];
2605             xList[i]  = xList[nrOfSeekAds];
2606             yList[i]  = yList[nrOfSeekAds];
2607             zList[i]  = zList[nrOfSeekAds];
2608             seekAdList[nrOfSeekAds] = NULL;
2609             break;
2610         }
2611 }
2612
2613 Boolean
2614 MatchSoughtLine (char *line)
2615 {
2616     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2617     int nr, base, inc, u=0; char dummy;
2618
2619     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2620        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2621        (u=1) &&
2622        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2623         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2624         // match: compact and save the line
2625         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2626         return TRUE;
2627     }
2628     return FALSE;
2629 }
2630
2631 int
2632 DrawSeekGraph ()
2633 {
2634     int i;
2635     if(!seekGraphUp) return FALSE;
2636     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2637     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2638
2639     DrawSeekBackground(0, 0, w, h);
2640     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2641     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2642     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2643         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2644         yy = h-1-yy;
2645         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2646         if(i%500 == 0) {
2647             char buf[MSG_SIZ];
2648             snprintf(buf, MSG_SIZ, "%d", i);
2649             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2650         }
2651     }
2652     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2653     for(i=1; i<100; i+=(i<10?1:5)) {
2654         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2655         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2656         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2657             char buf[MSG_SIZ];
2658             snprintf(buf, MSG_SIZ, "%d", i);
2659             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2660         }
2661     }
2662     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2663     return TRUE;
2664 }
2665
2666 int
2667 SeekGraphClick (ClickType click, int x, int y, int moving)
2668 {
2669     static int lastDown = 0, displayed = 0, lastSecond;
2670     if(y < 0) return FALSE;
2671     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2672         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2673         if(!seekGraphUp) return FALSE;
2674         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2675         DrawPosition(TRUE, NULL);
2676         return TRUE;
2677     }
2678     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2679         if(click == Release || moving) return FALSE;
2680         nrOfSeekAds = 0;
2681         soughtPending = TRUE;
2682         SendToICS(ics_prefix);
2683         SendToICS("sought\n"); // should this be "sought all"?
2684     } else { // issue challenge based on clicked ad
2685         int dist = 10000; int i, closest = 0, second = 0;
2686         for(i=0; i<nrOfSeekAds; i++) {
2687             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2688             if(d < dist) { dist = d; closest = i; }
2689             second += (d - zList[i] < 120); // count in-range ads
2690             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2691         }
2692         if(dist < 120) {
2693             char buf[MSG_SIZ];
2694             second = (second > 1);
2695             if(displayed != closest || second != lastSecond) {
2696                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2697                 lastSecond = second; displayed = closest;
2698             }
2699             if(click == Press) {
2700                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2701                 lastDown = closest;
2702                 return TRUE;
2703             } // on press 'hit', only show info
2704             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2705             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2706             SendToICS(ics_prefix);
2707             SendToICS(buf);
2708             return TRUE; // let incoming board of started game pop down the graph
2709         } else if(click == Release) { // release 'miss' is ignored
2710             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2711             if(moving == 2) { // right up-click
2712                 nrOfSeekAds = 0; // refresh graph
2713                 soughtPending = TRUE;
2714                 SendToICS(ics_prefix);
2715                 SendToICS("sought\n"); // should this be "sought all"?
2716             }
2717             return TRUE;
2718         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2719         // press miss or release hit 'pop down' seek graph
2720         seekGraphUp = FALSE;
2721         DrawPosition(TRUE, NULL);
2722     }
2723     return TRUE;
2724 }
2725
2726 void
2727 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2728 {
2729 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2730 #define STARTED_NONE 0
2731 #define STARTED_MOVES 1
2732 #define STARTED_BOARD 2
2733 #define STARTED_OBSERVE 3
2734 #define STARTED_HOLDINGS 4
2735 #define STARTED_CHATTER 5
2736 #define STARTED_COMMENT 6
2737 #define STARTED_MOVES_NOHIDE 7
2738
2739     static int started = STARTED_NONE;
2740     static char parse[20000];
2741     static int parse_pos = 0;
2742     static char buf[BUF_SIZE + 1];
2743     static int firstTime = TRUE, intfSet = FALSE;
2744     static ColorClass prevColor = ColorNormal;
2745     static int savingComment = FALSE;
2746     static int cmatch = 0; // continuation sequence match
2747     char *bp;
2748     char str[MSG_SIZ];
2749     int i, oldi;
2750     int buf_len;
2751     int next_out;
2752     int tkind;
2753     int backup;    /* [DM] For zippy color lines */
2754     char *p;
2755     char talker[MSG_SIZ]; // [HGM] chat
2756     int channel;
2757
2758     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2759
2760     if (appData.debugMode) {
2761       if (!error) {
2762         fprintf(debugFP, "<ICS: ");
2763         show_bytes(debugFP, data, count);
2764         fprintf(debugFP, "\n");
2765       }
2766     }
2767
2768     if (appData.debugMode) { int f = forwardMostMove;
2769         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2770                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2771                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2772     }
2773     if (count > 0) {
2774         /* If last read ended with a partial line that we couldn't parse,
2775            prepend it to the new read and try again. */
2776         if (leftover_len > 0) {
2777             for (i=0; i<leftover_len; i++)
2778               buf[i] = buf[leftover_start + i];
2779         }
2780
2781     /* copy new characters into the buffer */
2782     bp = buf + leftover_len;
2783     buf_len=leftover_len;
2784     for (i=0; i<count; i++)
2785     {
2786         // ignore these
2787         if (data[i] == '\r')
2788             continue;
2789
2790         // join lines split by ICS?
2791         if (!appData.noJoin)
2792         {
2793             /*
2794                 Joining just consists of finding matches against the
2795                 continuation sequence, and discarding that sequence
2796                 if found instead of copying it.  So, until a match
2797                 fails, there's nothing to do since it might be the
2798                 complete sequence, and thus, something we don't want
2799                 copied.
2800             */
2801             if (data[i] == cont_seq[cmatch])
2802             {
2803                 cmatch++;
2804                 if (cmatch == strlen(cont_seq))
2805                 {
2806                     cmatch = 0; // complete match.  just reset the counter
2807
2808                     /*
2809                         it's possible for the ICS to not include the space
2810                         at the end of the last word, making our [correct]
2811                         join operation fuse two separate words.  the server
2812                         does this when the space occurs at the width setting.
2813                     */
2814                     if (!buf_len || buf[buf_len-1] != ' ')
2815                     {
2816                         *bp++ = ' ';
2817                         buf_len++;
2818                     }
2819                 }
2820                 continue;
2821             }
2822             else if (cmatch)
2823             {
2824                 /*
2825                     match failed, so we have to copy what matched before
2826                     falling through and copying this character.  In reality,
2827                     this will only ever be just the newline character, but
2828                     it doesn't hurt to be precise.
2829                 */
2830                 strncpy(bp, cont_seq, cmatch);
2831                 bp += cmatch;
2832                 buf_len += cmatch;
2833                 cmatch = 0;
2834             }
2835         }
2836
2837         // copy this char
2838         *bp++ = data[i];
2839         buf_len++;
2840     }
2841
2842         buf[buf_len] = NULLCHAR;
2843 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2844         next_out = 0;
2845         leftover_start = 0;
2846
2847         i = 0;
2848         while (i < buf_len) {
2849             /* Deal with part of the TELNET option negotiation
2850                protocol.  We refuse to do anything beyond the
2851                defaults, except that we allow the WILL ECHO option,
2852                which ICS uses to turn off password echoing when we are
2853                directly connected to it.  We reject this option
2854                if localLineEditing mode is on (always on in xboard)
2855                and we are talking to port 23, which might be a real
2856                telnet server that will try to keep WILL ECHO on permanently.
2857              */
2858             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2859                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2860                 unsigned char option;
2861                 oldi = i;
2862                 switch ((unsigned char) buf[++i]) {
2863                   case TN_WILL:
2864                     if (appData.debugMode)
2865                       fprintf(debugFP, "\n<WILL ");
2866                     switch (option = (unsigned char) buf[++i]) {
2867                       case TN_ECHO:
2868                         if (appData.debugMode)
2869                           fprintf(debugFP, "ECHO ");
2870                         /* Reply only if this is a change, according
2871                            to the protocol rules. */
2872                         if (remoteEchoOption) break;
2873                         if (appData.localLineEditing &&
2874                             atoi(appData.icsPort) == TN_PORT) {
2875                             TelnetRequest(TN_DONT, TN_ECHO);
2876                         } else {
2877                             EchoOff();
2878                             TelnetRequest(TN_DO, TN_ECHO);
2879                             remoteEchoOption = TRUE;
2880                         }
2881                         break;
2882                       default:
2883                         if (appData.debugMode)
2884                           fprintf(debugFP, "%d ", option);
2885                         /* Whatever this is, we don't want it. */
2886                         TelnetRequest(TN_DONT, option);
2887                         break;
2888                     }
2889                     break;
2890                   case TN_WONT:
2891                     if (appData.debugMode)
2892                       fprintf(debugFP, "\n<WONT ");
2893                     switch (option = (unsigned char) buf[++i]) {
2894                       case TN_ECHO:
2895                         if (appData.debugMode)
2896                           fprintf(debugFP, "ECHO ");
2897                         /* Reply only if this is a change, according
2898                            to the protocol rules. */
2899                         if (!remoteEchoOption) break;
2900                         EchoOn();
2901                         TelnetRequest(TN_DONT, TN_ECHO);
2902                         remoteEchoOption = FALSE;
2903                         break;
2904                       default:
2905                         if (appData.debugMode)
2906                           fprintf(debugFP, "%d ", (unsigned char) option);
2907                         /* Whatever this is, it must already be turned
2908                            off, because we never agree to turn on
2909                            anything non-default, so according to the
2910                            protocol rules, we don't reply. */
2911                         break;
2912                     }
2913                     break;
2914                   case TN_DO:
2915                     if (appData.debugMode)
2916                       fprintf(debugFP, "\n<DO ");
2917                     switch (option = (unsigned char) buf[++i]) {
2918                       default:
2919                         /* Whatever this is, we refuse to do it. */
2920                         if (appData.debugMode)
2921                           fprintf(debugFP, "%d ", option);
2922                         TelnetRequest(TN_WONT, option);
2923                         break;
2924                     }
2925                     break;
2926                   case TN_DONT:
2927                     if (appData.debugMode)
2928                       fprintf(debugFP, "\n<DONT ");
2929                     switch (option = (unsigned char) buf[++i]) {
2930                       default:
2931                         if (appData.debugMode)
2932                           fprintf(debugFP, "%d ", option);
2933                         /* Whatever this is, we are already not doing
2934                            it, because we never agree to do anything
2935                            non-default, so according to the protocol
2936                            rules, we don't reply. */
2937                         break;
2938                     }
2939                     break;
2940                   case TN_IAC:
2941                     if (appData.debugMode)
2942                       fprintf(debugFP, "\n<IAC ");
2943                     /* Doubled IAC; pass it through */
2944                     i--;
2945                     break;
2946                   default:
2947                     if (appData.debugMode)
2948                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2949                     /* Drop all other telnet commands on the floor */
2950                     break;
2951                 }
2952                 if (oldi > next_out)
2953                   SendToPlayer(&buf[next_out], oldi - next_out);
2954                 if (++i > next_out)
2955                   next_out = i;
2956                 continue;
2957             }
2958
2959             /* OK, this at least will *usually* work */
2960             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2961                 loggedOn = TRUE;
2962             }
2963
2964             if (loggedOn && !intfSet) {
2965                 if (ics_type == ICS_ICC) {
2966                   snprintf(str, MSG_SIZ,
2967                           "/set-quietly interface %s\n/set-quietly style 12\n",
2968                           programVersion);
2969                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2970                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2971                 } else if (ics_type == ICS_CHESSNET) {
2972                   snprintf(str, MSG_SIZ, "/style 12\n");
2973                 } else {
2974                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2975                   strcat(str, programVersion);
2976                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2977                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2978                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2979 #ifdef WIN32
2980                   strcat(str, "$iset nohighlight 1\n");
2981 #endif
2982                   strcat(str, "$iset lock 1\n$style 12\n");
2983                 }
2984                 SendToICS(str);
2985                 NotifyFrontendLogin();
2986                 intfSet = TRUE;
2987             }
2988
2989             if (started == STARTED_COMMENT) {
2990                 /* Accumulate characters in comment */
2991                 parse[parse_pos++] = buf[i];
2992                 if (buf[i] == '\n') {
2993                     parse[parse_pos] = NULLCHAR;
2994                     if(chattingPartner>=0) {
2995                         char mess[MSG_SIZ];
2996                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2997                         OutputChatMessage(chattingPartner, mess);
2998                         chattingPartner = -1;
2999                         next_out = i+1; // [HGM] suppress printing in ICS window
3000                     } else
3001                     if(!suppressKibitz) // [HGM] kibitz
3002                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3003                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3004                         int nrDigit = 0, nrAlph = 0, j;
3005                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3006                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3007                         parse[parse_pos] = NULLCHAR;
3008                         // try to be smart: if it does not look like search info, it should go to
3009                         // ICS interaction window after all, not to engine-output window.
3010                         for(j=0; j<parse_pos; j++) { // count letters and digits
3011                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3012                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3013                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3014                         }
3015                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3016                             int depth=0; float score;
3017                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3018                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3019                                 pvInfoList[forwardMostMove-1].depth = depth;
3020                                 pvInfoList[forwardMostMove-1].score = 100*score;
3021                             }
3022                             OutputKibitz(suppressKibitz, parse);
3023                         } else {
3024                             char tmp[MSG_SIZ];
3025                             if(gameMode == IcsObserving) // restore original ICS messages
3026                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3027                             else
3028                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3029                             SendToPlayer(tmp, strlen(tmp));
3030                         }
3031                         next_out = i+1; // [HGM] suppress printing in ICS window
3032                     }
3033                     started = STARTED_NONE;
3034                 } else {
3035                     /* Don't match patterns against characters in comment */
3036                     i++;
3037                     continue;
3038                 }
3039             }
3040             if (started == STARTED_CHATTER) {
3041                 if (buf[i] != '\n') {
3042                     /* Don't match patterns against characters in chatter */
3043                     i++;
3044                     continue;
3045                 }
3046                 started = STARTED_NONE;
3047                 if(suppressKibitz) next_out = i+1;
3048             }
3049
3050             /* Kludge to deal with rcmd protocol */
3051             if (firstTime && looking_at(buf, &i, "\001*")) {
3052                 DisplayFatalError(&buf[1], 0, 1);
3053                 continue;
3054             } else {
3055                 firstTime = FALSE;
3056             }
3057
3058             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3059                 ics_type = ICS_ICC;
3060                 ics_prefix = "/";
3061                 if (appData.debugMode)
3062                   fprintf(debugFP, "ics_type %d\n", ics_type);
3063                 continue;
3064             }
3065             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3066                 ics_type = ICS_FICS;
3067                 ics_prefix = "$";
3068                 if (appData.debugMode)
3069                   fprintf(debugFP, "ics_type %d\n", ics_type);
3070                 continue;
3071             }
3072             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3073                 ics_type = ICS_CHESSNET;
3074                 ics_prefix = "/";
3075                 if (appData.debugMode)
3076                   fprintf(debugFP, "ics_type %d\n", ics_type);
3077                 continue;
3078             }
3079
3080             if (!loggedOn &&
3081                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3082                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3083                  looking_at(buf, &i, "will be \"*\""))) {
3084               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3085               continue;
3086             }
3087
3088             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3089               char buf[MSG_SIZ];
3090               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3091               DisplayIcsInteractionTitle(buf);
3092               have_set_title = TRUE;
3093             }
3094
3095             /* skip finger notes */
3096             if (started == STARTED_NONE &&
3097                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3098                  (buf[i] == '1' && buf[i+1] == '0')) &&
3099                 buf[i+2] == ':' && buf[i+3] == ' ') {
3100               started = STARTED_CHATTER;
3101               i += 3;
3102               continue;
3103             }
3104
3105             oldi = i;
3106             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3107             if(appData.seekGraph) {
3108                 if(soughtPending && MatchSoughtLine(buf+i)) {
3109                     i = strstr(buf+i, "rated") - buf;
3110                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3111                     next_out = leftover_start = i;
3112                     started = STARTED_CHATTER;
3113                     suppressKibitz = TRUE;
3114                     continue;
3115                 }
3116                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3117                         && looking_at(buf, &i, "* ads displayed")) {
3118                     soughtPending = FALSE;
3119                     seekGraphUp = TRUE;
3120                     DrawSeekGraph();
3121                     continue;
3122                 }
3123                 if(appData.autoRefresh) {
3124                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3125                         int s = (ics_type == ICS_ICC); // ICC format differs
3126                         if(seekGraphUp)
3127                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3128                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3129                         looking_at(buf, &i, "*% "); // eat prompt
3130                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3131                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3132                         next_out = i; // suppress
3133                         continue;
3134                     }
3135                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3136                         char *p = star_match[0];
3137                         while(*p) {
3138                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3139                             while(*p && *p++ != ' '); // next
3140                         }
3141                         looking_at(buf, &i, "*% "); // eat prompt
3142                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3143                         next_out = i;
3144                         continue;
3145                     }
3146                 }
3147             }
3148
3149             /* skip formula vars */
3150             if (started == STARTED_NONE &&
3151                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3152               started = STARTED_CHATTER;
3153               i += 3;
3154               continue;
3155             }
3156
3157             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3158             if (appData.autoKibitz && started == STARTED_NONE &&
3159                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3160                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3161                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3162                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3163                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3164                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3165                         suppressKibitz = TRUE;
3166                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3167                         next_out = i;
3168                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3169                                 && (gameMode == IcsPlayingWhite)) ||
3170                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3171                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3172                             started = STARTED_CHATTER; // own kibitz we simply discard
3173                         else {
3174                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3175                             parse_pos = 0; parse[0] = NULLCHAR;
3176                             savingComment = TRUE;
3177                             suppressKibitz = gameMode != IcsObserving ? 2 :
3178                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3179                         }
3180                         continue;
3181                 } else
3182                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3183                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3184                          && atoi(star_match[0])) {
3185                     // suppress the acknowledgements of our own autoKibitz
3186                     char *p;
3187                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3188                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3189                     SendToPlayer(star_match[0], strlen(star_match[0]));
3190                     if(looking_at(buf, &i, "*% ")) // eat prompt
3191                         suppressKibitz = FALSE;
3192                     next_out = i;
3193                     continue;
3194                 }
3195             } // [HGM] kibitz: end of patch
3196
3197             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3198
3199             // [HGM] chat: intercept tells by users for which we have an open chat window
3200             channel = -1;
3201             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3202                                            looking_at(buf, &i, "* whispers:") ||
3203                                            looking_at(buf, &i, "* kibitzes:") ||
3204                                            looking_at(buf, &i, "* shouts:") ||
3205                                            looking_at(buf, &i, "* c-shouts:") ||
3206                                            looking_at(buf, &i, "--> * ") ||
3207                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3208                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3209                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3210                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3211                 int p;
3212                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3213                 chattingPartner = -1;
3214
3215                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3216                 for(p=0; p<MAX_CHAT; p++) {
3217                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3218                     talker[0] = '['; strcat(talker, "] ");
3219                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3220                     chattingPartner = p; break;
3221                     }
3222                 } else
3223                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3224                 for(p=0; p<MAX_CHAT; p++) {
3225                     if(!strcmp("kibitzes", chatPartner[p])) {
3226                         talker[0] = '['; strcat(talker, "] ");
3227                         chattingPartner = p; break;
3228                     }
3229                 } else
3230                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3231                 for(p=0; p<MAX_CHAT; p++) {
3232                     if(!strcmp("whispers", chatPartner[p])) {
3233                         talker[0] = '['; strcat(talker, "] ");
3234                         chattingPartner = p; break;
3235                     }
3236                 } else
3237                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3238                   if(buf[i-8] == '-' && buf[i-3] == 't')
3239                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3240                     if(!strcmp("c-shouts", chatPartner[p])) {
3241                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3242                         chattingPartner = p; break;
3243                     }
3244                   }
3245                   if(chattingPartner < 0)
3246                   for(p=0; p<MAX_CHAT; p++) {
3247                     if(!strcmp("shouts", chatPartner[p])) {
3248                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3249                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3250                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3251                         chattingPartner = p; break;
3252                     }
3253                   }
3254                 }
3255                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3256                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3257                     talker[0] = 0; Colorize(ColorTell, FALSE);
3258                     chattingPartner = p; break;
3259                 }
3260                 if(chattingPartner<0) i = oldi; else {
3261                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3262                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3263                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3264                     started = STARTED_COMMENT;
3265                     parse_pos = 0; parse[0] = NULLCHAR;
3266                     savingComment = 3 + chattingPartner; // counts as TRUE
3267                     suppressKibitz = TRUE;
3268                     continue;
3269                 }
3270             } // [HGM] chat: end of patch
3271
3272           backup = i;
3273             if (appData.zippyTalk || appData.zippyPlay) {
3274                 /* [DM] Backup address for color zippy lines */
3275 #if ZIPPY
3276                if (loggedOn == TRUE)
3277                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3278                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3279 #endif
3280             } // [DM] 'else { ' deleted
3281                 if (
3282                     /* Regular tells and says */
3283                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3284                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3285                     looking_at(buf, &i, "* says: ") ||
3286                     /* Don't color "message" or "messages" output */
3287                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3288                     looking_at(buf, &i, "*. * at *:*: ") ||
3289                     looking_at(buf, &i, "--* (*:*): ") ||
3290                     /* Message notifications (same color as tells) */
3291                     looking_at(buf, &i, "* has left a message ") ||
3292                     looking_at(buf, &i, "* just sent you a message:\n") ||
3293                     /* Whispers and kibitzes */
3294                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3295                     looking_at(buf, &i, "* kibitzes: ") ||
3296                     /* Channel tells */
3297                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3298
3299                   if (tkind == 1 && strchr(star_match[0], ':')) {
3300                       /* Avoid "tells you:" spoofs in channels */
3301                      tkind = 3;
3302                   }
3303                   if (star_match[0][0] == NULLCHAR ||
3304                       strchr(star_match[0], ' ') ||
3305                       (tkind == 3 && strchr(star_match[1], ' '))) {
3306                     /* Reject bogus matches */
3307                     i = oldi;
3308                   } else {
3309                     if (appData.colorize) {
3310                       if (oldi > next_out) {
3311                         SendToPlayer(&buf[next_out], oldi - next_out);
3312                         next_out = oldi;
3313                       }
3314                       switch (tkind) {
3315                       case 1:
3316                         Colorize(ColorTell, FALSE);
3317                         curColor = ColorTell;
3318                         break;
3319                       case 2:
3320                         Colorize(ColorKibitz, FALSE);
3321                         curColor = ColorKibitz;
3322                         break;
3323                       case 3:
3324                         p = strrchr(star_match[1], '(');
3325                         if (p == NULL) {
3326                           p = star_match[1];
3327                         } else {
3328                           p++;
3329                         }
3330                         if (atoi(p) == 1) {
3331                           Colorize(ColorChannel1, FALSE);
3332                           curColor = ColorChannel1;
3333                         } else {
3334                           Colorize(ColorChannel, FALSE);
3335                           curColor = ColorChannel;
3336                         }
3337                         break;
3338                       case 5:
3339                         curColor = ColorNormal;
3340                         break;
3341                       }
3342                     }
3343                     if (started == STARTED_NONE && appData.autoComment &&
3344                         (gameMode == IcsObserving ||
3345                          gameMode == IcsPlayingWhite ||
3346                          gameMode == IcsPlayingBlack)) {
3347                       parse_pos = i - oldi;
3348                       memcpy(parse, &buf[oldi], parse_pos);
3349                       parse[parse_pos] = NULLCHAR;
3350                       started = STARTED_COMMENT;
3351                       savingComment = TRUE;
3352                     } else {
3353                       started = STARTED_CHATTER;
3354                       savingComment = FALSE;
3355                     }
3356                     loggedOn = TRUE;
3357                     continue;
3358                   }
3359                 }
3360
3361                 if (looking_at(buf, &i, "* s-shouts: ") ||
3362                     looking_at(buf, &i, "* c-shouts: ")) {
3363                     if (appData.colorize) {
3364                         if (oldi > next_out) {
3365                             SendToPlayer(&buf[next_out], oldi - next_out);
3366                             next_out = oldi;
3367                         }
3368                         Colorize(ColorSShout, FALSE);
3369                         curColor = ColorSShout;
3370                     }
3371                     loggedOn = TRUE;
3372                     started = STARTED_CHATTER;
3373                     continue;
3374                 }
3375
3376                 if (looking_at(buf, &i, "--->")) {
3377                     loggedOn = TRUE;
3378                     continue;
3379                 }
3380
3381                 if (looking_at(buf, &i, "* shouts: ") ||
3382                     looking_at(buf, &i, "--> ")) {
3383                     if (appData.colorize) {
3384                         if (oldi > next_out) {
3385                             SendToPlayer(&buf[next_out], oldi - next_out);
3386                             next_out = oldi;
3387                         }
3388                         Colorize(ColorShout, FALSE);
3389                         curColor = ColorShout;
3390                     }
3391                     loggedOn = TRUE;
3392                     started = STARTED_CHATTER;
3393                     continue;
3394                 }
3395
3396                 if (looking_at( buf, &i, "Challenge:")) {
3397                     if (appData.colorize) {
3398                         if (oldi > next_out) {
3399                             SendToPlayer(&buf[next_out], oldi - next_out);
3400                             next_out = oldi;
3401                         }
3402                         Colorize(ColorChallenge, FALSE);
3403                         curColor = ColorChallenge;
3404                     }
3405                     loggedOn = TRUE;
3406                     continue;
3407                 }
3408
3409                 if (looking_at(buf, &i, "* offers you") ||
3410                     looking_at(buf, &i, "* offers to be") ||
3411                     looking_at(buf, &i, "* would like to") ||
3412                     looking_at(buf, &i, "* requests to") ||
3413                     looking_at(buf, &i, "Your opponent offers") ||
3414                     looking_at(buf, &i, "Your opponent requests")) {
3415
3416                     if (appData.colorize) {
3417                         if (oldi > next_out) {
3418                             SendToPlayer(&buf[next_out], oldi - next_out);
3419                             next_out = oldi;
3420                         }
3421                         Colorize(ColorRequest, FALSE);
3422                         curColor = ColorRequest;
3423                     }
3424                     continue;
3425                 }
3426
3427                 if (looking_at(buf, &i, "* (*) seeking")) {
3428                     if (appData.colorize) {
3429                         if (oldi > next_out) {
3430                             SendToPlayer(&buf[next_out], oldi - next_out);
3431                             next_out = oldi;
3432                         }
3433                         Colorize(ColorSeek, FALSE);
3434                         curColor = ColorSeek;
3435                     }
3436                     continue;
3437             }
3438
3439           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3440
3441             if (looking_at(buf, &i, "\\   ")) {
3442                 if (prevColor != ColorNormal) {
3443                     if (oldi > next_out) {
3444                         SendToPlayer(&buf[next_out], oldi - next_out);
3445                         next_out = oldi;
3446                     }
3447                     Colorize(prevColor, TRUE);
3448                     curColor = prevColor;
3449                 }
3450                 if (savingComment) {
3451                     parse_pos = i - oldi;
3452                     memcpy(parse, &buf[oldi], parse_pos);
3453                     parse[parse_pos] = NULLCHAR;
3454                     started = STARTED_COMMENT;
3455                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3456                         chattingPartner = savingComment - 3; // kludge to remember the box
3457                 } else {
3458                     started = STARTED_CHATTER;
3459                 }
3460                 continue;
3461             }
3462
3463             if (looking_at(buf, &i, "Black Strength :") ||
3464                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3465                 looking_at(buf, &i, "<10>") ||
3466                 looking_at(buf, &i, "#@#")) {
3467                 /* Wrong board style */
3468                 loggedOn = TRUE;
3469                 SendToICS(ics_prefix);
3470                 SendToICS("set style 12\n");
3471                 SendToICS(ics_prefix);
3472                 SendToICS("refresh\n");
3473                 continue;
3474             }
3475
3476             if (looking_at(buf, &i, "login:")) {
3477               if (!have_sent_ICS_logon) {
3478                 if(ICSInitScript())
3479                   have_sent_ICS_logon = 1;
3480                 else // no init script was found
3481                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3482               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3483                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3484               }
3485                 continue;
3486             }
3487
3488             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3489                 (looking_at(buf, &i, "\n<12> ") ||
3490                  looking_at(buf, &i, "<12> "))) {
3491                 loggedOn = TRUE;
3492                 if (oldi > next_out) {
3493                     SendToPlayer(&buf[next_out], oldi - next_out);
3494                 }
3495                 next_out = i;
3496                 started = STARTED_BOARD;
3497                 parse_pos = 0;
3498                 continue;
3499             }
3500
3501             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3502                 looking_at(buf, &i, "<b1> ")) {
3503                 if (oldi > next_out) {
3504                     SendToPlayer(&buf[next_out], oldi - next_out);
3505                 }
3506                 next_out = i;
3507                 started = STARTED_HOLDINGS;
3508                 parse_pos = 0;
3509                 continue;
3510             }
3511
3512             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3513                 loggedOn = TRUE;
3514                 /* Header for a move list -- first line */
3515
3516                 switch (ics_getting_history) {
3517                   case H_FALSE:
3518                     switch (gameMode) {
3519                       case IcsIdle:
3520                       case BeginningOfGame:
3521                         /* User typed "moves" or "oldmoves" while we
3522                            were idle.  Pretend we asked for these
3523                            moves and soak them up so user can step
3524                            through them and/or save them.
3525                            */
3526                         Reset(FALSE, TRUE);
3527                         gameMode = IcsObserving;
3528                         ModeHighlight();
3529                         ics_gamenum = -1;
3530                         ics_getting_history = H_GOT_UNREQ_HEADER;
3531                         break;
3532                       case EditGame: /*?*/
3533                       case EditPosition: /*?*/
3534                         /* Should above feature work in these modes too? */
3535                         /* For now it doesn't */
3536                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3537                         break;
3538                       default:
3539                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3540                         break;
3541                     }
3542                     break;
3543                   case H_REQUESTED:
3544                     /* Is this the right one? */
3545                     if (gameInfo.white && gameInfo.black &&
3546                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3547                         strcmp(gameInfo.black, star_match[2]) == 0) {
3548                         /* All is well */
3549                         ics_getting_history = H_GOT_REQ_HEADER;
3550                     }
3551                     break;
3552                   case H_GOT_REQ_HEADER:
3553                   case H_GOT_UNREQ_HEADER:
3554                   case H_GOT_UNWANTED_HEADER:
3555                   case H_GETTING_MOVES:
3556                     /* Should not happen */
3557                     DisplayError(_("Error gathering move list: two headers"), 0);
3558                     ics_getting_history = H_FALSE;
3559                     break;
3560                 }
3561
3562                 /* Save player ratings into gameInfo if needed */
3563                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3564                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3565                     (gameInfo.whiteRating == -1 ||
3566                      gameInfo.blackRating == -1)) {
3567
3568                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3569                     gameInfo.blackRating = string_to_rating(star_match[3]);
3570                     if (appData.debugMode)
3571                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3572                               gameInfo.whiteRating, gameInfo.blackRating);
3573                 }
3574                 continue;
3575             }
3576
3577             if (looking_at(buf, &i,
3578               "* * match, initial time: * minute*, increment: * second")) {
3579                 /* Header for a move list -- second line */
3580                 /* Initial board will follow if this is a wild game */
3581                 if (gameInfo.event != NULL) free(gameInfo.event);
3582                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3583                 gameInfo.event = StrSave(str);
3584                 /* [HGM] we switched variant. Translate boards if needed. */
3585                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3586                 continue;
3587             }
3588
3589             if (looking_at(buf, &i, "Move  ")) {
3590                 /* Beginning of a move list */
3591                 switch (ics_getting_history) {
3592                   case H_FALSE:
3593                     /* Normally should not happen */
3594                     /* Maybe user hit reset while we were parsing */
3595                     break;
3596                   case H_REQUESTED:
3597                     /* Happens if we are ignoring a move list that is not
3598                      * the one we just requested.  Common if the user
3599                      * tries to observe two games without turning off
3600                      * getMoveList */
3601                     break;
3602                   case H_GETTING_MOVES:
3603                     /* Should not happen */
3604                     DisplayError(_("Error gathering move list: nested"), 0);
3605                     ics_getting_history = H_FALSE;
3606                     break;
3607                   case H_GOT_REQ_HEADER:
3608                     ics_getting_history = H_GETTING_MOVES;
3609                     started = STARTED_MOVES;
3610                     parse_pos = 0;
3611                     if (oldi > next_out) {
3612                         SendToPlayer(&buf[next_out], oldi - next_out);
3613                     }
3614                     break;
3615                   case H_GOT_UNREQ_HEADER:
3616                     ics_getting_history = H_GETTING_MOVES;
3617                     started = STARTED_MOVES_NOHIDE;
3618                     parse_pos = 0;
3619                     break;
3620                   case H_GOT_UNWANTED_HEADER:
3621                     ics_getting_history = H_FALSE;
3622                     break;
3623                 }
3624                 continue;
3625             }
3626
3627             if (looking_at(buf, &i, "% ") ||
3628                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3629                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3630                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3631                     soughtPending = FALSE;
3632                     seekGraphUp = TRUE;
3633                     DrawSeekGraph();
3634                 }
3635                 if(suppressKibitz) next_out = i;
3636                 savingComment = FALSE;
3637                 suppressKibitz = 0;
3638                 switch (started) {
3639                   case STARTED_MOVES:
3640                   case STARTED_MOVES_NOHIDE:
3641                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3642                     parse[parse_pos + i - oldi] = NULLCHAR;
3643                     ParseGameHistory(parse);
3644 #if ZIPPY
3645                     if (appData.zippyPlay && first.initDone) {
3646                         FeedMovesToProgram(&first, forwardMostMove);
3647                         if (gameMode == IcsPlayingWhite) {
3648                             if (WhiteOnMove(forwardMostMove)) {
3649                                 if (first.sendTime) {
3650                                   if (first.useColors) {
3651                                     SendToProgram("black\n", &first);
3652                                   }
3653                                   SendTimeRemaining(&first, TRUE);
3654                                 }
3655                                 if (first.useColors) {
3656                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3657                                 }
3658                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3659                                 first.maybeThinking = TRUE;
3660                             } else {
3661                                 if (first.usePlayother) {
3662                                   if (first.sendTime) {
3663                                     SendTimeRemaining(&first, TRUE);
3664                                   }
3665                                   SendToProgram("playother\n", &first);
3666                                   firstMove = FALSE;
3667                                 } else {
3668                                   firstMove = TRUE;
3669                                 }
3670                             }
3671                         } else if (gameMode == IcsPlayingBlack) {
3672                             if (!WhiteOnMove(forwardMostMove)) {
3673                                 if (first.sendTime) {
3674                                   if (first.useColors) {
3675                                     SendToProgram("white\n", &first);
3676                                   }
3677                                   SendTimeRemaining(&first, FALSE);
3678                                 }
3679                                 if (first.useColors) {
3680                                   SendToProgram("black\n", &first);
3681                                 }
3682                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3683                                 first.maybeThinking = TRUE;
3684                             } else {
3685                                 if (first.usePlayother) {
3686                                   if (first.sendTime) {
3687                                     SendTimeRemaining(&first, FALSE);
3688                                   }
3689                                   SendToProgram("playother\n", &first);
3690                                   firstMove = FALSE;
3691                                 } else {
3692                                   firstMove = TRUE;
3693                                 }
3694                             }
3695                         }
3696                     }
3697 #endif
3698                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3699                         /* Moves came from oldmoves or moves command
3700                            while we weren't doing anything else.
3701                            */
3702                         currentMove = forwardMostMove;
3703                         ClearHighlights();/*!!could figure this out*/
3704                         flipView = appData.flipView;
3705                         DrawPosition(TRUE, boards[currentMove]);
3706                         DisplayBothClocks();
3707                         snprintf(str, MSG_SIZ, "%s %s %s",
3708                                 gameInfo.white, _("vs."),  gameInfo.black);
3709                         DisplayTitle(str);
3710                         gameMode = IcsIdle;
3711                     } else {
3712                         /* Moves were history of an active game */
3713                         if (gameInfo.resultDetails != NULL) {
3714                             free(gameInfo.resultDetails);
3715                             gameInfo.resultDetails = NULL;
3716                         }
3717                     }
3718                     HistorySet(parseList, backwardMostMove,
3719                                forwardMostMove, currentMove-1);
3720                     DisplayMove(currentMove - 1);
3721                     if (started == STARTED_MOVES) next_out = i;
3722                     started = STARTED_NONE;
3723                     ics_getting_history = H_FALSE;
3724                     break;
3725
3726                   case STARTED_OBSERVE:
3727                     started = STARTED_NONE;
3728                     SendToICS(ics_prefix);
3729                     SendToICS("refresh\n");
3730                     break;
3731
3732                   default:
3733                     break;
3734                 }
3735                 if(bookHit) { // [HGM] book: simulate book reply
3736                     static char bookMove[MSG_SIZ]; // a bit generous?
3737
3738                     programStats.nodes = programStats.depth = programStats.time =
3739                     programStats.score = programStats.got_only_move = 0;
3740                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3741
3742                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3743                     strcat(bookMove, bookHit);
3744                     HandleMachineMove(bookMove, &first);
3745                 }
3746                 continue;
3747             }
3748
3749             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3750                  started == STARTED_HOLDINGS ||
3751                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3752                 /* Accumulate characters in move list or board */
3753                 parse[parse_pos++] = buf[i];
3754             }
3755
3756             /* Start of game messages.  Mostly we detect start of game
3757                when the first board image arrives.  On some versions
3758                of the ICS, though, we need to do a "refresh" after starting
3759                to observe in order to get the current board right away. */
3760             if (looking_at(buf, &i, "Adding game * to observation list")) {
3761                 started = STARTED_OBSERVE;
3762                 continue;
3763             }
3764
3765             /* Handle auto-observe */
3766             if (appData.autoObserve &&
3767                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3768                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3769                 char *player;
3770                 /* Choose the player that was highlighted, if any. */
3771                 if (star_match[0][0] == '\033' ||
3772                     star_match[1][0] != '\033') {
3773                     player = star_match[0];
3774                 } else {
3775                     player = star_match[2];
3776                 }
3777                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3778                         ics_prefix, StripHighlightAndTitle(player));
3779                 SendToICS(str);
3780
3781                 /* Save ratings from notify string */
3782                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3783                 player1Rating = string_to_rating(star_match[1]);
3784                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3785                 player2Rating = string_to_rating(star_match[3]);
3786
3787                 if (appData.debugMode)
3788                   fprintf(debugFP,
3789                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3790                           player1Name, player1Rating,
3791                           player2Name, player2Rating);
3792
3793                 continue;
3794             }
3795
3796             /* Deal with automatic examine mode after a game,
3797                and with IcsObserving -> IcsExamining transition */
3798             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3799                 looking_at(buf, &i, "has made you an examiner of game *")) {
3800
3801                 int gamenum = atoi(star_match[0]);
3802                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3803                     gamenum == ics_gamenum) {
3804                     /* We were already playing or observing this game;
3805                        no need to refetch history */
3806                     gameMode = IcsExamining;
3807                     if (pausing) {
3808                         pauseExamForwardMostMove = forwardMostMove;
3809                     } else if (currentMove < forwardMostMove) {
3810                         ForwardInner(forwardMostMove);
3811                     }
3812                 } else {
3813                     /* I don't think this case really can happen */
3814                     SendToICS(ics_prefix);
3815                     SendToICS("refresh\n");
3816                 }
3817                 continue;
3818             }
3819
3820             /* Error messages */
3821 //          if (ics_user_moved) {
3822             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3823                 if (looking_at(buf, &i, "Illegal move") ||
3824                     looking_at(buf, &i, "Not a legal move") ||
3825                     looking_at(buf, &i, "Your king is in check") ||
3826                     looking_at(buf, &i, "It isn't your turn") ||
3827                     looking_at(buf, &i, "It is not your move")) {
3828                     /* Illegal move */
3829                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3830                         currentMove = forwardMostMove-1;
3831                         DisplayMove(currentMove - 1); /* before DMError */
3832                         DrawPosition(FALSE, boards[currentMove]);
3833                         SwitchClocks(forwardMostMove-1); // [HGM] race
3834                         DisplayBothClocks();
3835                     }
3836                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3837                     ics_user_moved = 0;
3838                     continue;
3839                 }
3840             }
3841
3842             if (looking_at(buf, &i, "still have time") ||
3843                 looking_at(buf, &i, "not out of time") ||
3844                 looking_at(buf, &i, "either player is out of time") ||
3845                 looking_at(buf, &i, "has timeseal; checking")) {
3846                 /* We must have called his flag a little too soon */
3847                 whiteFlag = blackFlag = FALSE;
3848                 continue;
3849             }
3850
3851             if (looking_at(buf, &i, "added * seconds to") ||
3852                 looking_at(buf, &i, "seconds were added to")) {
3853                 /* Update the clocks */
3854                 SendToICS(ics_prefix);
3855                 SendToICS("refresh\n");
3856                 continue;
3857             }
3858
3859             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3860                 ics_clock_paused = TRUE;
3861                 StopClocks();
3862                 continue;
3863             }
3864
3865             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3866                 ics_clock_paused = FALSE;
3867                 StartClocks();
3868                 continue;
3869             }
3870
3871             /* Grab player ratings from the Creating: message.
3872                Note we have to check for the special case when
3873                the ICS inserts things like [white] or [black]. */
3874             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3875                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3876                 /* star_matches:
3877                    0    player 1 name (not necessarily white)
3878                    1    player 1 rating
3879                    2    empty, white, or black (IGNORED)
3880                    3    player 2 name (not necessarily black)
3881                    4    player 2 rating
3882
3883                    The names/ratings are sorted out when the game
3884                    actually starts (below).
3885                 */
3886                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3887                 player1Rating = string_to_rating(star_match[1]);
3888                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3889                 player2Rating = string_to_rating(star_match[4]);
3890
3891                 if (appData.debugMode)
3892                   fprintf(debugFP,
3893                           "Ratings from 'Creating:' %s %d, %s %d\n",
3894                           player1Name, player1Rating,
3895                           player2Name, player2Rating);
3896
3897                 continue;
3898             }
3899
3900             /* Improved generic start/end-of-game messages */
3901             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3902                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3903                 /* If tkind == 0: */
3904                 /* star_match[0] is the game number */
3905                 /*           [1] is the white player's name */
3906                 /*           [2] is the black player's name */
3907                 /* For end-of-game: */
3908                 /*           [3] is the reason for the game end */
3909                 /*           [4] is a PGN end game-token, preceded by " " */
3910                 /* For start-of-game: */
3911                 /*           [3] begins with "Creating" or "Continuing" */
3912                 /*           [4] is " *" or empty (don't care). */
3913                 int gamenum = atoi(star_match[0]);
3914                 char *whitename, *blackname, *why, *endtoken;
3915                 ChessMove endtype = EndOfFile;
3916
3917                 if (tkind == 0) {
3918                   whitename = star_match[1];
3919                   blackname = star_match[2];
3920                   why = star_match[3];
3921                   endtoken = star_match[4];
3922                 } else {
3923                   whitename = star_match[1];
3924                   blackname = star_match[3];
3925                   why = star_match[5];
3926                   endtoken = star_match[6];
3927                 }
3928
3929                 /* Game start messages */
3930                 if (strncmp(why, "Creating ", 9) == 0 ||
3931                     strncmp(why, "Continuing ", 11) == 0) {
3932                     gs_gamenum = gamenum;
3933                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3934                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3935                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3936 #if ZIPPY
3937                     if (appData.zippyPlay) {
3938                         ZippyGameStart(whitename, blackname);
3939                     }
3940 #endif /*ZIPPY*/
3941                     partnerBoardValid = FALSE; // [HGM] bughouse
3942                     continue;
3943                 }
3944
3945                 /* Game end messages */
3946                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3947                     ics_gamenum != gamenum) {
3948                     continue;
3949                 }
3950                 while (endtoken[0] == ' ') endtoken++;
3951                 switch (endtoken[0]) {
3952                   case '*':
3953                   default:
3954                     endtype = GameUnfinished;
3955                     break;
3956                   case '0':
3957                     endtype = BlackWins;
3958                     break;
3959                   case '1':
3960                     if (endtoken[1] == '/')
3961                       endtype = GameIsDrawn;
3962                     else
3963                       endtype = WhiteWins;
3964                     break;
3965                 }
3966                 GameEnds(endtype, why, GE_ICS);
3967 #if ZIPPY
3968                 if (appData.zippyPlay && first.initDone) {
3969                     ZippyGameEnd(endtype, why);
3970                     if (first.pr == NoProc) {
3971                       /* Start the next process early so that we'll
3972                          be ready for the next challenge */
3973                       StartChessProgram(&first);
3974                     }
3975                     /* Send "new" early, in case this command takes
3976                        a long time to finish, so that we'll be ready
3977                        for the next challenge. */
3978                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3979                     Reset(TRUE, TRUE);
3980                 }
3981 #endif /*ZIPPY*/
3982                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3983                 continue;
3984             }
3985
3986             if (looking_at(buf, &i, "Removing game * from observation") ||
3987                 looking_at(buf, &i, "no longer observing game *") ||
3988                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3989                 if (gameMode == IcsObserving &&
3990                     atoi(star_match[0]) == ics_gamenum)
3991                   {
3992                       /* icsEngineAnalyze */
3993                       if (appData.icsEngineAnalyze) {
3994                             ExitAnalyzeMode();
3995                             ModeHighlight();
3996                       }
3997                       StopClocks();
3998                       gameMode = IcsIdle;
3999                       ics_gamenum = -1;
4000                       ics_user_moved = FALSE;
4001                   }
4002                 continue;
4003             }
4004
4005             if (looking_at(buf, &i, "no longer examining game *")) {
4006                 if (gameMode == IcsExamining &&
4007                     atoi(star_match[0]) == ics_gamenum)
4008                   {
4009                       gameMode = IcsIdle;
4010                       ics_gamenum = -1;
4011                       ics_user_moved = FALSE;
4012                   }
4013                 continue;
4014             }
4015
4016             /* Advance leftover_start past any newlines we find,
4017                so only partial lines can get reparsed */
4018             if (looking_at(buf, &i, "\n")) {
4019                 prevColor = curColor;
4020                 if (curColor != ColorNormal) {
4021                     if (oldi > next_out) {
4022                         SendToPlayer(&buf[next_out], oldi - next_out);
4023                         next_out = oldi;
4024                     }
4025                     Colorize(ColorNormal, FALSE);
4026                     curColor = ColorNormal;
4027                 }
4028                 if (started == STARTED_BOARD) {
4029                     started = STARTED_NONE;
4030                     parse[parse_pos] = NULLCHAR;
4031                     ParseBoard12(parse);
4032                     ics_user_moved = 0;
4033
4034                     /* Send premove here */
4035                     if (appData.premove) {
4036                       char str[MSG_SIZ];
4037                       if (currentMove == 0 &&
4038                           gameMode == IcsPlayingWhite &&
4039                           appData.premoveWhite) {
4040                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4041                         if (appData.debugMode)
4042                           fprintf(debugFP, "Sending premove:\n");
4043                         SendToICS(str);
4044                       } else if (currentMove == 1 &&
4045                                  gameMode == IcsPlayingBlack &&
4046                                  appData.premoveBlack) {
4047                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4048                         if (appData.debugMode)
4049                           fprintf(debugFP, "Sending premove:\n");
4050                         SendToICS(str);
4051                       } else if (gotPremove) {
4052                         gotPremove = 0;
4053                         ClearPremoveHighlights();
4054                         if (appData.debugMode)
4055                           fprintf(debugFP, "Sending premove:\n");
4056                           UserMoveEvent(premoveFromX, premoveFromY,
4057                                         premoveToX, premoveToY,
4058                                         premovePromoChar);
4059                       }
4060                     }
4061
4062                     /* Usually suppress following prompt */
4063                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4064                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4065                         if (looking_at(buf, &i, "*% ")) {
4066                             savingComment = FALSE;
4067                             suppressKibitz = 0;
4068                         }
4069                     }
4070                     next_out = i;
4071                 } else if (started == STARTED_HOLDINGS) {
4072                     int gamenum;
4073                     char new_piece[MSG_SIZ];
4074                     started = STARTED_NONE;
4075                     parse[parse_pos] = NULLCHAR;
4076                     if (appData.debugMode)
4077                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4078                                                         parse, currentMove);
4079                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4080                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4081                         if (gameInfo.variant == VariantNormal) {
4082                           /* [HGM] We seem to switch variant during a game!
4083                            * Presumably no holdings were displayed, so we have
4084                            * to move the position two files to the right to
4085                            * create room for them!
4086                            */
4087                           VariantClass newVariant;
4088                           switch(gameInfo.boardWidth) { // base guess on board width
4089                                 case 9:  newVariant = VariantShogi; break;
4090                                 case 10: newVariant = VariantGreat; break;
4091                                 default: newVariant = VariantCrazyhouse; break;
4092                           }
4093                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4094                           /* Get a move list just to see the header, which
4095                              will tell us whether this is really bug or zh */
4096                           if (ics_getting_history == H_FALSE) {
4097                             ics_getting_history = H_REQUESTED;
4098                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4099                             SendToICS(str);
4100                           }
4101                         }
4102                         new_piece[0] = NULLCHAR;
4103                         sscanf(parse, "game %d white [%s black [%s <- %s",
4104                                &gamenum, white_holding, black_holding,
4105                                new_piece);
4106                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4107                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4108                         /* [HGM] copy holdings to board holdings area */
4109                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4110                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4111                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4112 #if ZIPPY
4113                         if (appData.zippyPlay && first.initDone) {
4114                             ZippyHoldings(white_holding, black_holding,
4115                                           new_piece);
4116                         }
4117 #endif /*ZIPPY*/
4118                         if (tinyLayout || smallLayout) {
4119                             char wh[16], bh[16];
4120                             PackHolding(wh, white_holding);
4121                             PackHolding(bh, black_holding);
4122                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4123                                     gameInfo.white, gameInfo.black);
4124                         } else {
4125                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4126                                     gameInfo.white, white_holding, _("vs."),
4127                                     gameInfo.black, black_holding);
4128                         }
4129                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4130                         DrawPosition(FALSE, boards[currentMove]);
4131                         DisplayTitle(str);
4132                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4133                         sscanf(parse, "game %d white [%s black [%s <- %s",
4134                                &gamenum, white_holding, black_holding,
4135                                new_piece);
4136                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4137                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4138                         /* [HGM] copy holdings to partner-board holdings area */
4139                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4140                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4141                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4142                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4143                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4144                       }
4145                     }
4146                     /* Suppress following prompt */
4147                     if (looking_at(buf, &i, "*% ")) {
4148                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4149                         savingComment = FALSE;
4150                         suppressKibitz = 0;
4151                     }
4152                     next_out = i;
4153                 }
4154                 continue;
4155             }
4156
4157             i++;                /* skip unparsed character and loop back */
4158         }
4159
4160         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4161 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4162 //          SendToPlayer(&buf[next_out], i - next_out);
4163             started != STARTED_HOLDINGS && leftover_start > next_out) {
4164             SendToPlayer(&buf[next_out], leftover_start - next_out);
4165             next_out = i;
4166         }
4167
4168         leftover_len = buf_len - leftover_start;
4169         /* if buffer ends with something we couldn't parse,
4170            reparse it after appending the next read */
4171
4172     } else if (count == 0) {
4173         RemoveInputSource(isr);
4174         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4175     } else {
4176         DisplayFatalError(_("Error reading from ICS"), error, 1);
4177     }
4178 }
4179
4180
4181 /* Board style 12 looks like this:
4182
4183    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4184
4185  * The "<12> " is stripped before it gets to this routine.  The two
4186  * trailing 0's (flip state and clock ticking) are later addition, and
4187  * some chess servers may not have them, or may have only the first.
4188  * Additional trailing fields may be added in the future.
4189  */
4190
4191 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4192
4193 #define RELATION_OBSERVING_PLAYED    0
4194 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4195 #define RELATION_PLAYING_MYMOVE      1
4196 #define RELATION_PLAYING_NOTMYMOVE  -1
4197 #define RELATION_EXAMINING           2
4198 #define RELATION_ISOLATED_BOARD     -3
4199 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4200
4201 void
4202 ParseBoard12 (char *string)
4203 {
4204 #if ZIPPY
4205     int i, takeback;
4206     char *bookHit = NULL; // [HGM] book
4207 #endif
4208     GameMode newGameMode;
4209     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4210     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4211     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4212     char to_play, board_chars[200];
4213     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4214     char black[32], white[32];
4215     Board board;
4216     int prevMove = currentMove;
4217     int ticking = 2;
4218     ChessMove moveType;
4219     int fromX, fromY, toX, toY;
4220     char promoChar;
4221     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4222     Boolean weird = FALSE, reqFlag = FALSE;
4223
4224     fromX = fromY = toX = toY = -1;
4225
4226     newGame = FALSE;
4227
4228     if (appData.debugMode)
4229       fprintf(debugFP, "Parsing board: %s\n", string);
4230
4231     move_str[0] = NULLCHAR;
4232     elapsed_time[0] = NULLCHAR;
4233     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4234         int  i = 0, j;
4235         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4236             if(string[i] == ' ') { ranks++; files = 0; }
4237             else files++;
4238             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4239             i++;
4240         }
4241         for(j = 0; j <i; j++) board_chars[j] = string[j];
4242         board_chars[i] = '\0';
4243         string += i + 1;
4244     }
4245     n = sscanf(string, PATTERN, &to_play, &double_push,
4246                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4247                &gamenum, white, black, &relation, &basetime, &increment,
4248                &white_stren, &black_stren, &white_time, &black_time,
4249                &moveNum, str, elapsed_time, move_str, &ics_flip,
4250                &ticking);
4251
4252     if (n < 21) {
4253         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4254         DisplayError(str, 0);
4255         return;
4256     }
4257
4258     /* Convert the move number to internal form */
4259     moveNum = (moveNum - 1) * 2;
4260     if (to_play == 'B') moveNum++;
4261     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4262       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4263                         0, 1);
4264       return;
4265     }
4266
4267     switch (relation) {
4268       case RELATION_OBSERVING_PLAYED:
4269       case RELATION_OBSERVING_STATIC:
4270         if (gamenum == -1) {
4271             /* Old ICC buglet */
4272             relation = RELATION_OBSERVING_STATIC;
4273         }
4274         newGameMode = IcsObserving;
4275         break;
4276       case RELATION_PLAYING_MYMOVE:
4277       case RELATION_PLAYING_NOTMYMOVE:
4278         newGameMode =
4279           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4280             IcsPlayingWhite : IcsPlayingBlack;
4281         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4282         break;
4283       case RELATION_EXAMINING:
4284         newGameMode = IcsExamining;
4285         break;
4286       case RELATION_ISOLATED_BOARD:
4287       default:
4288         /* Just display this board.  If user was doing something else,
4289            we will forget about it until the next board comes. */
4290         newGameMode = IcsIdle;
4291         break;
4292       case RELATION_STARTING_POSITION:
4293         newGameMode = gameMode;
4294         break;
4295     }
4296
4297     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4298         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4299          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4300       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4301       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4302       static int lastBgGame = -1;
4303       char *toSqr;
4304       for (k = 0; k < ranks; k++) {
4305         for (j = 0; j < files; j++)
4306           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4307         if(gameInfo.holdingsWidth > 1) {
4308              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4309              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4310         }
4311       }
4312       CopyBoard(partnerBoard, board);
4313       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4314         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4315         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4316       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4317       if(toSqr = strchr(str, '-')) {
4318         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4319         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4320       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4321       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4322       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4323       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4324       if(twoBoards) {
4325           DisplayWhiteClock(white_time*fac, to_play == 'W');
4326           DisplayBlackClock(black_time*fac, to_play != 'W');
4327           activePartner = to_play;
4328           if(gamenum != lastBgGame) {
4329               char buf[MSG_SIZ];
4330               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4331               DisplayTitle(buf);
4332           }
4333           lastBgGame = gamenum;
4334           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4335                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4336       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4337                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4338       if(!twoBoards) DisplayMessage(partnerStatus, "");
4339         partnerBoardValid = TRUE;
4340       return;
4341     }
4342
4343     if(appData.dualBoard && appData.bgObserve) {
4344         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4345             SendToICS(ics_prefix), SendToICS("pobserve\n");
4346         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4347             char buf[MSG_SIZ];
4348             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4349             SendToICS(buf);
4350         }
4351     }
4352
4353     /* Modify behavior for initial board display on move listing
4354        of wild games.
4355        */
4356     switch (ics_getting_history) {
4357       case H_FALSE:
4358       case H_REQUESTED:
4359         break;
4360       case H_GOT_REQ_HEADER:
4361       case H_GOT_UNREQ_HEADER:
4362         /* This is the initial position of the current game */
4363         gamenum = ics_gamenum;
4364         moveNum = 0;            /* old ICS bug workaround */
4365         if (to_play == 'B') {
4366           startedFromSetupPosition = TRUE;
4367           blackPlaysFirst = TRUE;
4368           moveNum = 1;
4369           if (forwardMostMove == 0) forwardMostMove = 1;
4370           if (backwardMostMove == 0) backwardMostMove = 1;
4371           if (currentMove == 0) currentMove = 1;
4372         }
4373         newGameMode = gameMode;
4374         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4375         break;
4376       case H_GOT_UNWANTED_HEADER:
4377         /* This is an initial board that we don't want */
4378         return;
4379       case H_GETTING_MOVES:
4380         /* Should not happen */
4381         DisplayError(_("Error gathering move list: extra board"), 0);
4382         ics_getting_history = H_FALSE;
4383         return;
4384     }
4385
4386    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4387                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4388                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4389      /* [HGM] We seem to have switched variant unexpectedly
4390       * Try to guess new variant from board size
4391       */
4392           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4393           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4394           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4395           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4396           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4397           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4398           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4399           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4400           /* Get a move list just to see the header, which
4401              will tell us whether this is really bug or zh */
4402           if (ics_getting_history == H_FALSE) {
4403             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4404             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4405             SendToICS(str);
4406           }
4407     }
4408
4409     /* Take action if this is the first board of a new game, or of a
4410        different game than is currently being displayed.  */
4411     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4412         relation == RELATION_ISOLATED_BOARD) {
4413
4414         /* Forget the old game and get the history (if any) of the new one */
4415         if (gameMode != BeginningOfGame) {
4416           Reset(TRUE, TRUE);
4417         }
4418         newGame = TRUE;
4419         if (appData.autoRaiseBoard) BoardToTop();
4420         prevMove = -3;
4421         if (gamenum == -1) {
4422             newGameMode = IcsIdle;
4423         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4424                    appData.getMoveList && !reqFlag) {
4425             /* Need to get game history */
4426             ics_getting_history = H_REQUESTED;
4427             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4428             SendToICS(str);
4429         }
4430
4431         /* Initially flip the board to have black on the bottom if playing
4432            black or if the ICS flip flag is set, but let the user change
4433            it with the Flip View button. */
4434         flipView = appData.autoFlipView ?
4435           (newGameMode == IcsPlayingBlack) || ics_flip :
4436           appData.flipView;
4437
4438         /* Done with values from previous mode; copy in new ones */
4439         gameMode = newGameMode;
4440         ModeHighlight();
4441         ics_gamenum = gamenum;
4442         if (gamenum == gs_gamenum) {
4443             int klen = strlen(gs_kind);
4444             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4445             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4446             gameInfo.event = StrSave(str);
4447         } else {
4448             gameInfo.event = StrSave("ICS game");
4449         }
4450         gameInfo.site = StrSave(appData.icsHost);
4451         gameInfo.date = PGNDate();
4452         gameInfo.round = StrSave("-");
4453         gameInfo.white = StrSave(white);
4454         gameInfo.black = StrSave(black);
4455         timeControl = basetime * 60 * 1000;
4456         timeControl_2 = 0;
4457         timeIncrement = increment * 1000;
4458         movesPerSession = 0;
4459         gameInfo.timeControl = TimeControlTagValue();
4460         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4461   if (appData.debugMode) {
4462     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4463     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4464     setbuf(debugFP, NULL);
4465   }
4466
4467         gameInfo.outOfBook = NULL;
4468
4469         /* Do we have the ratings? */
4470         if (strcmp(player1Name, white) == 0 &&
4471             strcmp(player2Name, black) == 0) {
4472             if (appData.debugMode)
4473               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4474                       player1Rating, player2Rating);
4475             gameInfo.whiteRating = player1Rating;
4476             gameInfo.blackRating = player2Rating;
4477         } else if (strcmp(player2Name, white) == 0 &&
4478                    strcmp(player1Name, black) == 0) {
4479             if (appData.debugMode)
4480               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4481                       player2Rating, player1Rating);
4482             gameInfo.whiteRating = player2Rating;
4483             gameInfo.blackRating = player1Rating;
4484         }
4485         player1Name[0] = player2Name[0] = NULLCHAR;
4486
4487         /* Silence shouts if requested */
4488         if (appData.quietPlay &&
4489             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4490             SendToICS(ics_prefix);
4491             SendToICS("set shout 0\n");
4492         }
4493     }
4494
4495     /* Deal with midgame name changes */
4496     if (!newGame) {
4497         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4498             if (gameInfo.white) free(gameInfo.white);
4499             gameInfo.white = StrSave(white);
4500         }
4501         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4502             if (gameInfo.black) free(gameInfo.black);
4503             gameInfo.black = StrSave(black);
4504         }
4505     }
4506
4507     /* Throw away game result if anything actually changes in examine mode */
4508     if (gameMode == IcsExamining && !newGame) {
4509         gameInfo.result = GameUnfinished;
4510         if (gameInfo.resultDetails != NULL) {
4511             free(gameInfo.resultDetails);
4512             gameInfo.resultDetails = NULL;
4513         }
4514     }
4515
4516     /* In pausing && IcsExamining mode, we ignore boards coming
4517        in if they are in a different variation than we are. */
4518     if (pauseExamInvalid) return;
4519     if (pausing && gameMode == IcsExamining) {
4520         if (moveNum <= pauseExamForwardMostMove) {
4521             pauseExamInvalid = TRUE;
4522             forwardMostMove = pauseExamForwardMostMove;
4523             return;
4524         }
4525     }
4526
4527   if (appData.debugMode) {
4528     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4529   }
4530     /* Parse the board */
4531     for (k = 0; k < ranks; k++) {
4532       for (j = 0; j < files; j++)
4533         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4534       if(gameInfo.holdingsWidth > 1) {
4535            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4536            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4537       }
4538     }
4539     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4540       board[5][BOARD_RGHT+1] = WhiteAngel;
4541       board[6][BOARD_RGHT+1] = WhiteMarshall;
4542       board[1][0] = BlackMarshall;
4543       board[2][0] = BlackAngel;
4544       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4545     }
4546     CopyBoard(boards[moveNum], board);
4547     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4548     if (moveNum == 0) {
4549         startedFromSetupPosition =
4550           !CompareBoards(board, initialPosition);
4551         if(startedFromSetupPosition)
4552             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4553     }
4554
4555     /* [HGM] Set castling rights. Take the outermost Rooks,
4556        to make it also work for FRC opening positions. Note that board12
4557        is really defective for later FRC positions, as it has no way to
4558        indicate which Rook can castle if they are on the same side of King.
4559        For the initial position we grant rights to the outermost Rooks,
4560        and remember thos rights, and we then copy them on positions
4561        later in an FRC game. This means WB might not recognize castlings with
4562        Rooks that have moved back to their original position as illegal,
4563        but in ICS mode that is not its job anyway.
4564     */
4565     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4566     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4567
4568         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4569             if(board[0][i] == WhiteRook) j = i;
4570         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4571         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4572             if(board[0][i] == WhiteRook) j = i;
4573         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4574         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4575             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4576         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4577         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4578             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4579         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4580
4581         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4582         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4583         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4584             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4585         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4586             if(board[BOARD_HEIGHT-1][k] == bKing)
4587                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4588         if(gameInfo.variant == VariantTwoKings) {
4589             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4590             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4591             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4592         }
4593     } else { int r;
4594         r = boards[moveNum][CASTLING][0] = initialRights[0];
4595         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4596         r = boards[moveNum][CASTLING][1] = initialRights[1];
4597         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4598         r = boards[moveNum][CASTLING][3] = initialRights[3];
4599         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4600         r = boards[moveNum][CASTLING][4] = initialRights[4];
4601         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4602         /* wildcastle kludge: always assume King has rights */
4603         r = boards[moveNum][CASTLING][2] = initialRights[2];
4604         r = boards[moveNum][CASTLING][5] = initialRights[5];
4605     }
4606     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4607     boards[moveNum][EP_STATUS] = EP_NONE;
4608     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4609     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4610     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4611
4612
4613     if (ics_getting_history == H_GOT_REQ_HEADER ||
4614         ics_getting_history == H_GOT_UNREQ_HEADER) {
4615         /* This was an initial position from a move list, not
4616            the current position */
4617         return;
4618     }
4619
4620     /* Update currentMove and known move number limits */
4621     newMove = newGame || moveNum > forwardMostMove;
4622
4623     if (newGame) {
4624         forwardMostMove = backwardMostMove = currentMove = moveNum;
4625         if (gameMode == IcsExamining && moveNum == 0) {
4626           /* Workaround for ICS limitation: we are not told the wild
4627              type when starting to examine a game.  But if we ask for
4628              the move list, the move list header will tell us */
4629             ics_getting_history = H_REQUESTED;
4630             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4631             SendToICS(str);
4632         }
4633     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4634                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4635 #if ZIPPY
4636         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4637         /* [HGM] applied this also to an engine that is silently watching        */
4638         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4639             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4640             gameInfo.variant == currentlyInitializedVariant) {
4641           takeback = forwardMostMove - moveNum;
4642           for (i = 0; i < takeback; i++) {
4643             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4644             SendToProgram("undo\n", &first);
4645           }
4646         }
4647 #endif
4648
4649         forwardMostMove = moveNum;
4650         if (!pausing || currentMove > forwardMostMove)
4651           currentMove = forwardMostMove;
4652     } else {
4653         /* New part of history that is not contiguous with old part */
4654         if (pausing && gameMode == IcsExamining) {
4655             pauseExamInvalid = TRUE;
4656             forwardMostMove = pauseExamForwardMostMove;
4657             return;
4658         }
4659         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4660 #if ZIPPY
4661             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4662                 // [HGM] when we will receive the move list we now request, it will be
4663                 // fed to the engine from the first move on. So if the engine is not
4664                 // in the initial position now, bring it there.
4665                 InitChessProgram(&first, 0);
4666             }
4667 #endif
4668             ics_getting_history = H_REQUESTED;
4669             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4670             SendToICS(str);
4671         }
4672         forwardMostMove = backwardMostMove = currentMove = moveNum;
4673     }
4674
4675     /* Update the clocks */
4676     if (strchr(elapsed_time, '.')) {
4677       /* Time is in ms */
4678       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4679       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4680     } else {
4681       /* Time is in seconds */
4682       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4683       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4684     }
4685
4686
4687 #if ZIPPY
4688     if (appData.zippyPlay && newGame &&
4689         gameMode != IcsObserving && gameMode != IcsIdle &&
4690         gameMode != IcsExamining)
4691       ZippyFirstBoard(moveNum, basetime, increment);
4692 #endif
4693
4694     /* Put the move on the move list, first converting
4695        to canonical algebraic form. */
4696     if (moveNum > 0) {
4697   if (appData.debugMode) {
4698     int f = forwardMostMove;
4699     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4700             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4701             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4702     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4703     fprintf(debugFP, "moveNum = %d\n", moveNum);
4704     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4705     setbuf(debugFP, NULL);
4706   }
4707         if (moveNum <= backwardMostMove) {
4708             /* We don't know what the board looked like before
4709                this move.  Punt. */
4710           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4711             strcat(parseList[moveNum - 1], " ");
4712             strcat(parseList[moveNum - 1], elapsed_time);
4713             moveList[moveNum - 1][0] = NULLCHAR;
4714         } else if (strcmp(move_str, "none") == 0) {
4715             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4716             /* Again, we don't know what the board looked like;
4717                this is really the start of the game. */
4718             parseList[moveNum - 1][0] = NULLCHAR;
4719             moveList[moveNum - 1][0] = NULLCHAR;
4720             backwardMostMove = moveNum;
4721             startedFromSetupPosition = TRUE;
4722             fromX = fromY = toX = toY = -1;
4723         } else {
4724           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4725           //                 So we parse the long-algebraic move string in stead of the SAN move
4726           int valid; char buf[MSG_SIZ], *prom;
4727
4728           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4729                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4730           // str looks something like "Q/a1-a2"; kill the slash
4731           if(str[1] == '/')
4732             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4733           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4734           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4735                 strcat(buf, prom); // long move lacks promo specification!
4736           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4737                 if(appData.debugMode)
4738                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4739                 safeStrCpy(move_str, buf, MSG_SIZ);
4740           }
4741           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4742                                 &fromX, &fromY, &toX, &toY, &promoChar)
4743                || ParseOneMove(buf, moveNum - 1, &moveType,
4744                                 &fromX, &fromY, &toX, &toY, &promoChar);
4745           // end of long SAN patch
4746           if (valid) {
4747             (void) CoordsToAlgebraic(boards[moveNum - 1],
4748                                      PosFlags(moveNum - 1),
4749                                      fromY, fromX, toY, toX, promoChar,
4750                                      parseList[moveNum-1]);
4751             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4752               case MT_NONE:
4753               case MT_STALEMATE:
4754               default:
4755                 break;
4756               case MT_CHECK:
4757                 if(gameInfo.variant != VariantShogi)
4758                     strcat(parseList[moveNum - 1], "+");
4759                 break;
4760               case MT_CHECKMATE:
4761               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4762                 strcat(parseList[moveNum - 1], "#");
4763                 break;
4764             }
4765             strcat(parseList[moveNum - 1], " ");
4766             strcat(parseList[moveNum - 1], elapsed_time);
4767             /* currentMoveString is set as a side-effect of ParseOneMove */
4768             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4769             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4770             strcat(moveList[moveNum - 1], "\n");
4771
4772             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4773                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4774               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4775                 ChessSquare old, new = boards[moveNum][k][j];
4776                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4777                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4778                   if(old == new) continue;
4779                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4780                   else if(new == WhiteWazir || new == BlackWazir) {
4781                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4782                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4783                       else boards[moveNum][k][j] = old; // preserve type of Gold
4784                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4785                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4786               }
4787           } else {
4788             /* Move from ICS was illegal!?  Punt. */
4789             if (appData.debugMode) {
4790               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4791               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4792             }
4793             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4794             strcat(parseList[moveNum - 1], " ");
4795             strcat(parseList[moveNum - 1], elapsed_time);
4796             moveList[moveNum - 1][0] = NULLCHAR;
4797             fromX = fromY = toX = toY = -1;
4798           }
4799         }
4800   if (appData.debugMode) {
4801     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4802     setbuf(debugFP, NULL);
4803   }
4804
4805 #if ZIPPY
4806         /* Send move to chess program (BEFORE animating it). */
4807         if (appData.zippyPlay && !newGame && newMove &&
4808            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4809
4810             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4811                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4812                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4813                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4814                             move_str);
4815                     DisplayError(str, 0);
4816                 } else {
4817                     if (first.sendTime) {
4818                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4819                     }
4820                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4821                     if (firstMove && !bookHit) {
4822                         firstMove = FALSE;
4823                         if (first.useColors) {
4824                           SendToProgram(gameMode == IcsPlayingWhite ?
4825                                         "white\ngo\n" :
4826                                         "black\ngo\n", &first);
4827                         } else {
4828                           SendToProgram("go\n", &first);
4829                         }
4830                         first.maybeThinking = TRUE;
4831                     }
4832                 }
4833             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4834               if (moveList[moveNum - 1][0] == NULLCHAR) {
4835                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4836                 DisplayError(str, 0);
4837               } else {
4838                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4839                 SendMoveToProgram(moveNum - 1, &first);
4840               }
4841             }
4842         }
4843 #endif
4844     }
4845
4846     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4847         /* If move comes from a remote source, animate it.  If it
4848            isn't remote, it will have already been animated. */
4849         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4850             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4851         }
4852         if (!pausing && appData.highlightLastMove) {
4853             SetHighlights(fromX, fromY, toX, toY);
4854         }
4855     }
4856
4857     /* Start the clocks */
4858     whiteFlag = blackFlag = FALSE;
4859     appData.clockMode = !(basetime == 0 && increment == 0);
4860     if (ticking == 0) {
4861       ics_clock_paused = TRUE;
4862       StopClocks();
4863     } else if (ticking == 1) {
4864       ics_clock_paused = FALSE;
4865     }
4866     if (gameMode == IcsIdle ||
4867         relation == RELATION_OBSERVING_STATIC ||
4868         relation == RELATION_EXAMINING ||
4869         ics_clock_paused)
4870       DisplayBothClocks();
4871     else
4872       StartClocks();
4873
4874     /* Display opponents and material strengths */
4875     if (gameInfo.variant != VariantBughouse &&
4876         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4877         if (tinyLayout || smallLayout) {
4878             if(gameInfo.variant == VariantNormal)
4879               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4880                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4881                     basetime, increment);
4882             else
4883               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4884                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4885                     basetime, increment, (int) gameInfo.variant);
4886         } else {
4887             if(gameInfo.variant == VariantNormal)
4888               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4889                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4890                     basetime, increment);
4891             else
4892               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4893                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4894                     basetime, increment, VariantName(gameInfo.variant));
4895         }
4896         DisplayTitle(str);
4897   if (appData.debugMode) {
4898     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4899   }
4900     }
4901
4902
4903     /* Display the board */
4904     if (!pausing && !appData.noGUI) {
4905
4906       if (appData.premove)
4907           if (!gotPremove ||
4908              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4909              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4910               ClearPremoveHighlights();
4911
4912       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4913         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4914       DrawPosition(j, boards[currentMove]);
4915
4916       DisplayMove(moveNum - 1);
4917       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4918             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4919               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4920         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4921       }
4922     }
4923
4924     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4925 #if ZIPPY
4926     if(bookHit) { // [HGM] book: simulate book reply
4927         static char bookMove[MSG_SIZ]; // a bit generous?
4928
4929         programStats.nodes = programStats.depth = programStats.time =
4930         programStats.score = programStats.got_only_move = 0;
4931         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4932
4933         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4934         strcat(bookMove, bookHit);
4935         HandleMachineMove(bookMove, &first);
4936     }
4937 #endif
4938 }
4939
4940 void
4941 GetMoveListEvent ()
4942 {
4943     char buf[MSG_SIZ];
4944     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4945         ics_getting_history = H_REQUESTED;
4946         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4947         SendToICS(buf);
4948     }
4949 }
4950
4951 void
4952 SendToBoth (char *msg)
4953 {   // to make it easy to keep two engines in step in dual analysis
4954     SendToProgram(msg, &first);
4955     if(second.analyzing) SendToProgram(msg, &second);
4956 }
4957
4958 void
4959 AnalysisPeriodicEvent (int force)
4960 {
4961     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4962          && !force) || !appData.periodicUpdates)
4963       return;
4964
4965     /* Send . command to Crafty to collect stats */
4966     SendToBoth(".\n");
4967
4968     /* Don't send another until we get a response (this makes
4969        us stop sending to old Crafty's which don't understand
4970        the "." command (sending illegal cmds resets node count & time,
4971        which looks bad)) */
4972     programStats.ok_to_send = 0;
4973 }
4974
4975 void
4976 ics_update_width (int new_width)
4977 {
4978         ics_printf("set width %d\n", new_width);
4979 }
4980
4981 void
4982 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4983 {
4984     char buf[MSG_SIZ];
4985
4986     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4987         // null move in variant where engine does not understand it (for analysis purposes)
4988         SendBoard(cps, moveNum + 1); // send position after move in stead.
4989         return;
4990     }
4991     if (cps->useUsermove) {
4992       SendToProgram("usermove ", cps);
4993     }
4994     if (cps->useSAN) {
4995       char *space;
4996       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4997         int len = space - parseList[moveNum];
4998         memcpy(buf, parseList[moveNum], len);
4999         buf[len++] = '\n';
5000         buf[len] = NULLCHAR;
5001       } else {
5002         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5003       }
5004       SendToProgram(buf, cps);
5005     } else {
5006       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5007         AlphaRank(moveList[moveNum], 4);
5008         SendToProgram(moveList[moveNum], cps);
5009         AlphaRank(moveList[moveNum], 4); // and back
5010       } else
5011       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5012        * the engine. It would be nice to have a better way to identify castle
5013        * moves here. */
5014       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5015                                                                          && cps->useOOCastle) {
5016         int fromX = moveList[moveNum][0] - AAA;
5017         int fromY = moveList[moveNum][1] - ONE;
5018         int toX = moveList[moveNum][2] - AAA;
5019         int toY = moveList[moveNum][3] - ONE;
5020         if((boards[moveNum][fromY][fromX] == WhiteKing
5021             && boards[moveNum][toY][toX] == WhiteRook)
5022            || (boards[moveNum][fromY][fromX] == BlackKing
5023                && boards[moveNum][toY][toX] == BlackRook)) {
5024           if(toX > fromX) SendToProgram("O-O\n", cps);
5025           else SendToProgram("O-O-O\n", cps);
5026         }
5027         else SendToProgram(moveList[moveNum], cps);
5028       } else
5029       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5030         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5031           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5032           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5033                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5034         } else
5035           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5036                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5037         SendToProgram(buf, cps);
5038       }
5039       else SendToProgram(moveList[moveNum], cps);
5040       /* End of additions by Tord */
5041     }
5042
5043     /* [HGM] setting up the opening has brought engine in force mode! */
5044     /*       Send 'go' if we are in a mode where machine should play. */
5045     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5046         (gameMode == TwoMachinesPlay   ||
5047 #if ZIPPY
5048          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5049 #endif
5050          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5051         SendToProgram("go\n", cps);
5052   if (appData.debugMode) {
5053     fprintf(debugFP, "(extra)\n");
5054   }
5055     }
5056     setboardSpoiledMachineBlack = 0;
5057 }
5058
5059 void
5060 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5061 {
5062     char user_move[MSG_SIZ];
5063     char suffix[4];
5064
5065     if(gameInfo.variant == VariantSChess && promoChar) {
5066         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5067         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5068     } else suffix[0] = NULLCHAR;
5069
5070     switch (moveType) {
5071       default:
5072         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5073                 (int)moveType, fromX, fromY, toX, toY);
5074         DisplayError(user_move + strlen("say "), 0);
5075         break;
5076       case WhiteKingSideCastle:
5077       case BlackKingSideCastle:
5078       case WhiteQueenSideCastleWild:
5079       case BlackQueenSideCastleWild:
5080       /* PUSH Fabien */
5081       case WhiteHSideCastleFR:
5082       case BlackHSideCastleFR:
5083       /* POP Fabien */
5084         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5085         break;
5086       case WhiteQueenSideCastle:
5087       case BlackQueenSideCastle:
5088       case WhiteKingSideCastleWild:
5089       case BlackKingSideCastleWild:
5090       /* PUSH Fabien */
5091       case WhiteASideCastleFR:
5092       case BlackASideCastleFR:
5093       /* POP Fabien */
5094         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5095         break;
5096       case WhiteNonPromotion:
5097       case BlackNonPromotion:
5098         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5099         break;
5100       case WhitePromotion:
5101       case BlackPromotion:
5102         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5103            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5104           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5105                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5106                 PieceToChar(WhiteFerz));
5107         else if(gameInfo.variant == VariantGreat)
5108           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5109                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5110                 PieceToChar(WhiteMan));
5111         else
5112           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5113                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5114                 promoChar);
5115         break;
5116       case WhiteDrop:
5117       case BlackDrop:
5118       drop:
5119         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5120                  ToUpper(PieceToChar((ChessSquare) fromX)),
5121                  AAA + toX, ONE + toY);
5122         break;
5123       case IllegalMove:  /* could be a variant we don't quite understand */
5124         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5125       case NormalMove:
5126       case WhiteCapturesEnPassant:
5127       case BlackCapturesEnPassant:
5128         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5129                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5130         break;
5131     }
5132     SendToICS(user_move);
5133     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5134         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5135 }
5136
5137 void
5138 UploadGameEvent ()
5139 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5140     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5141     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5142     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5143       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5144       return;
5145     }
5146     if(gameMode != IcsExamining) { // is this ever not the case?
5147         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5148
5149         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5150           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5151         } else { // on FICS we must first go to general examine mode
5152           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5153         }
5154         if(gameInfo.variant != VariantNormal) {
5155             // try figure out wild number, as xboard names are not always valid on ICS
5156             for(i=1; i<=36; i++) {
5157               snprintf(buf, MSG_SIZ, "wild/%d", i);
5158                 if(StringToVariant(buf) == gameInfo.variant) break;
5159             }
5160             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5161             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5162             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5163         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5164         SendToICS(ics_prefix);
5165         SendToICS(buf);
5166         if(startedFromSetupPosition || backwardMostMove != 0) {
5167           fen = PositionToFEN(backwardMostMove, NULL, 1);
5168           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5169             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5170             SendToICS(buf);
5171           } else { // FICS: everything has to set by separate bsetup commands
5172             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5173             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5174             SendToICS(buf);
5175             if(!WhiteOnMove(backwardMostMove)) {
5176                 SendToICS("bsetup tomove black\n");
5177             }
5178             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5179             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5180             SendToICS(buf);
5181             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5182             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5183             SendToICS(buf);
5184             i = boards[backwardMostMove][EP_STATUS];
5185             if(i >= 0) { // set e.p.
5186               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5187                 SendToICS(buf);
5188             }
5189             bsetup++;
5190           }
5191         }
5192       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5193             SendToICS("bsetup done\n"); // switch to normal examining.
5194     }
5195     for(i = backwardMostMove; i<last; i++) {
5196         char buf[20];
5197         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5198         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5199             int len = strlen(moveList[i]);
5200             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5201             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5202         }
5203         SendToICS(buf);
5204     }
5205     SendToICS(ics_prefix);
5206     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5207 }
5208
5209 void
5210 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5211 {
5212     if (rf == DROP_RANK) {
5213       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5214       sprintf(move, "%c@%c%c\n",
5215                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5216     } else {
5217         if (promoChar == 'x' || promoChar == NULLCHAR) {
5218           sprintf(move, "%c%c%c%c\n",
5219                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5220         } else {
5221             sprintf(move, "%c%c%c%c%c\n",
5222                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5223         }
5224     }
5225 }
5226
5227 void
5228 ProcessICSInitScript (FILE *f)
5229 {
5230     char buf[MSG_SIZ];
5231
5232     while (fgets(buf, MSG_SIZ, f)) {
5233         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5234     }
5235
5236     fclose(f);
5237 }
5238
5239
5240 static int lastX, lastY, selectFlag, dragging;
5241
5242 void
5243 Sweep (int step)
5244 {
5245     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5246     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5247     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5248     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5249     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5250     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5251     do {
5252         promoSweep -= step;
5253         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5254         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5255         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5256         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5257         if(!step) step = -1;
5258     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5259             appData.testLegality && (promoSweep == king ||
5260             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5261     if(toX >= 0) {
5262         int victim = boards[currentMove][toY][toX];
5263         boards[currentMove][toY][toX] = promoSweep;
5264         DrawPosition(FALSE, boards[currentMove]);
5265         boards[currentMove][toY][toX] = victim;
5266     } else
5267     ChangeDragPiece(promoSweep);
5268 }
5269
5270 int
5271 PromoScroll (int x, int y)
5272 {
5273   int step = 0;
5274
5275   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5276   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5277   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5278   if(!step) return FALSE;
5279   lastX = x; lastY = y;
5280   if((promoSweep < BlackPawn) == flipView) step = -step;
5281   if(step > 0) selectFlag = 1;
5282   if(!selectFlag) Sweep(step);
5283   return FALSE;
5284 }
5285
5286 void
5287 NextPiece (int step)
5288 {
5289     ChessSquare piece = boards[currentMove][toY][toX];
5290     do {
5291         pieceSweep -= step;
5292         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5293         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5294         if(!step) step = -1;
5295     } while(PieceToChar(pieceSweep) == '.');
5296     boards[currentMove][toY][toX] = pieceSweep;
5297     DrawPosition(FALSE, boards[currentMove]);
5298     boards[currentMove][toY][toX] = piece;
5299 }
5300 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5301 void
5302 AlphaRank (char *move, int n)
5303 {
5304 //    char *p = move, c; int x, y;
5305
5306     if (appData.debugMode) {
5307         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5308     }
5309
5310     if(move[1]=='*' &&
5311        move[2]>='0' && move[2]<='9' &&
5312        move[3]>='a' && move[3]<='x'    ) {
5313         move[1] = '@';
5314         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5315         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5316     } else
5317     if(move[0]>='0' && move[0]<='9' &&
5318        move[1]>='a' && move[1]<='x' &&
5319        move[2]>='0' && move[2]<='9' &&
5320        move[3]>='a' && move[3]<='x'    ) {
5321         /* input move, Shogi -> normal */
5322         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5323         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5324         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5325         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5326     } else
5327     if(move[1]=='@' &&
5328        move[3]>='0' && move[3]<='9' &&
5329        move[2]>='a' && move[2]<='x'    ) {
5330         move[1] = '*';
5331         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5332         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5333     } else
5334     if(
5335        move[0]>='a' && move[0]<='x' &&
5336        move[3]>='0' && move[3]<='9' &&
5337        move[2]>='a' && move[2]<='x'    ) {
5338          /* output move, normal -> Shogi */
5339         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5340         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5341         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5342         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5343         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5344     }
5345     if (appData.debugMode) {
5346         fprintf(debugFP, "   out = '%s'\n", move);
5347     }
5348 }
5349
5350 char yy_textstr[8000];
5351
5352 /* Parser for moves from gnuchess, ICS, or user typein box */
5353 Boolean
5354 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5355 {
5356     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5357
5358     switch (*moveType) {
5359       case WhitePromotion:
5360       case BlackPromotion:
5361       case WhiteNonPromotion:
5362       case BlackNonPromotion:
5363       case NormalMove:
5364       case WhiteCapturesEnPassant:
5365       case BlackCapturesEnPassant:
5366       case WhiteKingSideCastle:
5367       case WhiteQueenSideCastle:
5368       case BlackKingSideCastle:
5369       case BlackQueenSideCastle:
5370       case WhiteKingSideCastleWild:
5371       case WhiteQueenSideCastleWild:
5372       case BlackKingSideCastleWild:
5373       case BlackQueenSideCastleWild:
5374       /* Code added by Tord: */
5375       case WhiteHSideCastleFR:
5376       case WhiteASideCastleFR:
5377       case BlackHSideCastleFR:
5378       case BlackASideCastleFR:
5379       /* End of code added by Tord */
5380       case IllegalMove:         /* bug or odd chess variant */
5381         *fromX = currentMoveString[0] - AAA;
5382         *fromY = currentMoveString[1] - ONE;
5383         *toX = currentMoveString[2] - AAA;
5384         *toY = currentMoveString[3] - ONE;
5385         *promoChar = currentMoveString[4];
5386         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5387             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5388     if (appData.debugMode) {
5389         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5390     }
5391             *fromX = *fromY = *toX = *toY = 0;
5392             return FALSE;
5393         }
5394         if (appData.testLegality) {
5395           return (*moveType != IllegalMove);
5396         } else {
5397           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5398                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5399         }
5400
5401       case WhiteDrop:
5402       case BlackDrop:
5403         *fromX = *moveType == WhiteDrop ?
5404           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5405           (int) CharToPiece(ToLower(currentMoveString[0]));
5406         *fromY = DROP_RANK;
5407         *toX = currentMoveString[2] - AAA;
5408         *toY = currentMoveString[3] - ONE;
5409         *promoChar = NULLCHAR;
5410         return TRUE;
5411
5412       case AmbiguousMove:
5413       case ImpossibleMove:
5414       case EndOfFile:
5415       case ElapsedTime:
5416       case Comment:
5417       case PGNTag:
5418       case NAG:
5419       case WhiteWins:
5420       case BlackWins:
5421       case GameIsDrawn:
5422       default:
5423     if (appData.debugMode) {
5424         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5425     }
5426         /* bug? */
5427         *fromX = *fromY = *toX = *toY = 0;
5428         *promoChar = NULLCHAR;
5429         return FALSE;
5430     }
5431 }
5432
5433 Boolean pushed = FALSE;
5434 char *lastParseAttempt;
5435
5436 void
5437 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5438 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5439   int fromX, fromY, toX, toY; char promoChar;
5440   ChessMove moveType;
5441   Boolean valid;
5442   int nr = 0;
5443
5444   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5445   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5446     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5447     pushed = TRUE;
5448   }
5449   endPV = forwardMostMove;
5450   do {
5451     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5452     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5453     lastParseAttempt = pv;
5454     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5455     if(!valid && nr == 0 &&
5456        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5457         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5458         // Hande case where played move is different from leading PV move
5459         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5460         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5461         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5462         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5463           endPV += 2; // if position different, keep this
5464           moveList[endPV-1][0] = fromX + AAA;
5465           moveList[endPV-1][1] = fromY + ONE;
5466           moveList[endPV-1][2] = toX + AAA;
5467           moveList[endPV-1][3] = toY + ONE;
5468           parseList[endPV-1][0] = NULLCHAR;
5469           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5470         }
5471       }
5472     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5473     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5474     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5475     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5476         valid++; // allow comments in PV
5477         continue;
5478     }
5479     nr++;
5480     if(endPV+1 > framePtr) break; // no space, truncate
5481     if(!valid) break;
5482     endPV++;
5483     CopyBoard(boards[endPV], boards[endPV-1]);
5484     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5485     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5486     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5487     CoordsToAlgebraic(boards[endPV - 1],
5488                              PosFlags(endPV - 1),
5489                              fromY, fromX, toY, toX, promoChar,
5490                              parseList[endPV - 1]);
5491   } while(valid);
5492   if(atEnd == 2) return; // used hidden, for PV conversion
5493   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5494   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5495   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5496                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5497   DrawPosition(TRUE, boards[currentMove]);
5498 }
5499
5500 int
5501 MultiPV (ChessProgramState *cps)
5502 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5503         int i;
5504         for(i=0; i<cps->nrOptions; i++)
5505             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5506                 return i;
5507         return -1;
5508 }
5509
5510 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5511
5512 Boolean
5513 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5514 {
5515         int startPV, multi, lineStart, origIndex = index;
5516         char *p, buf2[MSG_SIZ];
5517         ChessProgramState *cps = (pane ? &second : &first);
5518
5519         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5520         lastX = x; lastY = y;
5521         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5522         lineStart = startPV = index;
5523         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5524         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5525         index = startPV;
5526         do{ while(buf[index] && buf[index] != '\n') index++;
5527         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5528         buf[index] = 0;
5529         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5530                 int n = cps->option[multi].value;
5531                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5532                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5533                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5534                 cps->option[multi].value = n;
5535                 *start = *end = 0;
5536                 return FALSE;
5537         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5538                 ExcludeClick(origIndex - lineStart);
5539                 return FALSE;
5540         }
5541         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5542         *start = startPV; *end = index-1;
5543         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5544         return TRUE;
5545 }
5546
5547 char *
5548 PvToSAN (char *pv)
5549 {
5550         static char buf[10*MSG_SIZ];
5551         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5552         *buf = NULLCHAR;
5553         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5554         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5555         for(i = forwardMostMove; i<endPV; i++){
5556             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5557             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5558             k += strlen(buf+k);
5559         }
5560         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5561         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5562         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5563         endPV = savedEnd;
5564         return buf;
5565 }
5566
5567 Boolean
5568 LoadPV (int x, int y)
5569 { // called on right mouse click to load PV
5570   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5571   lastX = x; lastY = y;
5572   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5573   extendGame = FALSE;
5574   return TRUE;
5575 }
5576
5577 void
5578 UnLoadPV ()
5579 {
5580   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5581   if(endPV < 0) return;
5582   if(appData.autoCopyPV) CopyFENToClipboard();
5583   endPV = -1;
5584   if(extendGame && currentMove > forwardMostMove) {
5585         Boolean saveAnimate = appData.animate;
5586         if(pushed) {
5587             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5588                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5589             } else storedGames--; // abandon shelved tail of original game
5590         }
5591         pushed = FALSE;
5592         forwardMostMove = currentMove;
5593         currentMove = oldFMM;
5594         appData.animate = FALSE;
5595         ToNrEvent(forwardMostMove);
5596         appData.animate = saveAnimate;
5597   }
5598   currentMove = forwardMostMove;
5599   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5600   ClearPremoveHighlights();
5601   DrawPosition(TRUE, boards[currentMove]);
5602 }
5603
5604 void
5605 MovePV (int x, int y, int h)
5606 { // step through PV based on mouse coordinates (called on mouse move)
5607   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5608
5609   // we must somehow check if right button is still down (might be released off board!)
5610   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5611   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5612   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5613   if(!step) return;
5614   lastX = x; lastY = y;
5615
5616   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5617   if(endPV < 0) return;
5618   if(y < margin) step = 1; else
5619   if(y > h - margin) step = -1;
5620   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5621   currentMove += step;
5622   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5623   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5624                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5625   DrawPosition(FALSE, boards[currentMove]);
5626 }
5627
5628
5629 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5630 // All positions will have equal probability, but the current method will not provide a unique
5631 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5632 #define DARK 1
5633 #define LITE 2
5634 #define ANY 3
5635
5636 int squaresLeft[4];
5637 int piecesLeft[(int)BlackPawn];
5638 int seed, nrOfShuffles;
5639
5640 void
5641 GetPositionNumber ()
5642 {       // sets global variable seed
5643         int i;
5644
5645         seed = appData.defaultFrcPosition;
5646         if(seed < 0) { // randomize based on time for negative FRC position numbers
5647                 for(i=0; i<50; i++) seed += random();
5648                 seed = random() ^ random() >> 8 ^ random() << 8;
5649                 if(seed<0) seed = -seed;
5650         }
5651 }
5652
5653 int
5654 put (Board board, int pieceType, int rank, int n, int shade)
5655 // put the piece on the (n-1)-th empty squares of the given shade
5656 {
5657         int i;
5658
5659         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5660                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5661                         board[rank][i] = (ChessSquare) pieceType;
5662                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5663                         squaresLeft[ANY]--;
5664                         piecesLeft[pieceType]--;
5665                         return i;
5666                 }
5667         }
5668         return -1;
5669 }
5670
5671
5672 void
5673 AddOnePiece (Board board, int pieceType, int rank, int shade)
5674 // calculate where the next piece goes, (any empty square), and put it there
5675 {
5676         int i;
5677
5678         i = seed % squaresLeft[shade];
5679         nrOfShuffles *= squaresLeft[shade];
5680         seed /= squaresLeft[shade];
5681         put(board, pieceType, rank, i, shade);
5682 }
5683
5684 void
5685 AddTwoPieces (Board board, int pieceType, int rank)
5686 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5687 {
5688         int i, n=squaresLeft[ANY], j=n-1, k;
5689
5690         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5691         i = seed % k;  // pick one
5692         nrOfShuffles *= k;
5693         seed /= k;
5694         while(i >= j) i -= j--;
5695         j = n - 1 - j; i += j;
5696         put(board, pieceType, rank, j, ANY);
5697         put(board, pieceType, rank, i, ANY);
5698 }
5699
5700 void
5701 SetUpShuffle (Board board, int number)
5702 {
5703         int i, p, first=1;
5704
5705         GetPositionNumber(); nrOfShuffles = 1;
5706
5707         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5708         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5709         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5710
5711         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5712
5713         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5714             p = (int) board[0][i];
5715             if(p < (int) BlackPawn) piecesLeft[p] ++;
5716             board[0][i] = EmptySquare;
5717         }
5718
5719         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5720             // shuffles restricted to allow normal castling put KRR first
5721             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5722                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5723             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5724                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5725             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5726                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5727             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5728                 put(board, WhiteRook, 0, 0, ANY);
5729             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5730         }
5731
5732         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5733             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5734             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5735                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5736                 while(piecesLeft[p] >= 2) {
5737                     AddOnePiece(board, p, 0, LITE);
5738                     AddOnePiece(board, p, 0, DARK);
5739                 }
5740                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5741             }
5742
5743         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5744             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5745             // but we leave King and Rooks for last, to possibly obey FRC restriction
5746             if(p == (int)WhiteRook) continue;
5747             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5748             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5749         }
5750
5751         // now everything is placed, except perhaps King (Unicorn) and Rooks
5752
5753         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5754             // Last King gets castling rights
5755             while(piecesLeft[(int)WhiteUnicorn]) {
5756                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5757                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5758             }
5759
5760             while(piecesLeft[(int)WhiteKing]) {
5761                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5762                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5763             }
5764
5765
5766         } else {
5767             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5768             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5769         }
5770
5771         // Only Rooks can be left; simply place them all
5772         while(piecesLeft[(int)WhiteRook]) {
5773                 i = put(board, WhiteRook, 0, 0, ANY);
5774                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5775                         if(first) {
5776                                 first=0;
5777                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5778                         }
5779                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5780                 }
5781         }
5782         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5783             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5784         }
5785
5786         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5787 }
5788
5789 int
5790 SetCharTable (char *table, const char * map)
5791 /* [HGM] moved here from winboard.c because of its general usefulness */
5792 /*       Basically a safe strcpy that uses the last character as King */
5793 {
5794     int result = FALSE; int NrPieces;
5795
5796     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5797                     && NrPieces >= 12 && !(NrPieces&1)) {
5798         int i; /* [HGM] Accept even length from 12 to 34 */
5799
5800         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5801         for( i=0; i<NrPieces/2-1; i++ ) {
5802             table[i] = map[i];
5803             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5804         }
5805         table[(int) WhiteKing]  = map[NrPieces/2-1];
5806         table[(int) BlackKing]  = map[NrPieces-1];
5807
5808         result = TRUE;
5809     }
5810
5811     return result;
5812 }
5813
5814 void
5815 Prelude (Board board)
5816 {       // [HGM] superchess: random selection of exo-pieces
5817         int i, j, k; ChessSquare p;
5818         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5819
5820         GetPositionNumber(); // use FRC position number
5821
5822         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5823             SetCharTable(pieceToChar, appData.pieceToCharTable);
5824             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5825                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5826         }
5827
5828         j = seed%4;                 seed /= 4;
5829         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5830         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5831         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5832         j = seed%3 + (seed%3 >= j); seed /= 3;
5833         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5834         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5835         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5836         j = seed%3;                 seed /= 3;
5837         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5838         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5839         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5840         j = seed%2 + (seed%2 >= j); seed /= 2;
5841         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5842         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5843         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5844         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5845         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5846         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5847         put(board, exoPieces[0],    0, 0, ANY);
5848         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5849 }
5850
5851 void
5852 InitPosition (int redraw)
5853 {
5854     ChessSquare (* pieces)[BOARD_FILES];
5855     int i, j, pawnRow, overrule,
5856     oldx = gameInfo.boardWidth,
5857     oldy = gameInfo.boardHeight,
5858     oldh = gameInfo.holdingsWidth;
5859     static int oldv;
5860
5861     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5862
5863     /* [AS] Initialize pv info list [HGM] and game status */
5864     {
5865         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5866             pvInfoList[i].depth = 0;
5867             boards[i][EP_STATUS] = EP_NONE;
5868             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5869         }
5870
5871         initialRulePlies = 0; /* 50-move counter start */
5872
5873         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5874         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5875     }
5876
5877
5878     /* [HGM] logic here is completely changed. In stead of full positions */
5879     /* the initialized data only consist of the two backranks. The switch */
5880     /* selects which one we will use, which is than copied to the Board   */
5881     /* initialPosition, which for the rest is initialized by Pawns and    */
5882     /* empty squares. This initial position is then copied to boards[0],  */
5883     /* possibly after shuffling, so that it remains available.            */
5884
5885     gameInfo.holdingsWidth = 0; /* default board sizes */
5886     gameInfo.boardWidth    = 8;
5887     gameInfo.boardHeight   = 8;
5888     gameInfo.holdingsSize  = 0;
5889     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5890     for(i=0; i<BOARD_FILES-2; i++)
5891       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5892     initialPosition[EP_STATUS] = EP_NONE;
5893     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5894     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5895          SetCharTable(pieceNickName, appData.pieceNickNames);
5896     else SetCharTable(pieceNickName, "............");
5897     pieces = FIDEArray;
5898
5899     switch (gameInfo.variant) {
5900     case VariantFischeRandom:
5901       shuffleOpenings = TRUE;
5902     default:
5903       break;
5904     case VariantShatranj:
5905       pieces = ShatranjArray;
5906       nrCastlingRights = 0;
5907       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5908       break;
5909     case VariantMakruk:
5910       pieces = makrukArray;
5911       nrCastlingRights = 0;
5912       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5913       break;
5914     case VariantASEAN:
5915       pieces = aseanArray;
5916       nrCastlingRights = 0;
5917       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5918       break;
5919     case VariantTwoKings:
5920       pieces = twoKingsArray;
5921       break;
5922     case VariantGrand:
5923       pieces = GrandArray;
5924       nrCastlingRights = 0;
5925       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5926       gameInfo.boardWidth = 10;
5927       gameInfo.boardHeight = 10;
5928       gameInfo.holdingsSize = 7;
5929       break;
5930     case VariantCapaRandom:
5931       shuffleOpenings = TRUE;
5932     case VariantCapablanca:
5933       pieces = CapablancaArray;
5934       gameInfo.boardWidth = 10;
5935       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5936       break;
5937     case VariantGothic:
5938       pieces = GothicArray;
5939       gameInfo.boardWidth = 10;
5940       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5941       break;
5942     case VariantSChess:
5943       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5944       gameInfo.holdingsSize = 7;
5945       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5946       break;
5947     case VariantJanus:
5948       pieces = JanusArray;
5949       gameInfo.boardWidth = 10;
5950       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5951       nrCastlingRights = 6;
5952         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5953         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5954         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5955         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5956         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5957         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5958       break;
5959     case VariantFalcon:
5960       pieces = FalconArray;
5961       gameInfo.boardWidth = 10;
5962       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5963       break;
5964     case VariantXiangqi:
5965       pieces = XiangqiArray;
5966       gameInfo.boardWidth  = 9;
5967       gameInfo.boardHeight = 10;
5968       nrCastlingRights = 0;
5969       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5970       break;
5971     case VariantShogi:
5972       pieces = ShogiArray;
5973       gameInfo.boardWidth  = 9;
5974       gameInfo.boardHeight = 9;
5975       gameInfo.holdingsSize = 7;
5976       nrCastlingRights = 0;
5977       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5978       break;
5979     case VariantCourier:
5980       pieces = CourierArray;
5981       gameInfo.boardWidth  = 12;
5982       nrCastlingRights = 0;
5983       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5984       break;
5985     case VariantKnightmate:
5986       pieces = KnightmateArray;
5987       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5988       break;
5989     case VariantSpartan:
5990       pieces = SpartanArray;
5991       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5992       break;
5993     case VariantFairy:
5994       pieces = fairyArray;
5995       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5996       break;
5997     case VariantGreat:
5998       pieces = GreatArray;
5999       gameInfo.boardWidth = 10;
6000       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6001       gameInfo.holdingsSize = 8;
6002       break;
6003     case VariantSuper:
6004       pieces = FIDEArray;
6005       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6006       gameInfo.holdingsSize = 8;
6007       startedFromSetupPosition = TRUE;
6008       break;
6009     case VariantCrazyhouse:
6010     case VariantBughouse:
6011       pieces = FIDEArray;
6012       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6013       gameInfo.holdingsSize = 5;
6014       break;
6015     case VariantWildCastle:
6016       pieces = FIDEArray;
6017       /* !!?shuffle with kings guaranteed to be on d or e file */
6018       shuffleOpenings = 1;
6019       break;
6020     case VariantNoCastle:
6021       pieces = FIDEArray;
6022       nrCastlingRights = 0;
6023       /* !!?unconstrained back-rank shuffle */
6024       shuffleOpenings = 1;
6025       break;
6026     }
6027
6028     overrule = 0;
6029     if(appData.NrFiles >= 0) {
6030         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6031         gameInfo.boardWidth = appData.NrFiles;
6032     }
6033     if(appData.NrRanks >= 0) {
6034         gameInfo.boardHeight = appData.NrRanks;
6035     }
6036     if(appData.holdingsSize >= 0) {
6037         i = appData.holdingsSize;
6038         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6039         gameInfo.holdingsSize = i;
6040     }
6041     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6042     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6043         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6044
6045     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6046     if(pawnRow < 1) pawnRow = 1;
6047     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6048
6049     /* User pieceToChar list overrules defaults */
6050     if(appData.pieceToCharTable != NULL)
6051         SetCharTable(pieceToChar, appData.pieceToCharTable);
6052
6053     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6054
6055         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6056             s = (ChessSquare) 0; /* account holding counts in guard band */
6057         for( i=0; i<BOARD_HEIGHT; i++ )
6058             initialPosition[i][j] = s;
6059
6060         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6061         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6062         initialPosition[pawnRow][j] = WhitePawn;
6063         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6064         if(gameInfo.variant == VariantXiangqi) {
6065             if(j&1) {
6066                 initialPosition[pawnRow][j] =
6067                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6068                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6069                    initialPosition[2][j] = WhiteCannon;
6070                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6071                 }
6072             }
6073         }
6074         if(gameInfo.variant == VariantGrand) {
6075             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6076                initialPosition[0][j] = WhiteRook;
6077                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6078             }
6079         }
6080         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6081     }
6082     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6083
6084             j=BOARD_LEFT+1;
6085             initialPosition[1][j] = WhiteBishop;
6086             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6087             j=BOARD_RGHT-2;
6088             initialPosition[1][j] = WhiteRook;
6089             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6090     }
6091
6092     if( nrCastlingRights == -1) {
6093         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6094         /*       This sets default castling rights from none to normal corners   */
6095         /* Variants with other castling rights must set them themselves above    */
6096         nrCastlingRights = 6;
6097
6098         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6099         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6100         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6101         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6102         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6103         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6104      }
6105
6106      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6107      if(gameInfo.variant == VariantGreat) { // promotion commoners
6108         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6109         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6110         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6111         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6112      }
6113      if( gameInfo.variant == VariantSChess ) {
6114       initialPosition[1][0] = BlackMarshall;
6115       initialPosition[2][0] = BlackAngel;
6116       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6117       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6118       initialPosition[1][1] = initialPosition[2][1] =
6119       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6120      }
6121   if (appData.debugMode) {
6122     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6123   }
6124     if(shuffleOpenings) {
6125         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6126         startedFromSetupPosition = TRUE;
6127     }
6128     if(startedFromPositionFile) {
6129       /* [HGM] loadPos: use PositionFile for every new game */
6130       CopyBoard(initialPosition, filePosition);
6131       for(i=0; i<nrCastlingRights; i++)
6132           initialRights[i] = filePosition[CASTLING][i];
6133       startedFromSetupPosition = TRUE;
6134     }
6135
6136     CopyBoard(boards[0], initialPosition);
6137
6138     if(oldx != gameInfo.boardWidth ||
6139        oldy != gameInfo.boardHeight ||
6140        oldv != gameInfo.variant ||
6141        oldh != gameInfo.holdingsWidth
6142                                          )
6143             InitDrawingSizes(-2 ,0);
6144
6145     oldv = gameInfo.variant;
6146     if (redraw)
6147       DrawPosition(TRUE, boards[currentMove]);
6148 }
6149
6150 void
6151 SendBoard (ChessProgramState *cps, int moveNum)
6152 {
6153     char message[MSG_SIZ];
6154
6155     if (cps->useSetboard) {
6156       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6157       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6158       SendToProgram(message, cps);
6159       free(fen);
6160
6161     } else {
6162       ChessSquare *bp;
6163       int i, j, left=0, right=BOARD_WIDTH;
6164       /* Kludge to set black to move, avoiding the troublesome and now
6165        * deprecated "black" command.
6166        */
6167       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6168         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6169
6170       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6171
6172       SendToProgram("edit\n", cps);
6173       SendToProgram("#\n", cps);
6174       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6175         bp = &boards[moveNum][i][left];
6176         for (j = left; j < right; j++, bp++) {
6177           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6178           if ((int) *bp < (int) BlackPawn) {
6179             if(j == BOARD_RGHT+1)
6180                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6181             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6182             if(message[0] == '+' || message[0] == '~') {
6183               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6184                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6185                         AAA + j, ONE + i);
6186             }
6187             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6188                 message[1] = BOARD_RGHT   - 1 - j + '1';
6189                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6190             }
6191             SendToProgram(message, cps);
6192           }
6193         }
6194       }
6195
6196       SendToProgram("c\n", cps);
6197       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6198         bp = &boards[moveNum][i][left];
6199         for (j = left; j < right; j++, bp++) {
6200           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6201           if (((int) *bp != (int) EmptySquare)
6202               && ((int) *bp >= (int) BlackPawn)) {
6203             if(j == BOARD_LEFT-2)
6204                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6205             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6206                     AAA + j, ONE + i);
6207             if(message[0] == '+' || message[0] == '~') {
6208               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6209                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6210                         AAA + j, ONE + i);
6211             }
6212             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6213                 message[1] = BOARD_RGHT   - 1 - j + '1';
6214                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6215             }
6216             SendToProgram(message, cps);
6217           }
6218         }
6219       }
6220
6221       SendToProgram(".\n", cps);
6222     }
6223     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6224 }
6225
6226 char exclusionHeader[MSG_SIZ];
6227 int exCnt, excludePtr;
6228 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6229 static Exclusion excluTab[200];
6230 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6231
6232 static void
6233 WriteMap (int s)
6234 {
6235     int j;
6236     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6237     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6238 }
6239
6240 static void
6241 ClearMap ()
6242 {
6243     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6244     excludePtr = 24; exCnt = 0;
6245     WriteMap(0);
6246 }
6247
6248 static void
6249 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6250 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6251     char buf[2*MOVE_LEN], *p;
6252     Exclusion *e = excluTab;
6253     int i;
6254     for(i=0; i<exCnt; i++)
6255         if(e[i].ff == fromX && e[i].fr == fromY &&
6256            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6257     if(i == exCnt) { // was not in exclude list; add it
6258         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6259         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6260             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6261             return; // abort
6262         }
6263         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6264         excludePtr++; e[i].mark = excludePtr++;
6265         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6266         exCnt++;
6267     }
6268     exclusionHeader[e[i].mark] = state;
6269 }
6270
6271 static int
6272 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6273 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6274     char buf[MSG_SIZ];
6275     int j, k;
6276     ChessMove moveType;
6277     if((signed char)promoChar == -1) { // kludge to indicate best move
6278         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6279             return 1; // if unparsable, abort
6280     }
6281     // update exclusion map (resolving toggle by consulting existing state)
6282     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6283     j = k%8; k >>= 3;
6284     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6285     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6286          excludeMap[k] |=   1<<j;
6287     else excludeMap[k] &= ~(1<<j);
6288     // update header
6289     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6290     // inform engine
6291     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6292     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6293     SendToBoth(buf);
6294     return (state == '+');
6295 }
6296
6297 static void
6298 ExcludeClick (int index)
6299 {
6300     int i, j;
6301     Exclusion *e = excluTab;
6302     if(index < 25) { // none, best or tail clicked
6303         if(index < 13) { // none: include all
6304             WriteMap(0); // clear map
6305             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6306             SendToBoth("include all\n"); // and inform engine
6307         } else if(index > 18) { // tail
6308             if(exclusionHeader[19] == '-') { // tail was excluded
6309                 SendToBoth("include all\n");
6310                 WriteMap(0); // clear map completely
6311                 // now re-exclude selected moves
6312                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6313                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6314             } else { // tail was included or in mixed state
6315                 SendToBoth("exclude all\n");
6316                 WriteMap(0xFF); // fill map completely
6317                 // now re-include selected moves
6318                 j = 0; // count them
6319                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6320                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6321                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6322             }
6323         } else { // best
6324             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6325         }
6326     } else {
6327         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6328             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6329             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6330             break;
6331         }
6332     }
6333 }
6334
6335 ChessSquare
6336 DefaultPromoChoice (int white)
6337 {
6338     ChessSquare result;
6339     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6340        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6341         result = WhiteFerz; // no choice
6342     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6343         result= WhiteKing; // in Suicide Q is the last thing we want
6344     else if(gameInfo.variant == VariantSpartan)
6345         result = white ? WhiteQueen : WhiteAngel;
6346     else result = WhiteQueen;
6347     if(!white) result = WHITE_TO_BLACK result;
6348     return result;
6349 }
6350
6351 static int autoQueen; // [HGM] oneclick
6352
6353 int
6354 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6355 {
6356     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6357     /* [HGM] add Shogi promotions */
6358     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6359     ChessSquare piece;
6360     ChessMove moveType;
6361     Boolean premove;
6362
6363     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6364     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6365
6366     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6367       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6368         return FALSE;
6369
6370     piece = boards[currentMove][fromY][fromX];
6371     if(gameInfo.variant == VariantShogi) {
6372         promotionZoneSize = BOARD_HEIGHT/3;
6373         highestPromotingPiece = (int)WhiteFerz;
6374     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6375         promotionZoneSize = 3;
6376     }
6377
6378     // Treat Lance as Pawn when it is not representing Amazon
6379     if(gameInfo.variant != VariantSuper) {
6380         if(piece == WhiteLance) piece = WhitePawn; else
6381         if(piece == BlackLance) piece = BlackPawn;
6382     }
6383
6384     // next weed out all moves that do not touch the promotion zone at all
6385     if((int)piece >= BlackPawn) {
6386         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6387              return FALSE;
6388         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6389     } else {
6390         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6391            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6392     }
6393
6394     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6395
6396     // weed out mandatory Shogi promotions
6397     if(gameInfo.variant == VariantShogi) {
6398         if(piece >= BlackPawn) {
6399             if(toY == 0 && piece == BlackPawn ||
6400                toY == 0 && piece == BlackQueen ||
6401                toY <= 1 && piece == BlackKnight) {
6402                 *promoChoice = '+';
6403                 return FALSE;
6404             }
6405         } else {
6406             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6407                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6408                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6409                 *promoChoice = '+';
6410                 return FALSE;
6411             }
6412         }
6413     }
6414
6415     // weed out obviously illegal Pawn moves
6416     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6417         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6418         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6419         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6420         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6421         // note we are not allowed to test for valid (non-)capture, due to premove
6422     }
6423
6424     // we either have a choice what to promote to, or (in Shogi) whether to promote
6425     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6426        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6427         *promoChoice = PieceToChar(BlackFerz);  // no choice
6428         return FALSE;
6429     }
6430     // no sense asking what we must promote to if it is going to explode...
6431     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6432         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6433         return FALSE;
6434     }
6435     // give caller the default choice even if we will not make it
6436     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6437     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6438     if(        sweepSelect && gameInfo.variant != VariantGreat
6439                            && gameInfo.variant != VariantGrand
6440                            && gameInfo.variant != VariantSuper) return FALSE;
6441     if(autoQueen) return FALSE; // predetermined
6442
6443     // suppress promotion popup on illegal moves that are not premoves
6444     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6445               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6446     if(appData.testLegality && !premove) {
6447         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6448                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6449         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6450             return FALSE;
6451     }
6452
6453     return TRUE;
6454 }
6455
6456 int
6457 InPalace (int row, int column)
6458 {   /* [HGM] for Xiangqi */
6459     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6460          column < (BOARD_WIDTH + 4)/2 &&
6461          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6462     return FALSE;
6463 }
6464
6465 int
6466 PieceForSquare (int x, int y)
6467 {
6468   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6469      return -1;
6470   else
6471      return boards[currentMove][y][x];
6472 }
6473
6474 int
6475 OKToStartUserMove (int x, int y)
6476 {
6477     ChessSquare from_piece;
6478     int white_piece;
6479
6480     if (matchMode) return FALSE;
6481     if (gameMode == EditPosition) return TRUE;
6482
6483     if (x >= 0 && y >= 0)
6484       from_piece = boards[currentMove][y][x];
6485     else
6486       from_piece = EmptySquare;
6487
6488     if (from_piece == EmptySquare) return FALSE;
6489
6490     white_piece = (int)from_piece >= (int)WhitePawn &&
6491       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6492
6493     switch (gameMode) {
6494       case AnalyzeFile:
6495       case TwoMachinesPlay:
6496       case EndOfGame:
6497         return FALSE;
6498
6499       case IcsObserving:
6500       case IcsIdle:
6501         return FALSE;
6502
6503       case MachinePlaysWhite:
6504       case IcsPlayingBlack:
6505         if (appData.zippyPlay) return FALSE;
6506         if (white_piece) {
6507             DisplayMoveError(_("You are playing Black"));
6508             return FALSE;
6509         }
6510         break;
6511
6512       case MachinePlaysBlack:
6513       case IcsPlayingWhite:
6514         if (appData.zippyPlay) return FALSE;
6515         if (!white_piece) {
6516             DisplayMoveError(_("You are playing White"));
6517             return FALSE;
6518         }
6519         break;
6520
6521       case PlayFromGameFile:
6522             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6523       case EditGame:
6524         if (!white_piece && WhiteOnMove(currentMove)) {
6525             DisplayMoveError(_("It is White's turn"));
6526             return FALSE;
6527         }
6528         if (white_piece && !WhiteOnMove(currentMove)) {
6529             DisplayMoveError(_("It is Black's turn"));
6530             return FALSE;
6531         }
6532         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6533             /* Editing correspondence game history */
6534             /* Could disallow this or prompt for confirmation */
6535             cmailOldMove = -1;
6536         }
6537         break;
6538
6539       case BeginningOfGame:
6540         if (appData.icsActive) return FALSE;
6541         if (!appData.noChessProgram) {
6542             if (!white_piece) {
6543                 DisplayMoveError(_("You are playing White"));
6544                 return FALSE;
6545             }
6546         }
6547         break;
6548
6549       case Training:
6550         if (!white_piece && WhiteOnMove(currentMove)) {
6551             DisplayMoveError(_("It is White's turn"));
6552             return FALSE;
6553         }
6554         if (white_piece && !WhiteOnMove(currentMove)) {
6555             DisplayMoveError(_("It is Black's turn"));
6556             return FALSE;
6557         }
6558         break;
6559
6560       default:
6561       case IcsExamining:
6562         break;
6563     }
6564     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6565         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6566         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6567         && gameMode != AnalyzeFile && gameMode != Training) {
6568         DisplayMoveError(_("Displayed position is not current"));
6569         return FALSE;
6570     }
6571     return TRUE;
6572 }
6573
6574 Boolean
6575 OnlyMove (int *x, int *y, Boolean captures)
6576 {
6577     DisambiguateClosure cl;
6578     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6579     switch(gameMode) {
6580       case MachinePlaysBlack:
6581       case IcsPlayingWhite:
6582       case BeginningOfGame:
6583         if(!WhiteOnMove(currentMove)) return FALSE;
6584         break;
6585       case MachinePlaysWhite:
6586       case IcsPlayingBlack:
6587         if(WhiteOnMove(currentMove)) return FALSE;
6588         break;
6589       case EditGame:
6590         break;
6591       default:
6592         return FALSE;
6593     }
6594     cl.pieceIn = EmptySquare;
6595     cl.rfIn = *y;
6596     cl.ffIn = *x;
6597     cl.rtIn = -1;
6598     cl.ftIn = -1;
6599     cl.promoCharIn = NULLCHAR;
6600     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6601     if( cl.kind == NormalMove ||
6602         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6603         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6604         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6605       fromX = cl.ff;
6606       fromY = cl.rf;
6607       *x = cl.ft;
6608       *y = cl.rt;
6609       return TRUE;
6610     }
6611     if(cl.kind != ImpossibleMove) return FALSE;
6612     cl.pieceIn = EmptySquare;
6613     cl.rfIn = -1;
6614     cl.ffIn = -1;
6615     cl.rtIn = *y;
6616     cl.ftIn = *x;
6617     cl.promoCharIn = NULLCHAR;
6618     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6619     if( cl.kind == NormalMove ||
6620         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6621         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6622         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6623       fromX = cl.ff;
6624       fromY = cl.rf;
6625       *x = cl.ft;
6626       *y = cl.rt;
6627       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6628       return TRUE;
6629     }
6630     return FALSE;
6631 }
6632
6633 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6634 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6635 int lastLoadGameUseList = FALSE;
6636 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6637 ChessMove lastLoadGameStart = EndOfFile;
6638 int doubleClick;
6639
6640 void
6641 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6642 {
6643     ChessMove moveType;
6644     ChessSquare pup;
6645     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6646
6647     /* Check if the user is playing in turn.  This is complicated because we
6648        let the user "pick up" a piece before it is his turn.  So the piece he
6649        tried to pick up may have been captured by the time he puts it down!
6650        Therefore we use the color the user is supposed to be playing in this
6651        test, not the color of the piece that is currently on the starting
6652        square---except in EditGame mode, where the user is playing both
6653        sides; fortunately there the capture race can't happen.  (It can
6654        now happen in IcsExamining mode, but that's just too bad.  The user
6655        will get a somewhat confusing message in that case.)
6656        */
6657
6658     switch (gameMode) {
6659       case AnalyzeFile:
6660       case TwoMachinesPlay:
6661       case EndOfGame:
6662       case IcsObserving:
6663       case IcsIdle:
6664         /* We switched into a game mode where moves are not accepted,
6665            perhaps while the mouse button was down. */
6666         return;
6667
6668       case MachinePlaysWhite:
6669         /* User is moving for Black */
6670         if (WhiteOnMove(currentMove)) {
6671             DisplayMoveError(_("It is White's turn"));
6672             return;
6673         }
6674         break;
6675
6676       case MachinePlaysBlack:
6677         /* User is moving for White */
6678         if (!WhiteOnMove(currentMove)) {
6679             DisplayMoveError(_("It is Black's turn"));
6680             return;
6681         }
6682         break;
6683
6684       case PlayFromGameFile:
6685             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6686       case EditGame:
6687       case IcsExamining:
6688       case BeginningOfGame:
6689       case AnalyzeMode:
6690       case Training:
6691         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6692         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6693             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6694             /* User is moving for Black */
6695             if (WhiteOnMove(currentMove)) {
6696                 DisplayMoveError(_("It is White's turn"));
6697                 return;
6698             }
6699         } else {
6700             /* User is moving for White */
6701             if (!WhiteOnMove(currentMove)) {
6702                 DisplayMoveError(_("It is Black's turn"));
6703                 return;
6704             }
6705         }
6706         break;
6707
6708       case IcsPlayingBlack:
6709         /* User is moving for Black */
6710         if (WhiteOnMove(currentMove)) {
6711             if (!appData.premove) {
6712                 DisplayMoveError(_("It is White's turn"));
6713             } else if (toX >= 0 && toY >= 0) {
6714                 premoveToX = toX;
6715                 premoveToY = toY;
6716                 premoveFromX = fromX;
6717                 premoveFromY = fromY;
6718                 premovePromoChar = promoChar;
6719                 gotPremove = 1;
6720                 if (appData.debugMode)
6721                     fprintf(debugFP, "Got premove: fromX %d,"
6722                             "fromY %d, toX %d, toY %d\n",
6723                             fromX, fromY, toX, toY);
6724             }
6725             return;
6726         }
6727         break;
6728
6729       case IcsPlayingWhite:
6730         /* User is moving for White */
6731         if (!WhiteOnMove(currentMove)) {
6732             if (!appData.premove) {
6733                 DisplayMoveError(_("It is Black's turn"));
6734             } else if (toX >= 0 && toY >= 0) {
6735                 premoveToX = toX;
6736                 premoveToY = toY;
6737                 premoveFromX = fromX;
6738                 premoveFromY = fromY;
6739                 premovePromoChar = promoChar;
6740                 gotPremove = 1;
6741                 if (appData.debugMode)
6742                     fprintf(debugFP, "Got premove: fromX %d,"
6743                             "fromY %d, toX %d, toY %d\n",
6744                             fromX, fromY, toX, toY);
6745             }
6746             return;
6747         }
6748         break;
6749
6750       default:
6751         break;
6752
6753       case EditPosition:
6754         /* EditPosition, empty square, or different color piece;
6755            click-click move is possible */
6756         if (toX == -2 || toY == -2) {
6757             boards[0][fromY][fromX] = EmptySquare;
6758             DrawPosition(FALSE, boards[currentMove]);
6759             return;
6760         } else if (toX >= 0 && toY >= 0) {
6761             boards[0][toY][toX] = boards[0][fromY][fromX];
6762             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6763                 if(boards[0][fromY][0] != EmptySquare) {
6764                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6765                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6766                 }
6767             } else
6768             if(fromX == BOARD_RGHT+1) {
6769                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6770                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6771                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6772                 }
6773             } else
6774             boards[0][fromY][fromX] = gatingPiece;
6775             DrawPosition(FALSE, boards[currentMove]);
6776             return;
6777         }
6778         return;
6779     }
6780
6781     if(toX < 0 || toY < 0) return;
6782     pup = boards[currentMove][toY][toX];
6783
6784     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6785     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6786          if( pup != EmptySquare ) return;
6787          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6788            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6789                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6790            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6791            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6792            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6793            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6794          fromY = DROP_RANK;
6795     }
6796
6797     /* [HGM] always test for legality, to get promotion info */
6798     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6799                                          fromY, fromX, toY, toX, promoChar);
6800
6801     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6802
6803     /* [HGM] but possibly ignore an IllegalMove result */
6804     if (appData.testLegality) {
6805         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6806             DisplayMoveError(_("Illegal move"));
6807             return;
6808         }
6809     }
6810
6811     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6812         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6813              ClearPremoveHighlights(); // was included
6814         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6815         return;
6816     }
6817
6818     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6819 }
6820
6821 /* Common tail of UserMoveEvent and DropMenuEvent */
6822 int
6823 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6824 {
6825     char *bookHit = 0;
6826
6827     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6828         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6829         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6830         if(WhiteOnMove(currentMove)) {
6831             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6832         } else {
6833             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6834         }
6835     }
6836
6837     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6838        move type in caller when we know the move is a legal promotion */
6839     if(moveType == NormalMove && promoChar)
6840         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6841
6842     /* [HGM] <popupFix> The following if has been moved here from
6843        UserMoveEvent(). Because it seemed to belong here (why not allow
6844        piece drops in training games?), and because it can only be
6845        performed after it is known to what we promote. */
6846     if (gameMode == Training) {
6847       /* compare the move played on the board to the next move in the
6848        * game. If they match, display the move and the opponent's response.
6849        * If they don't match, display an error message.
6850        */
6851       int saveAnimate;
6852       Board testBoard;
6853       CopyBoard(testBoard, boards[currentMove]);
6854       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6855
6856       if (CompareBoards(testBoard, boards[currentMove+1])) {
6857         ForwardInner(currentMove+1);
6858
6859         /* Autoplay the opponent's response.
6860          * if appData.animate was TRUE when Training mode was entered,
6861          * the response will be animated.
6862          */
6863         saveAnimate = appData.animate;
6864         appData.animate = animateTraining;
6865         ForwardInner(currentMove+1);
6866         appData.animate = saveAnimate;
6867
6868         /* check for the end of the game */
6869         if (currentMove >= forwardMostMove) {
6870           gameMode = PlayFromGameFile;
6871           ModeHighlight();
6872           SetTrainingModeOff();
6873           DisplayInformation(_("End of game"));
6874         }
6875       } else {
6876         DisplayError(_("Incorrect move"), 0);
6877       }
6878       return 1;
6879     }
6880
6881   /* Ok, now we know that the move is good, so we can kill
6882      the previous line in Analysis Mode */
6883   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6884                                 && currentMove < forwardMostMove) {
6885     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6886     else forwardMostMove = currentMove;
6887   }
6888
6889   ClearMap();
6890
6891   /* If we need the chess program but it's dead, restart it */
6892   ResurrectChessProgram();
6893
6894   /* A user move restarts a paused game*/
6895   if (pausing)
6896     PauseEvent();
6897
6898   thinkOutput[0] = NULLCHAR;
6899
6900   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6901
6902   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6903     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6904     return 1;
6905   }
6906
6907   if (gameMode == BeginningOfGame) {
6908     if (appData.noChessProgram) {
6909       gameMode = EditGame;
6910       SetGameInfo();
6911     } else {
6912       char buf[MSG_SIZ];
6913       gameMode = MachinePlaysBlack;
6914       StartClocks();
6915       SetGameInfo();
6916       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6917       DisplayTitle(buf);
6918       if (first.sendName) {
6919         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6920         SendToProgram(buf, &first);
6921       }
6922       StartClocks();
6923     }
6924     ModeHighlight();
6925   }
6926
6927   /* Relay move to ICS or chess engine */
6928   if (appData.icsActive) {
6929     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6930         gameMode == IcsExamining) {
6931       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6932         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6933         SendToICS("draw ");
6934         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6935       }
6936       // also send plain move, in case ICS does not understand atomic claims
6937       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6938       ics_user_moved = 1;
6939     }
6940   } else {
6941     if (first.sendTime && (gameMode == BeginningOfGame ||
6942                            gameMode == MachinePlaysWhite ||
6943                            gameMode == MachinePlaysBlack)) {
6944       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6945     }
6946     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6947          // [HGM] book: if program might be playing, let it use book
6948         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6949         first.maybeThinking = TRUE;
6950     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6951         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6952         SendBoard(&first, currentMove+1);
6953         if(second.analyzing) {
6954             if(!second.useSetboard) SendToProgram("undo\n", &second);
6955             SendBoard(&second, currentMove+1);
6956         }
6957     } else {
6958         SendMoveToProgram(forwardMostMove-1, &first);
6959         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6960     }
6961     if (currentMove == cmailOldMove + 1) {
6962       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6963     }
6964   }
6965
6966   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6967
6968   switch (gameMode) {
6969   case EditGame:
6970     if(appData.testLegality)
6971     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6972     case MT_NONE:
6973     case MT_CHECK:
6974       break;
6975     case MT_CHECKMATE:
6976     case MT_STAINMATE:
6977       if (WhiteOnMove(currentMove)) {
6978         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6979       } else {
6980         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6981       }
6982       break;
6983     case MT_STALEMATE:
6984       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6985       break;
6986     }
6987     break;
6988
6989   case MachinePlaysBlack:
6990   case MachinePlaysWhite:
6991     /* disable certain menu options while machine is thinking */
6992     SetMachineThinkingEnables();
6993     break;
6994
6995   default:
6996     break;
6997   }
6998
6999   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7000   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7001
7002   if(bookHit) { // [HGM] book: simulate book reply
7003         static char bookMove[MSG_SIZ]; // a bit generous?
7004
7005         programStats.nodes = programStats.depth = programStats.time =
7006         programStats.score = programStats.got_only_move = 0;
7007         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7008
7009         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7010         strcat(bookMove, bookHit);
7011         HandleMachineMove(bookMove, &first);
7012   }
7013   return 1;
7014 }
7015
7016 void
7017 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7018 {
7019     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7020     Markers *m = (Markers *) closure;
7021     if(rf == fromY && ff == fromX)
7022         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7023                          || kind == WhiteCapturesEnPassant
7024                          || kind == BlackCapturesEnPassant);
7025     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7026 }
7027
7028 void
7029 MarkTargetSquares (int clear)
7030 {
7031   int x, y;
7032   if(clear) // no reason to ever suppress clearing
7033     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
7034   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7035      !appData.testLegality || gameMode == EditPosition) return;
7036   if(!clear) {
7037     int capt = 0;
7038     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7039     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7040       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7041       if(capt)
7042       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7043     }
7044   }
7045   DrawPosition(FALSE, NULL);
7046 }
7047
7048 int
7049 Explode (Board board, int fromX, int fromY, int toX, int toY)
7050 {
7051     if(gameInfo.variant == VariantAtomic &&
7052        (board[toY][toX] != EmptySquare ||                     // capture?
7053         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7054                          board[fromY][fromX] == BlackPawn   )
7055       )) {
7056         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7057         return TRUE;
7058     }
7059     return FALSE;
7060 }
7061
7062 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7063
7064 int
7065 CanPromote (ChessSquare piece, int y)
7066 {
7067         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7068         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7069         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7070            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7071            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7072          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7073         return (piece == BlackPawn && y == 1 ||
7074                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7075                 piece == BlackLance && y == 1 ||
7076                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7077 }
7078
7079 void
7080 LeftClick (ClickType clickType, int xPix, int yPix)
7081 {
7082     int x, y;
7083     Boolean saveAnimate;
7084     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7085     char promoChoice = NULLCHAR;
7086     ChessSquare piece;
7087     static TimeMark lastClickTime, prevClickTime;
7088
7089     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7090
7091     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7092
7093     if (clickType == Press) ErrorPopDown();
7094
7095     x = EventToSquare(xPix, BOARD_WIDTH);
7096     y = EventToSquare(yPix, BOARD_HEIGHT);
7097     if (!flipView && y >= 0) {
7098         y = BOARD_HEIGHT - 1 - y;
7099     }
7100     if (flipView && x >= 0) {
7101         x = BOARD_WIDTH - 1 - x;
7102     }
7103
7104     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7105         defaultPromoChoice = promoSweep;
7106         promoSweep = EmptySquare;   // terminate sweep
7107         promoDefaultAltered = TRUE;
7108         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7109     }
7110
7111     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7112         if(clickType == Release) return; // ignore upclick of click-click destination
7113         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7114         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7115         if(gameInfo.holdingsWidth &&
7116                 (WhiteOnMove(currentMove)
7117                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7118                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7119             // click in right holdings, for determining promotion piece
7120             ChessSquare p = boards[currentMove][y][x];
7121             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7122             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7123             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7124                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7125                 fromX = fromY = -1;
7126                 return;
7127             }
7128         }
7129         DrawPosition(FALSE, boards[currentMove]);
7130         return;
7131     }
7132
7133     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7134     if(clickType == Press
7135             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7136               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7137               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7138         return;
7139
7140     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7141         // could be static click on premove from-square: abort premove
7142         gotPremove = 0;
7143         ClearPremoveHighlights();
7144     }
7145
7146     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7147         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7148
7149     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7150         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7151                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7152         defaultPromoChoice = DefaultPromoChoice(side);
7153     }
7154
7155     autoQueen = appData.alwaysPromoteToQueen;
7156
7157     if (fromX == -1) {
7158       int originalY = y;
7159       gatingPiece = EmptySquare;
7160       if (clickType != Press) {
7161         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7162             DragPieceEnd(xPix, yPix); dragging = 0;
7163             DrawPosition(FALSE, NULL);
7164         }
7165         return;
7166       }
7167       doubleClick = FALSE;
7168       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7169         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7170       }
7171       fromX = x; fromY = y; toX = toY = -1;
7172       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7173          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7174          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7175             /* First square */
7176             if (OKToStartUserMove(fromX, fromY)) {
7177                 second = 0;
7178                 MarkTargetSquares(0);
7179                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7180                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7181                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7182                     promoSweep = defaultPromoChoice;
7183                     selectFlag = 0; lastX = xPix; lastY = yPix;
7184                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7185                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7186                 }
7187                 if (appData.highlightDragging) {
7188                     SetHighlights(fromX, fromY, -1, -1);
7189                 } else {
7190                     ClearHighlights();
7191                 }
7192             } else fromX = fromY = -1;
7193             return;
7194         }
7195     }
7196
7197     /* fromX != -1 */
7198     if (clickType == Press && gameMode != EditPosition) {
7199         ChessSquare fromP;
7200         ChessSquare toP;
7201         int frc;
7202
7203         // ignore off-board to clicks
7204         if(y < 0 || x < 0) return;
7205
7206         /* Check if clicking again on the same color piece */
7207         fromP = boards[currentMove][fromY][fromX];
7208         toP = boards[currentMove][y][x];
7209         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7210         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7211              WhitePawn <= toP && toP <= WhiteKing &&
7212              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7213              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7214             (BlackPawn <= fromP && fromP <= BlackKing &&
7215              BlackPawn <= toP && toP <= BlackKing &&
7216              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7217              !(fromP == BlackKing && toP == BlackRook && frc))) {
7218             /* Clicked again on same color piece -- changed his mind */
7219             second = (x == fromX && y == fromY);
7220             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7221                 second = FALSE; // first double-click rather than scond click
7222                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7223             }
7224             promoDefaultAltered = FALSE;
7225             MarkTargetSquares(1);
7226            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7227             if (appData.highlightDragging) {
7228                 SetHighlights(x, y, -1, -1);
7229             } else {
7230                 ClearHighlights();
7231             }
7232             if (OKToStartUserMove(x, y)) {
7233                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7234                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7235                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7236                  gatingPiece = boards[currentMove][fromY][fromX];
7237                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7238                 fromX = x;
7239                 fromY = y; dragging = 1;
7240                 MarkTargetSquares(0);
7241                 DragPieceBegin(xPix, yPix, FALSE);
7242                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7243                     promoSweep = defaultPromoChoice;
7244                     selectFlag = 0; lastX = xPix; lastY = yPix;
7245                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7246                 }
7247             }
7248            }
7249            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7250            second = FALSE;
7251         }
7252         // ignore clicks on holdings
7253         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7254     }
7255
7256     if (clickType == Release && x == fromX && y == fromY) {
7257         DragPieceEnd(xPix, yPix); dragging = 0;
7258         if(clearFlag) {
7259             // a deferred attempt to click-click move an empty square on top of a piece
7260             boards[currentMove][y][x] = EmptySquare;
7261             ClearHighlights();
7262             DrawPosition(FALSE, boards[currentMove]);
7263             fromX = fromY = -1; clearFlag = 0;
7264             return;
7265         }
7266         if (appData.animateDragging) {
7267             /* Undo animation damage if any */
7268             DrawPosition(FALSE, NULL);
7269         }
7270         if (second || sweepSelecting) {
7271             /* Second up/down in same square; just abort move */
7272             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7273             second = sweepSelecting = 0;
7274             fromX = fromY = -1;
7275             gatingPiece = EmptySquare;
7276             MarkTargetSquares(1);
7277             ClearHighlights();
7278             gotPremove = 0;
7279             ClearPremoveHighlights();
7280         } else {
7281             /* First upclick in same square; start click-click mode */
7282             SetHighlights(x, y, -1, -1);
7283         }
7284         return;
7285     }
7286
7287     clearFlag = 0;
7288
7289     /* we now have a different from- and (possibly off-board) to-square */
7290     /* Completed move */
7291     if(!sweepSelecting) {
7292         toX = x;
7293         toY = y;
7294     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7295
7296     saveAnimate = appData.animate;
7297     if (clickType == Press) {
7298         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7299             // must be Edit Position mode with empty-square selected
7300             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7301             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7302             return;
7303         }
7304         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7305           if(appData.sweepSelect) {
7306             ChessSquare piece = boards[currentMove][fromY][fromX];
7307             promoSweep = defaultPromoChoice;
7308             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7309             selectFlag = 0; lastX = xPix; lastY = yPix;
7310             Sweep(0); // Pawn that is going to promote: preview promotion piece
7311             sweepSelecting = 1;
7312             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7313             MarkTargetSquares(1);
7314           }
7315           return; // promo popup appears on up-click
7316         }
7317         /* Finish clickclick move */
7318         if (appData.animate || appData.highlightLastMove) {
7319             SetHighlights(fromX, fromY, toX, toY);
7320         } else {
7321             ClearHighlights();
7322         }
7323     } else {
7324 #if 0
7325 // [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
7326         /* Finish drag move */
7327         if (appData.highlightLastMove) {
7328             SetHighlights(fromX, fromY, toX, toY);
7329         } else {
7330             ClearHighlights();
7331         }
7332 #endif
7333         DragPieceEnd(xPix, yPix); dragging = 0;
7334         /* Don't animate move and drag both */
7335         appData.animate = FALSE;
7336     }
7337
7338     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7339     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7340         ChessSquare piece = boards[currentMove][fromY][fromX];
7341         if(gameMode == EditPosition && piece != EmptySquare &&
7342            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7343             int n;
7344
7345             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7346                 n = PieceToNumber(piece - (int)BlackPawn);
7347                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7348                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7349                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7350             } else
7351             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7352                 n = PieceToNumber(piece);
7353                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7354                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7355                 boards[currentMove][n][BOARD_WIDTH-2]++;
7356             }
7357             boards[currentMove][fromY][fromX] = EmptySquare;
7358         }
7359         ClearHighlights();
7360         fromX = fromY = -1;
7361         MarkTargetSquares(1);
7362         DrawPosition(TRUE, boards[currentMove]);
7363         return;
7364     }
7365
7366     // off-board moves should not be highlighted
7367     if(x < 0 || y < 0) ClearHighlights();
7368
7369     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7370
7371     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7372         SetHighlights(fromX, fromY, toX, toY);
7373         MarkTargetSquares(1);
7374         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7375             // [HGM] super: promotion to captured piece selected from holdings
7376             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7377             promotionChoice = TRUE;
7378             // kludge follows to temporarily execute move on display, without promoting yet
7379             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7380             boards[currentMove][toY][toX] = p;
7381             DrawPosition(FALSE, boards[currentMove]);
7382             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7383             boards[currentMove][toY][toX] = q;
7384             DisplayMessage("Click in holdings to choose piece", "");
7385             return;
7386         }
7387         PromotionPopUp();
7388     } else {
7389         int oldMove = currentMove;
7390         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7391         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7392         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7393         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7394            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7395             DrawPosition(TRUE, boards[currentMove]);
7396         MarkTargetSquares(1);
7397         fromX = fromY = -1;
7398     }
7399     appData.animate = saveAnimate;
7400     if (appData.animate || appData.animateDragging) {
7401         /* Undo animation damage if needed */
7402         DrawPosition(FALSE, NULL);
7403     }
7404 }
7405
7406 int
7407 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7408 {   // front-end-free part taken out of PieceMenuPopup
7409     int whichMenu; int xSqr, ySqr;
7410
7411     if(seekGraphUp) { // [HGM] seekgraph
7412         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7413         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7414         return -2;
7415     }
7416
7417     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7418          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7419         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7420         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7421         if(action == Press)   {
7422             originalFlip = flipView;
7423             flipView = !flipView; // temporarily flip board to see game from partners perspective
7424             DrawPosition(TRUE, partnerBoard);
7425             DisplayMessage(partnerStatus, "");
7426             partnerUp = TRUE;
7427         } else if(action == Release) {
7428             flipView = originalFlip;
7429             DrawPosition(TRUE, boards[currentMove]);
7430             partnerUp = FALSE;
7431         }
7432         return -2;
7433     }
7434
7435     xSqr = EventToSquare(x, BOARD_WIDTH);
7436     ySqr = EventToSquare(y, BOARD_HEIGHT);
7437     if (action == Release) {
7438         if(pieceSweep != EmptySquare) {
7439             EditPositionMenuEvent(pieceSweep, toX, toY);
7440             pieceSweep = EmptySquare;
7441         } else UnLoadPV(); // [HGM] pv
7442     }
7443     if (action != Press) return -2; // return code to be ignored
7444     switch (gameMode) {
7445       case IcsExamining:
7446         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7447       case EditPosition:
7448         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7449         if (xSqr < 0 || ySqr < 0) return -1;
7450         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7451         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7452         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7453         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7454         NextPiece(0);
7455         return 2; // grab
7456       case IcsObserving:
7457         if(!appData.icsEngineAnalyze) return -1;
7458       case IcsPlayingWhite:
7459       case IcsPlayingBlack:
7460         if(!appData.zippyPlay) goto noZip;
7461       case AnalyzeMode:
7462       case AnalyzeFile:
7463       case MachinePlaysWhite:
7464       case MachinePlaysBlack:
7465       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7466         if (!appData.dropMenu) {
7467           LoadPV(x, y);
7468           return 2; // flag front-end to grab mouse events
7469         }
7470         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7471            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7472       case EditGame:
7473       noZip:
7474         if (xSqr < 0 || ySqr < 0) return -1;
7475         if (!appData.dropMenu || appData.testLegality &&
7476             gameInfo.variant != VariantBughouse &&
7477             gameInfo.variant != VariantCrazyhouse) return -1;
7478         whichMenu = 1; // drop menu
7479         break;
7480       default:
7481         return -1;
7482     }
7483
7484     if (((*fromX = xSqr) < 0) ||
7485         ((*fromY = ySqr) < 0)) {
7486         *fromX = *fromY = -1;
7487         return -1;
7488     }
7489     if (flipView)
7490       *fromX = BOARD_WIDTH - 1 - *fromX;
7491     else
7492       *fromY = BOARD_HEIGHT - 1 - *fromY;
7493
7494     return whichMenu;
7495 }
7496
7497 void
7498 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7499 {
7500 //    char * hint = lastHint;
7501     FrontEndProgramStats stats;
7502
7503     stats.which = cps == &first ? 0 : 1;
7504     stats.depth = cpstats->depth;
7505     stats.nodes = cpstats->nodes;
7506     stats.score = cpstats->score;
7507     stats.time = cpstats->time;
7508     stats.pv = cpstats->movelist;
7509     stats.hint = lastHint;
7510     stats.an_move_index = 0;
7511     stats.an_move_count = 0;
7512
7513     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7514         stats.hint = cpstats->move_name;
7515         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7516         stats.an_move_count = cpstats->nr_moves;
7517     }
7518
7519     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
7520
7521     SetProgramStats( &stats );
7522 }
7523
7524 void
7525 ClearEngineOutputPane (int which)
7526 {
7527     static FrontEndProgramStats dummyStats;
7528     dummyStats.which = which;
7529     dummyStats.pv = "#";
7530     SetProgramStats( &dummyStats );
7531 }
7532
7533 #define MAXPLAYERS 500
7534
7535 char *
7536 TourneyStandings (int display)
7537 {
7538     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7539     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7540     char result, *p, *names[MAXPLAYERS];
7541
7542     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7543         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7544     names[0] = p = strdup(appData.participants);
7545     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7546
7547     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7548
7549     while(result = appData.results[nr]) {
7550         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7551         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7552         wScore = bScore = 0;
7553         switch(result) {
7554           case '+': wScore = 2; break;
7555           case '-': bScore = 2; break;
7556           case '=': wScore = bScore = 1; break;
7557           case ' ':
7558           case '*': return strdup("busy"); // tourney not finished
7559         }
7560         score[w] += wScore;
7561         score[b] += bScore;
7562         games[w]++;
7563         games[b]++;
7564         nr++;
7565     }
7566     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7567     for(w=0; w<nPlayers; w++) {
7568         bScore = -1;
7569         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7570         ranking[w] = b; points[w] = bScore; score[b] = -2;
7571     }
7572     p = malloc(nPlayers*34+1);
7573     for(w=0; w<nPlayers && w<display; w++)
7574         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7575     free(names[0]);
7576     return p;
7577 }
7578
7579 void
7580 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7581 {       // count all piece types
7582         int p, f, r;
7583         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7584         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7585         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7586                 p = board[r][f];
7587                 pCnt[p]++;
7588                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7589                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7590                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7591                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7592                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7593                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7594         }
7595 }
7596
7597 int
7598 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7599 {
7600         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7601         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7602
7603         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7604         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7605         if(myPawns == 2 && nMine == 3) // KPP
7606             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7607         if(myPawns == 1 && nMine == 2) // KP
7608             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7609         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7610             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7611         if(myPawns) return FALSE;
7612         if(pCnt[WhiteRook+side])
7613             return pCnt[BlackRook-side] ||
7614                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7615                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7616                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7617         if(pCnt[WhiteCannon+side]) {
7618             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7619             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7620         }
7621         if(pCnt[WhiteKnight+side])
7622             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7623         return FALSE;
7624 }
7625
7626 int
7627 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7628 {
7629         VariantClass v = gameInfo.variant;
7630
7631         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7632         if(v == VariantShatranj) return TRUE; // always winnable through baring
7633         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7634         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7635
7636         if(v == VariantXiangqi) {
7637                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7638
7639                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7640                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7641                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7642                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7643                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7644                 if(stale) // we have at least one last-rank P plus perhaps C
7645                     return majors // KPKX
7646                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7647                 else // KCA*E*
7648                     return pCnt[WhiteFerz+side] // KCAK
7649                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7650                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7651                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7652
7653         } else if(v == VariantKnightmate) {
7654                 if(nMine == 1) return FALSE;
7655                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7656         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7657                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7658
7659                 if(nMine == 1) return FALSE; // bare King
7660                 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
7661                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7662                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7663                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7664                 if(pCnt[WhiteKnight+side])
7665                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7666                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7667                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7668                 if(nBishops)
7669                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7670                 if(pCnt[WhiteAlfil+side])
7671                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7672                 if(pCnt[WhiteWazir+side])
7673                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7674         }
7675
7676         return TRUE;
7677 }
7678
7679 int
7680 CompareWithRights (Board b1, Board b2)
7681 {
7682     int rights = 0;
7683     if(!CompareBoards(b1, b2)) return FALSE;
7684     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7685     /* compare castling rights */
7686     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7687            rights++; /* King lost rights, while rook still had them */
7688     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7689         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7690            rights++; /* but at least one rook lost them */
7691     }
7692     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7693            rights++;
7694     if( b1[CASTLING][5] != NoRights ) {
7695         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7696            rights++;
7697     }
7698     return rights == 0;
7699 }
7700
7701 int
7702 Adjudicate (ChessProgramState *cps)
7703 {       // [HGM] some adjudications useful with buggy engines
7704         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7705         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7706         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7707         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7708         int k, drop, count = 0; static int bare = 1;
7709         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7710         Boolean canAdjudicate = !appData.icsActive;
7711
7712         // most tests only when we understand the game, i.e. legality-checking on
7713             if( appData.testLegality )
7714             {   /* [HGM] Some more adjudications for obstinate engines */
7715                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7716                 static int moveCount = 6;
7717                 ChessMove result;
7718                 char *reason = NULL;
7719
7720                 /* Count what is on board. */
7721                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7722
7723                 /* Some material-based adjudications that have to be made before stalemate test */
7724                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7725                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7726                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7727                      if(canAdjudicate && appData.checkMates) {
7728                          if(engineOpponent)
7729                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7730                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7731                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7732                          return 1;
7733                      }
7734                 }
7735
7736                 /* Bare King in Shatranj (loses) or Losers (wins) */
7737                 if( nrW == 1 || nrB == 1) {
7738                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7739                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7740                      if(canAdjudicate && appData.checkMates) {
7741                          if(engineOpponent)
7742                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7743                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7744                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7745                          return 1;
7746                      }
7747                   } else
7748                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7749                   {    /* bare King */
7750                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7751                         if(canAdjudicate && appData.checkMates) {
7752                             /* but only adjudicate if adjudication enabled */
7753                             if(engineOpponent)
7754                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7755                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7756                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7757                             return 1;
7758                         }
7759                   }
7760                 } else bare = 1;
7761
7762
7763             // don't wait for engine to announce game end if we can judge ourselves
7764             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7765               case MT_CHECK:
7766                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7767                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7768                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7769                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7770                             checkCnt++;
7771                         if(checkCnt >= 2) {
7772                             reason = "Xboard adjudication: 3rd check";
7773                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7774                             break;
7775                         }
7776                     }
7777                 }
7778               case MT_NONE:
7779               default:
7780                 break;
7781               case MT_STALEMATE:
7782               case MT_STAINMATE:
7783                 reason = "Xboard adjudication: Stalemate";
7784                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7785                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7786                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7787                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7788                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7789                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7790                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7791                                                                         EP_CHECKMATE : EP_WINS);
7792                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7793                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7794                 }
7795                 break;
7796               case MT_CHECKMATE:
7797                 reason = "Xboard adjudication: Checkmate";
7798                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7799                 if(gameInfo.variant == VariantShogi) {
7800                     if(forwardMostMove > backwardMostMove
7801                        && moveList[forwardMostMove-1][1] == '@'
7802                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7803                         reason = "XBoard adjudication: pawn-drop mate";
7804                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7805                     }
7806                 }
7807                 break;
7808             }
7809
7810                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7811                     case EP_STALEMATE:
7812                         result = GameIsDrawn; break;
7813                     case EP_CHECKMATE:
7814                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7815                     case EP_WINS:
7816                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7817                     default:
7818                         result = EndOfFile;
7819                 }
7820                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7821                     if(engineOpponent)
7822                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7823                     GameEnds( result, reason, GE_XBOARD );
7824                     return 1;
7825                 }
7826
7827                 /* Next absolutely insufficient mating material. */
7828                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7829                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7830                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7831
7832                      /* always flag draws, for judging claims */
7833                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7834
7835                      if(canAdjudicate && appData.materialDraws) {
7836                          /* but only adjudicate them if adjudication enabled */
7837                          if(engineOpponent) {
7838                            SendToProgram("force\n", engineOpponent); // suppress reply
7839                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7840                          }
7841                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7842                          return 1;
7843                      }
7844                 }
7845
7846                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7847                 if(gameInfo.variant == VariantXiangqi ?
7848                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7849                  : nrW + nrB == 4 &&
7850                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7851                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7852                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7853                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7854                    ) ) {
7855                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7856                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7857                           if(engineOpponent) {
7858                             SendToProgram("force\n", engineOpponent); // suppress reply
7859                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7860                           }
7861                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7862                           return 1;
7863                      }
7864                 } else moveCount = 6;
7865             }
7866
7867         // Repetition draws and 50-move rule can be applied independently of legality testing
7868
7869                 /* Check for rep-draws */
7870                 count = 0;
7871                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7872                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7873                 for(k = forwardMostMove-2;
7874                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7875                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7876                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7877                     k-=2)
7878                 {   int rights=0;
7879                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7880                         /* compare castling rights */
7881                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7882                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7883                                 rights++; /* King lost rights, while rook still had them */
7884                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7885                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7886                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7887                                    rights++; /* but at least one rook lost them */
7888                         }
7889                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7890                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7891                                 rights++;
7892                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7893                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7894                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7895                                    rights++;
7896                         }
7897                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7898                             && appData.drawRepeats > 1) {
7899                              /* adjudicate after user-specified nr of repeats */
7900                              int result = GameIsDrawn;
7901                              char *details = "XBoard adjudication: repetition draw";
7902                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7903                                 // [HGM] xiangqi: check for forbidden perpetuals
7904                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7905                                 for(m=forwardMostMove; m>k; m-=2) {
7906                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7907                                         ourPerpetual = 0; // the current mover did not always check
7908                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7909                                         hisPerpetual = 0; // the opponent did not always check
7910                                 }
7911                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7912                                                                         ourPerpetual, hisPerpetual);
7913                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7914                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7915                                     details = "Xboard adjudication: perpetual checking";
7916                                 } else
7917                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7918                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7919                                 } else
7920                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
7921                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
7922                                         result = BlackWins;
7923                                         details = "Xboard adjudication: repetition";
7924                                     }
7925                                 } else // it must be XQ
7926                                 // Now check for perpetual chases
7927                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7928                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7929                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7930                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7931                                         static char resdet[MSG_SIZ];
7932                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7933                                         details = resdet;
7934                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7935                                     } else
7936                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7937                                         break; // Abort repetition-checking loop.
7938                                 }
7939                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7940                              }
7941                              if(engineOpponent) {
7942                                SendToProgram("force\n", engineOpponent); // suppress reply
7943                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7944                              }
7945                              GameEnds( result, details, GE_XBOARD );
7946                              return 1;
7947                         }
7948                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7949                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7950                     }
7951                 }
7952
7953                 /* Now we test for 50-move draws. Determine ply count */
7954                 count = forwardMostMove;
7955                 /* look for last irreversble move */
7956                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7957                     count--;
7958                 /* if we hit starting position, add initial plies */
7959                 if( count == backwardMostMove )
7960                     count -= initialRulePlies;
7961                 count = forwardMostMove - count;
7962                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7963                         // adjust reversible move counter for checks in Xiangqi
7964                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7965                         if(i < backwardMostMove) i = backwardMostMove;
7966                         while(i <= forwardMostMove) {
7967                                 lastCheck = inCheck; // check evasion does not count
7968                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7969                                 if(inCheck || lastCheck) count--; // check does not count
7970                                 i++;
7971                         }
7972                 }
7973                 if( count >= 100)
7974                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7975                          /* this is used to judge if draw claims are legal */
7976                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
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( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7982                          return 1;
7983                 }
7984
7985                 /* if draw offer is pending, treat it as a draw claim
7986                  * when draw condition present, to allow engines a way to
7987                  * claim draws before making their move to avoid a race
7988                  * condition occurring after their move
7989                  */
7990                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7991                          char *p = NULL;
7992                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7993                              p = "Draw claim: 50-move rule";
7994                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7995                              p = "Draw claim: 3-fold repetition";
7996                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7997                              p = "Draw claim: insufficient mating material";
7998                          if( p != NULL && canAdjudicate) {
7999                              if(engineOpponent) {
8000                                SendToProgram("force\n", engineOpponent); // suppress reply
8001                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8002                              }
8003                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8004                              return 1;
8005                          }
8006                 }
8007
8008                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8009                     if(engineOpponent) {
8010                       SendToProgram("force\n", engineOpponent); // suppress reply
8011                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8012                     }
8013                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8014                     return 1;
8015                 }
8016         return 0;
8017 }
8018
8019 char *
8020 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8021 {   // [HGM] book: this routine intercepts moves to simulate book replies
8022     char *bookHit = NULL;
8023
8024     //first determine if the incoming move brings opponent into his book
8025     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8026         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8027     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8028     if(bookHit != NULL && !cps->bookSuspend) {
8029         // make sure opponent is not going to reply after receiving move to book position
8030         SendToProgram("force\n", cps);
8031         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8032     }
8033     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8034     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8035     // now arrange restart after book miss
8036     if(bookHit) {
8037         // after a book hit we never send 'go', and the code after the call to this routine
8038         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8039         char buf[MSG_SIZ], *move = bookHit;
8040         if(cps->useSAN) {
8041             int fromX, fromY, toX, toY;
8042             char promoChar;
8043             ChessMove moveType;
8044             move = buf + 30;
8045             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8046                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8047                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8048                                     PosFlags(forwardMostMove),
8049                                     fromY, fromX, toY, toX, promoChar, move);
8050             } else {
8051                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8052                 bookHit = NULL;
8053             }
8054         }
8055         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8056         SendToProgram(buf, cps);
8057         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8058     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8059         SendToProgram("go\n", cps);
8060         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8061     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8062         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8063             SendToProgram("go\n", cps);
8064         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8065     }
8066     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8067 }
8068
8069 int
8070 LoadError (char *errmess, ChessProgramState *cps)
8071 {   // unloads engine and switches back to -ncp mode if it was first
8072     if(cps->initDone) return FALSE;
8073     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8074     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8075     cps->pr = NoProc;
8076     if(cps == &first) {
8077         appData.noChessProgram = TRUE;
8078         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8079         gameMode = BeginningOfGame; ModeHighlight();
8080         SetNCPMode();
8081     }
8082     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8083     DisplayMessage("", ""); // erase waiting message
8084     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8085     return TRUE;
8086 }
8087
8088 char *savedMessage;
8089 ChessProgramState *savedState;
8090 void
8091 DeferredBookMove (void)
8092 {
8093         if(savedState->lastPing != savedState->lastPong)
8094                     ScheduleDelayedEvent(DeferredBookMove, 10);
8095         else
8096         HandleMachineMove(savedMessage, savedState);
8097 }
8098
8099 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8100 static ChessProgramState *stalledEngine;
8101 static char stashedInputMove[MSG_SIZ];
8102
8103 void
8104 HandleMachineMove (char *message, ChessProgramState *cps)
8105 {
8106     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8107     char realname[MSG_SIZ];
8108     int fromX, fromY, toX, toY;
8109     ChessMove moveType;
8110     char promoChar;
8111     char *p, *pv=buf1;
8112     int machineWhite, oldError;
8113     char *bookHit;
8114
8115     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8116         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8117         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8118             DisplayError(_("Invalid pairing from pairing engine"), 0);
8119             return;
8120         }
8121         pairingReceived = 1;
8122         NextMatchGame();
8123         return; // Skim the pairing messages here.
8124     }
8125
8126     oldError = cps->userError; cps->userError = 0;
8127
8128 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8129     /*
8130      * Kludge to ignore BEL characters
8131      */
8132     while (*message == '\007') message++;
8133
8134     /*
8135      * [HGM] engine debug message: ignore lines starting with '#' character
8136      */
8137     if(cps->debug && *message == '#') return;
8138
8139     /*
8140      * Look for book output
8141      */
8142     if (cps == &first && bookRequested) {
8143         if (message[0] == '\t' || message[0] == ' ') {
8144             /* Part of the book output is here; append it */
8145             strcat(bookOutput, message);
8146             strcat(bookOutput, "  \n");
8147             return;
8148         } else if (bookOutput[0] != NULLCHAR) {
8149             /* All of book output has arrived; display it */
8150             char *p = bookOutput;
8151             while (*p != NULLCHAR) {
8152                 if (*p == '\t') *p = ' ';
8153                 p++;
8154             }
8155             DisplayInformation(bookOutput);
8156             bookRequested = FALSE;
8157             /* Fall through to parse the current output */
8158         }
8159     }
8160
8161     /*
8162      * Look for machine move.
8163      */
8164     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8165         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8166     {
8167         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8168             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8169             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8170             stalledEngine = cps;
8171             if(appData.ponderNextMove) { // bring opponent out of ponder
8172                 if(gameMode == TwoMachinesPlay) {
8173                     if(cps->other->pause)
8174                         PauseEngine(cps->other);
8175                     else
8176                         SendToProgram("easy\n", cps->other);
8177                 }
8178             }
8179             StopClocks();
8180             return;
8181         }
8182
8183         /* This method is only useful on engines that support ping */
8184         if (cps->lastPing != cps->lastPong) {
8185           if (gameMode == BeginningOfGame) {
8186             /* Extra move from before last new; ignore */
8187             if (appData.debugMode) {
8188                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8189             }
8190           } else {
8191             if (appData.debugMode) {
8192                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8193                         cps->which, gameMode);
8194             }
8195
8196             SendToProgram("undo\n", cps);
8197           }
8198           return;
8199         }
8200
8201         switch (gameMode) {
8202           case BeginningOfGame:
8203             /* Extra move from before last reset; ignore */
8204             if (appData.debugMode) {
8205                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8206             }
8207             return;
8208
8209           case EndOfGame:
8210           case IcsIdle:
8211           default:
8212             /* Extra move after we tried to stop.  The mode test is
8213                not a reliable way of detecting this problem, but it's
8214                the best we can do on engines that don't support ping.
8215             */
8216             if (appData.debugMode) {
8217                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8218                         cps->which, gameMode);
8219             }
8220             SendToProgram("undo\n", cps);
8221             return;
8222
8223           case MachinePlaysWhite:
8224           case IcsPlayingWhite:
8225             machineWhite = TRUE;
8226             break;
8227
8228           case MachinePlaysBlack:
8229           case IcsPlayingBlack:
8230             machineWhite = FALSE;
8231             break;
8232
8233           case TwoMachinesPlay:
8234             machineWhite = (cps->twoMachinesColor[0] == 'w');
8235             break;
8236         }
8237         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8238             if (appData.debugMode) {
8239                 fprintf(debugFP,
8240                         "Ignoring move out of turn by %s, gameMode %d"
8241                         ", forwardMost %d\n",
8242                         cps->which, gameMode, forwardMostMove);
8243             }
8244             return;
8245         }
8246
8247         if(cps->alphaRank) AlphaRank(machineMove, 4);
8248         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8249                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8250             /* Machine move could not be parsed; ignore it. */
8251           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8252                     machineMove, _(cps->which));
8253             DisplayMoveError(buf1);
8254             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8255                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8256             if (gameMode == TwoMachinesPlay) {
8257               GameEnds(machineWhite ? BlackWins : WhiteWins,
8258                        buf1, GE_XBOARD);
8259             }
8260             return;
8261         }
8262
8263         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8264         /* So we have to redo legality test with true e.p. status here,  */
8265         /* to make sure an illegal e.p. capture does not slip through,   */
8266         /* to cause a forfeit on a justified illegal-move complaint      */
8267         /* of the opponent.                                              */
8268         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8269            ChessMove moveType;
8270            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8271                              fromY, fromX, toY, toX, promoChar);
8272             if(moveType == IllegalMove) {
8273               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8274                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8275                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8276                            buf1, GE_XBOARD);
8277                 return;
8278            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8279            /* [HGM] Kludge to handle engines that send FRC-style castling
8280               when they shouldn't (like TSCP-Gothic) */
8281            switch(moveType) {
8282              case WhiteASideCastleFR:
8283              case BlackASideCastleFR:
8284                toX+=2;
8285                currentMoveString[2]++;
8286                break;
8287              case WhiteHSideCastleFR:
8288              case BlackHSideCastleFR:
8289                toX--;
8290                currentMoveString[2]--;
8291                break;
8292              default: ; // nothing to do, but suppresses warning of pedantic compilers
8293            }
8294         }
8295         hintRequested = FALSE;
8296         lastHint[0] = NULLCHAR;
8297         bookRequested = FALSE;
8298         /* Program may be pondering now */
8299         cps->maybeThinking = TRUE;
8300         if (cps->sendTime == 2) cps->sendTime = 1;
8301         if (cps->offeredDraw) cps->offeredDraw--;
8302
8303         /* [AS] Save move info*/
8304         pvInfoList[ forwardMostMove ].score = programStats.score;
8305         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8306         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8307
8308         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8309
8310         /* Test suites abort the 'game' after one move */
8311         if(*appData.finger) {
8312            static FILE *f;
8313            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8314            if(!f) f = fopen(appData.finger, "w");
8315            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8316            else { DisplayFatalError("Bad output file", errno, 0); return; }
8317            free(fen);
8318            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8319         }
8320
8321         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8322         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8323             int count = 0;
8324
8325             while( count < adjudicateLossPlies ) {
8326                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8327
8328                 if( count & 1 ) {
8329                     score = -score; /* Flip score for winning side */
8330                 }
8331
8332                 if( score > adjudicateLossThreshold ) {
8333                     break;
8334                 }
8335
8336                 count++;
8337             }
8338
8339             if( count >= adjudicateLossPlies ) {
8340                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8341
8342                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8343                     "Xboard adjudication",
8344                     GE_XBOARD );
8345
8346                 return;
8347             }
8348         }
8349
8350         if(Adjudicate(cps)) {
8351             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8352             return; // [HGM] adjudicate: for all automatic game ends
8353         }
8354
8355 #if ZIPPY
8356         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8357             first.initDone) {
8358           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8359                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8360                 SendToICS("draw ");
8361                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8362           }
8363           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8364           ics_user_moved = 1;
8365           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8366                 char buf[3*MSG_SIZ];
8367
8368                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8369                         programStats.score / 100.,
8370                         programStats.depth,
8371                         programStats.time / 100.,
8372                         (unsigned int)programStats.nodes,
8373                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8374                         programStats.movelist);
8375                 SendToICS(buf);
8376 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8377           }
8378         }
8379 #endif
8380
8381         /* [AS] Clear stats for next move */
8382         ClearProgramStats();
8383         thinkOutput[0] = NULLCHAR;
8384         hiddenThinkOutputState = 0;
8385
8386         bookHit = NULL;
8387         if (gameMode == TwoMachinesPlay) {
8388             /* [HGM] relaying draw offers moved to after reception of move */
8389             /* and interpreting offer as claim if it brings draw condition */
8390             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8391                 SendToProgram("draw\n", cps->other);
8392             }
8393             if (cps->other->sendTime) {
8394                 SendTimeRemaining(cps->other,
8395                                   cps->other->twoMachinesColor[0] == 'w');
8396             }
8397             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8398             if (firstMove && !bookHit) {
8399                 firstMove = FALSE;
8400                 if (cps->other->useColors) {
8401                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8402                 }
8403                 SendToProgram("go\n", cps->other);
8404             }
8405             cps->other->maybeThinking = TRUE;
8406         }
8407
8408         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8409
8410         if (!pausing && appData.ringBellAfterMoves) {
8411             RingBell();
8412         }
8413
8414         /*
8415          * Reenable menu items that were disabled while
8416          * machine was thinking
8417          */
8418         if (gameMode != TwoMachinesPlay)
8419             SetUserThinkingEnables();
8420
8421         // [HGM] book: after book hit opponent has received move and is now in force mode
8422         // force the book reply into it, and then fake that it outputted this move by jumping
8423         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8424         if(bookHit) {
8425                 static char bookMove[MSG_SIZ]; // a bit generous?
8426
8427                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8428                 strcat(bookMove, bookHit);
8429                 message = bookMove;
8430                 cps = cps->other;
8431                 programStats.nodes = programStats.depth = programStats.time =
8432                 programStats.score = programStats.got_only_move = 0;
8433                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8434
8435                 if(cps->lastPing != cps->lastPong) {
8436                     savedMessage = message; // args for deferred call
8437                     savedState = cps;
8438                     ScheduleDelayedEvent(DeferredBookMove, 10);
8439                     return;
8440                 }
8441                 goto FakeBookMove;
8442         }
8443
8444         return;
8445     }
8446
8447     /* Set special modes for chess engines.  Later something general
8448      *  could be added here; for now there is just one kludge feature,
8449      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8450      *  when "xboard" is given as an interactive command.
8451      */
8452     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8453         cps->useSigint = FALSE;
8454         cps->useSigterm = FALSE;
8455     }
8456     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8457       ParseFeatures(message+8, cps);
8458       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8459     }
8460
8461     if (!strncmp(message, "setup ", 6) && 
8462         (!appData.testLegality || gameInfo.variant == VariantFairy || NonStandardBoardSize())
8463                                         ) { // [HGM] allow first engine to define opening position
8464       int dummy, s=6; char buf[MSG_SIZ];
8465       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8466       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8467       if(startedFromSetupPosition) return;
8468       if(sscanf(message+s, "%dx%d+%d", &dummy, &dummy, &dummy) == 3) while(message[s] && message[s++] != ' '); // for compatibility with Alien Edition
8469       ParseFEN(boards[0], &dummy, message+s);
8470       DrawPosition(TRUE, boards[0]);
8471       startedFromSetupPosition = TRUE;
8472       return;
8473     }
8474     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8475      * want this, I was asked to put it in, and obliged.
8476      */
8477     if (!strncmp(message, "setboard ", 9)) {
8478         Board initial_position;
8479
8480         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8481
8482         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8483             DisplayError(_("Bad FEN received from engine"), 0);
8484             return ;
8485         } else {
8486            Reset(TRUE, FALSE);
8487            CopyBoard(boards[0], initial_position);
8488            initialRulePlies = FENrulePlies;
8489            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8490            else gameMode = MachinePlaysBlack;
8491            DrawPosition(FALSE, boards[currentMove]);
8492         }
8493         return;
8494     }
8495
8496     /*
8497      * Look for communication commands
8498      */
8499     if (!strncmp(message, "telluser ", 9)) {
8500         if(message[9] == '\\' && message[10] == '\\')
8501             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8502         PlayTellSound();
8503         DisplayNote(message + 9);
8504         return;
8505     }
8506     if (!strncmp(message, "tellusererror ", 14)) {
8507         cps->userError = 1;
8508         if(message[14] == '\\' && message[15] == '\\')
8509             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8510         PlayTellSound();
8511         DisplayError(message + 14, 0);
8512         return;
8513     }
8514     if (!strncmp(message, "tellopponent ", 13)) {
8515       if (appData.icsActive) {
8516         if (loggedOn) {
8517           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8518           SendToICS(buf1);
8519         }
8520       } else {
8521         DisplayNote(message + 13);
8522       }
8523       return;
8524     }
8525     if (!strncmp(message, "tellothers ", 11)) {
8526       if (appData.icsActive) {
8527         if (loggedOn) {
8528           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8529           SendToICS(buf1);
8530         }
8531       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8532       return;
8533     }
8534     if (!strncmp(message, "tellall ", 8)) {
8535       if (appData.icsActive) {
8536         if (loggedOn) {
8537           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8538           SendToICS(buf1);
8539         }
8540       } else {
8541         DisplayNote(message + 8);
8542       }
8543       return;
8544     }
8545     if (strncmp(message, "warning", 7) == 0) {
8546         /* Undocumented feature, use tellusererror in new code */
8547         DisplayError(message, 0);
8548         return;
8549     }
8550     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8551         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8552         strcat(realname, " query");
8553         AskQuestion(realname, buf2, buf1, cps->pr);
8554         return;
8555     }
8556     /* Commands from the engine directly to ICS.  We don't allow these to be
8557      *  sent until we are logged on. Crafty kibitzes have been known to
8558      *  interfere with the login process.
8559      */
8560     if (loggedOn) {
8561         if (!strncmp(message, "tellics ", 8)) {
8562             SendToICS(message + 8);
8563             SendToICS("\n");
8564             return;
8565         }
8566         if (!strncmp(message, "tellicsnoalias ", 15)) {
8567             SendToICS(ics_prefix);
8568             SendToICS(message + 15);
8569             SendToICS("\n");
8570             return;
8571         }
8572         /* The following are for backward compatibility only */
8573         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8574             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8575             SendToICS(ics_prefix);
8576             SendToICS(message);
8577             SendToICS("\n");
8578             return;
8579         }
8580     }
8581     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8582         return;
8583     }
8584     /*
8585      * If the move is illegal, cancel it and redraw the board.
8586      * Also deal with other error cases.  Matching is rather loose
8587      * here to accommodate engines written before the spec.
8588      */
8589     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8590         strncmp(message, "Error", 5) == 0) {
8591         if (StrStr(message, "name") ||
8592             StrStr(message, "rating") || StrStr(message, "?") ||
8593             StrStr(message, "result") || StrStr(message, "board") ||
8594             StrStr(message, "bk") || StrStr(message, "computer") ||
8595             StrStr(message, "variant") || StrStr(message, "hint") ||
8596             StrStr(message, "random") || StrStr(message, "depth") ||
8597             StrStr(message, "accepted")) {
8598             return;
8599         }
8600         if (StrStr(message, "protover")) {
8601           /* Program is responding to input, so it's apparently done
8602              initializing, and this error message indicates it is
8603              protocol version 1.  So we don't need to wait any longer
8604              for it to initialize and send feature commands. */
8605           FeatureDone(cps, 1);
8606           cps->protocolVersion = 1;
8607           return;
8608         }
8609         cps->maybeThinking = FALSE;
8610
8611         if (StrStr(message, "draw")) {
8612             /* Program doesn't have "draw" command */
8613             cps->sendDrawOffers = 0;
8614             return;
8615         }
8616         if (cps->sendTime != 1 &&
8617             (StrStr(message, "time") || StrStr(message, "otim"))) {
8618           /* Program apparently doesn't have "time" or "otim" command */
8619           cps->sendTime = 0;
8620           return;
8621         }
8622         if (StrStr(message, "analyze")) {
8623             cps->analysisSupport = FALSE;
8624             cps->analyzing = FALSE;
8625 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8626             EditGameEvent(); // [HGM] try to preserve loaded game
8627             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8628             DisplayError(buf2, 0);
8629             return;
8630         }
8631         if (StrStr(message, "(no matching move)st")) {
8632           /* Special kludge for GNU Chess 4 only */
8633           cps->stKludge = TRUE;
8634           SendTimeControl(cps, movesPerSession, timeControl,
8635                           timeIncrement, appData.searchDepth,
8636                           searchTime);
8637           return;
8638         }
8639         if (StrStr(message, "(no matching move)sd")) {
8640           /* Special kludge for GNU Chess 4 only */
8641           cps->sdKludge = TRUE;
8642           SendTimeControl(cps, movesPerSession, timeControl,
8643                           timeIncrement, appData.searchDepth,
8644                           searchTime);
8645           return;
8646         }
8647         if (!StrStr(message, "llegal")) {
8648             return;
8649         }
8650         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8651             gameMode == IcsIdle) return;
8652         if (forwardMostMove <= backwardMostMove) return;
8653         if (pausing) PauseEvent();
8654       if(appData.forceIllegal) {
8655             // [HGM] illegal: machine refused move; force position after move into it
8656           SendToProgram("force\n", cps);
8657           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8658                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8659                 // when black is to move, while there might be nothing on a2 or black
8660                 // might already have the move. So send the board as if white has the move.
8661                 // But first we must change the stm of the engine, as it refused the last move
8662                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8663                 if(WhiteOnMove(forwardMostMove)) {
8664                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8665                     SendBoard(cps, forwardMostMove); // kludgeless board
8666                 } else {
8667                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8668                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8669                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8670                 }
8671           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8672             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8673                  gameMode == TwoMachinesPlay)
8674               SendToProgram("go\n", cps);
8675             return;
8676       } else
8677         if (gameMode == PlayFromGameFile) {
8678             /* Stop reading this game file */
8679             gameMode = EditGame;
8680             ModeHighlight();
8681         }
8682         /* [HGM] illegal-move claim should forfeit game when Xboard */
8683         /* only passes fully legal moves                            */
8684         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8685             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8686                                 "False illegal-move claim", GE_XBOARD );
8687             return; // do not take back move we tested as valid
8688         }
8689         currentMove = forwardMostMove-1;
8690         DisplayMove(currentMove-1); /* before DisplayMoveError */
8691         SwitchClocks(forwardMostMove-1); // [HGM] race
8692         DisplayBothClocks();
8693         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8694                 parseList[currentMove], _(cps->which));
8695         DisplayMoveError(buf1);
8696         DrawPosition(FALSE, boards[currentMove]);
8697
8698         SetUserThinkingEnables();
8699         return;
8700     }
8701     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8702         /* Program has a broken "time" command that
8703            outputs a string not ending in newline.
8704            Don't use it. */
8705         cps->sendTime = 0;
8706     }
8707
8708     /*
8709      * If chess program startup fails, exit with an error message.
8710      * Attempts to recover here are futile. [HGM] Well, we try anyway
8711      */
8712     if ((StrStr(message, "unknown host") != NULL)
8713         || (StrStr(message, "No remote directory") != NULL)
8714         || (StrStr(message, "not found") != NULL)
8715         || (StrStr(message, "No such file") != NULL)
8716         || (StrStr(message, "can't alloc") != NULL)
8717         || (StrStr(message, "Permission denied") != NULL)) {
8718
8719         cps->maybeThinking = FALSE;
8720         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8721                 _(cps->which), cps->program, cps->host, message);
8722         RemoveInputSource(cps->isr);
8723         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8724             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8725             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8726         }
8727         return;
8728     }
8729
8730     /*
8731      * Look for hint output
8732      */
8733     if (sscanf(message, "Hint: %s", buf1) == 1) {
8734         if (cps == &first && hintRequested) {
8735             hintRequested = FALSE;
8736             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8737                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8738                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8739                                     PosFlags(forwardMostMove),
8740                                     fromY, fromX, toY, toX, promoChar, buf1);
8741                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8742                 DisplayInformation(buf2);
8743             } else {
8744                 /* Hint move could not be parsed!? */
8745               snprintf(buf2, sizeof(buf2),
8746                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8747                         buf1, _(cps->which));
8748                 DisplayError(buf2, 0);
8749             }
8750         } else {
8751           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8752         }
8753         return;
8754     }
8755
8756     /*
8757      * Ignore other messages if game is not in progress
8758      */
8759     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8760         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8761
8762     /*
8763      * look for win, lose, draw, or draw offer
8764      */
8765     if (strncmp(message, "1-0", 3) == 0) {
8766         char *p, *q, *r = "";
8767         p = strchr(message, '{');
8768         if (p) {
8769             q = strchr(p, '}');
8770             if (q) {
8771                 *q = NULLCHAR;
8772                 r = p + 1;
8773             }
8774         }
8775         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8776         return;
8777     } else if (strncmp(message, "0-1", 3) == 0) {
8778         char *p, *q, *r = "";
8779         p = strchr(message, '{');
8780         if (p) {
8781             q = strchr(p, '}');
8782             if (q) {
8783                 *q = NULLCHAR;
8784                 r = p + 1;
8785             }
8786         }
8787         /* Kludge for Arasan 4.1 bug */
8788         if (strcmp(r, "Black resigns") == 0) {
8789             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8790             return;
8791         }
8792         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8793         return;
8794     } else if (strncmp(message, "1/2", 3) == 0) {
8795         char *p, *q, *r = "";
8796         p = strchr(message, '{');
8797         if (p) {
8798             q = strchr(p, '}');
8799             if (q) {
8800                 *q = NULLCHAR;
8801                 r = p + 1;
8802             }
8803         }
8804
8805         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8806         return;
8807
8808     } else if (strncmp(message, "White resign", 12) == 0) {
8809         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8810         return;
8811     } else if (strncmp(message, "Black resign", 12) == 0) {
8812         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8813         return;
8814     } else if (strncmp(message, "White matches", 13) == 0 ||
8815                strncmp(message, "Black matches", 13) == 0   ) {
8816         /* [HGM] ignore GNUShogi noises */
8817         return;
8818     } else if (strncmp(message, "White", 5) == 0 &&
8819                message[5] != '(' &&
8820                StrStr(message, "Black") == NULL) {
8821         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8822         return;
8823     } else if (strncmp(message, "Black", 5) == 0 &&
8824                message[5] != '(') {
8825         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8826         return;
8827     } else if (strcmp(message, "resign") == 0 ||
8828                strcmp(message, "computer resigns") == 0) {
8829         switch (gameMode) {
8830           case MachinePlaysBlack:
8831           case IcsPlayingBlack:
8832             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8833             break;
8834           case MachinePlaysWhite:
8835           case IcsPlayingWhite:
8836             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8837             break;
8838           case TwoMachinesPlay:
8839             if (cps->twoMachinesColor[0] == 'w')
8840               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8841             else
8842               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8843             break;
8844           default:
8845             /* can't happen */
8846             break;
8847         }
8848         return;
8849     } else if (strncmp(message, "opponent mates", 14) == 0) {
8850         switch (gameMode) {
8851           case MachinePlaysBlack:
8852           case IcsPlayingBlack:
8853             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8854             break;
8855           case MachinePlaysWhite:
8856           case IcsPlayingWhite:
8857             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8858             break;
8859           case TwoMachinesPlay:
8860             if (cps->twoMachinesColor[0] == 'w')
8861               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8862             else
8863               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8864             break;
8865           default:
8866             /* can't happen */
8867             break;
8868         }
8869         return;
8870     } else if (strncmp(message, "computer mates", 14) == 0) {
8871         switch (gameMode) {
8872           case MachinePlaysBlack:
8873           case IcsPlayingBlack:
8874             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8875             break;
8876           case MachinePlaysWhite:
8877           case IcsPlayingWhite:
8878             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8879             break;
8880           case TwoMachinesPlay:
8881             if (cps->twoMachinesColor[0] == 'w')
8882               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8883             else
8884               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8885             break;
8886           default:
8887             /* can't happen */
8888             break;
8889         }
8890         return;
8891     } else if (strncmp(message, "checkmate", 9) == 0) {
8892         if (WhiteOnMove(forwardMostMove)) {
8893             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8894         } else {
8895             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8896         }
8897         return;
8898     } else if (strstr(message, "Draw") != NULL ||
8899                strstr(message, "game is a draw") != NULL) {
8900         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8901         return;
8902     } else if (strstr(message, "offer") != NULL &&
8903                strstr(message, "draw") != NULL) {
8904 #if ZIPPY
8905         if (appData.zippyPlay && first.initDone) {
8906             /* Relay offer to ICS */
8907             SendToICS(ics_prefix);
8908             SendToICS("draw\n");
8909         }
8910 #endif
8911         cps->offeredDraw = 2; /* valid until this engine moves twice */
8912         if (gameMode == TwoMachinesPlay) {
8913             if (cps->other->offeredDraw) {
8914                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8915             /* [HGM] in two-machine mode we delay relaying draw offer      */
8916             /* until after we also have move, to see if it is really claim */
8917             }
8918         } else if (gameMode == MachinePlaysWhite ||
8919                    gameMode == MachinePlaysBlack) {
8920           if (userOfferedDraw) {
8921             DisplayInformation(_("Machine accepts your draw offer"));
8922             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8923           } else {
8924             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8925           }
8926         }
8927     }
8928
8929
8930     /*
8931      * Look for thinking output
8932      */
8933     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8934           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8935                                 ) {
8936         int plylev, mvleft, mvtot, curscore, time;
8937         char mvname[MOVE_LEN];
8938         u64 nodes; // [DM]
8939         char plyext;
8940         int ignore = FALSE;
8941         int prefixHint = FALSE;
8942         mvname[0] = NULLCHAR;
8943
8944         switch (gameMode) {
8945           case MachinePlaysBlack:
8946           case IcsPlayingBlack:
8947             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8948             break;
8949           case MachinePlaysWhite:
8950           case IcsPlayingWhite:
8951             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8952             break;
8953           case AnalyzeMode:
8954           case AnalyzeFile:
8955             break;
8956           case IcsObserving: /* [DM] icsEngineAnalyze */
8957             if (!appData.icsEngineAnalyze) ignore = TRUE;
8958             break;
8959           case TwoMachinesPlay:
8960             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8961                 ignore = TRUE;
8962             }
8963             break;
8964           default:
8965             ignore = TRUE;
8966             break;
8967         }
8968
8969         if (!ignore) {
8970             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8971             buf1[0] = NULLCHAR;
8972             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8973                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8974
8975                 if (plyext != ' ' && plyext != '\t') {
8976                     time *= 100;
8977                 }
8978
8979                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8980                 if( cps->scoreIsAbsolute &&
8981                     ( gameMode == MachinePlaysBlack ||
8982                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8983                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8984                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8985                      !WhiteOnMove(currentMove)
8986                     ) )
8987                 {
8988                     curscore = -curscore;
8989                 }
8990
8991                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8992
8993                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8994                         char buf[MSG_SIZ];
8995                         FILE *f;
8996                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8997                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8998                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8999                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9000                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9001                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9002                                 fclose(f);
9003                         } else DisplayError(_("failed writing PV"), 0);
9004                 }
9005
9006                 tempStats.depth = plylev;
9007                 tempStats.nodes = nodes;
9008                 tempStats.time = time;
9009                 tempStats.score = curscore;
9010                 tempStats.got_only_move = 0;
9011
9012                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9013                         int ticklen;
9014
9015                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9016                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9017                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9018                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9019                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9020                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9021                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9022                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9023                 }
9024
9025                 /* Buffer overflow protection */
9026                 if (pv[0] != NULLCHAR) {
9027                     if (strlen(pv) >= sizeof(tempStats.movelist)
9028                         && appData.debugMode) {
9029                         fprintf(debugFP,
9030                                 "PV is too long; using the first %u bytes.\n",
9031                                 (unsigned) sizeof(tempStats.movelist) - 1);
9032                     }
9033
9034                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9035                 } else {
9036                     sprintf(tempStats.movelist, " no PV\n");
9037                 }
9038
9039                 if (tempStats.seen_stat) {
9040                     tempStats.ok_to_send = 1;
9041                 }
9042
9043                 if (strchr(tempStats.movelist, '(') != NULL) {
9044                     tempStats.line_is_book = 1;
9045                     tempStats.nr_moves = 0;
9046                     tempStats.moves_left = 0;
9047                 } else {
9048                     tempStats.line_is_book = 0;
9049                 }
9050
9051                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9052                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9053
9054                 SendProgramStatsToFrontend( cps, &tempStats );
9055
9056                 /*
9057                     [AS] Protect the thinkOutput buffer from overflow... this
9058                     is only useful if buf1 hasn't overflowed first!
9059                 */
9060                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9061                          plylev,
9062                          (gameMode == TwoMachinesPlay ?
9063                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9064                          ((double) curscore) / 100.0,
9065                          prefixHint ? lastHint : "",
9066                          prefixHint ? " " : "" );
9067
9068                 if( buf1[0] != NULLCHAR ) {
9069                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9070
9071                     if( strlen(pv) > max_len ) {
9072                         if( appData.debugMode) {
9073                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9074                         }
9075                         pv[max_len+1] = '\0';
9076                     }
9077
9078                     strcat( thinkOutput, pv);
9079                 }
9080
9081                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9082                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9083                     DisplayMove(currentMove - 1);
9084                 }
9085                 return;
9086
9087             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9088                 /* crafty (9.25+) says "(only move) <move>"
9089                  * if there is only 1 legal move
9090                  */
9091                 sscanf(p, "(only move) %s", buf1);
9092                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9093                 sprintf(programStats.movelist, "%s (only move)", buf1);
9094                 programStats.depth = 1;
9095                 programStats.nr_moves = 1;
9096                 programStats.moves_left = 1;
9097                 programStats.nodes = 1;
9098                 programStats.time = 1;
9099                 programStats.got_only_move = 1;
9100
9101                 /* Not really, but we also use this member to
9102                    mean "line isn't going to change" (Crafty
9103                    isn't searching, so stats won't change) */
9104                 programStats.line_is_book = 1;
9105
9106                 SendProgramStatsToFrontend( cps, &programStats );
9107
9108                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9109                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9110                     DisplayMove(currentMove - 1);
9111                 }
9112                 return;
9113             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9114                               &time, &nodes, &plylev, &mvleft,
9115                               &mvtot, mvname) >= 5) {
9116                 /* The stat01: line is from Crafty (9.29+) in response
9117                    to the "." command */
9118                 programStats.seen_stat = 1;
9119                 cps->maybeThinking = TRUE;
9120
9121                 if (programStats.got_only_move || !appData.periodicUpdates)
9122                   return;
9123
9124                 programStats.depth = plylev;
9125                 programStats.time = time;
9126                 programStats.nodes = nodes;
9127                 programStats.moves_left = mvleft;
9128                 programStats.nr_moves = mvtot;
9129                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9130                 programStats.ok_to_send = 1;
9131                 programStats.movelist[0] = '\0';
9132
9133                 SendProgramStatsToFrontend( cps, &programStats );
9134
9135                 return;
9136
9137             } else if (strncmp(message,"++",2) == 0) {
9138                 /* Crafty 9.29+ outputs this */
9139                 programStats.got_fail = 2;
9140                 return;
9141
9142             } else if (strncmp(message,"--",2) == 0) {
9143                 /* Crafty 9.29+ outputs this */
9144                 programStats.got_fail = 1;
9145                 return;
9146
9147             } else if (thinkOutput[0] != NULLCHAR &&
9148                        strncmp(message, "    ", 4) == 0) {
9149                 unsigned message_len;
9150
9151                 p = message;
9152                 while (*p && *p == ' ') p++;
9153
9154                 message_len = strlen( p );
9155
9156                 /* [AS] Avoid buffer overflow */
9157                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9158                     strcat(thinkOutput, " ");
9159                     strcat(thinkOutput, p);
9160                 }
9161
9162                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9163                     strcat(programStats.movelist, " ");
9164                     strcat(programStats.movelist, p);
9165                 }
9166
9167                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9168                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9169                     DisplayMove(currentMove - 1);
9170                 }
9171                 return;
9172             }
9173         }
9174         else {
9175             buf1[0] = NULLCHAR;
9176
9177             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9178                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9179             {
9180                 ChessProgramStats cpstats;
9181
9182                 if (plyext != ' ' && plyext != '\t') {
9183                     time *= 100;
9184                 }
9185
9186                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9187                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9188                     curscore = -curscore;
9189                 }
9190
9191                 cpstats.depth = plylev;
9192                 cpstats.nodes = nodes;
9193                 cpstats.time = time;
9194                 cpstats.score = curscore;
9195                 cpstats.got_only_move = 0;
9196                 cpstats.movelist[0] = '\0';
9197
9198                 if (buf1[0] != NULLCHAR) {
9199                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9200                 }
9201
9202                 cpstats.ok_to_send = 0;
9203                 cpstats.line_is_book = 0;
9204                 cpstats.nr_moves = 0;
9205                 cpstats.moves_left = 0;
9206
9207                 SendProgramStatsToFrontend( cps, &cpstats );
9208             }
9209         }
9210     }
9211 }
9212
9213
9214 /* Parse a game score from the character string "game", and
9215    record it as the history of the current game.  The game
9216    score is NOT assumed to start from the standard position.
9217    The display is not updated in any way.
9218    */
9219 void
9220 ParseGameHistory (char *game)
9221 {
9222     ChessMove moveType;
9223     int fromX, fromY, toX, toY, boardIndex;
9224     char promoChar;
9225     char *p, *q;
9226     char buf[MSG_SIZ];
9227
9228     if (appData.debugMode)
9229       fprintf(debugFP, "Parsing game history: %s\n", game);
9230
9231     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9232     gameInfo.site = StrSave(appData.icsHost);
9233     gameInfo.date = PGNDate();
9234     gameInfo.round = StrSave("-");
9235
9236     /* Parse out names of players */
9237     while (*game == ' ') game++;
9238     p = buf;
9239     while (*game != ' ') *p++ = *game++;
9240     *p = NULLCHAR;
9241     gameInfo.white = StrSave(buf);
9242     while (*game == ' ') game++;
9243     p = buf;
9244     while (*game != ' ' && *game != '\n') *p++ = *game++;
9245     *p = NULLCHAR;
9246     gameInfo.black = StrSave(buf);
9247
9248     /* Parse moves */
9249     boardIndex = blackPlaysFirst ? 1 : 0;
9250     yynewstr(game);
9251     for (;;) {
9252         yyboardindex = boardIndex;
9253         moveType = (ChessMove) Myylex();
9254         switch (moveType) {
9255           case IllegalMove:             /* maybe suicide chess, etc. */
9256   if (appData.debugMode) {
9257     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9258     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9259     setbuf(debugFP, NULL);
9260   }
9261           case WhitePromotion:
9262           case BlackPromotion:
9263           case WhiteNonPromotion:
9264           case BlackNonPromotion:
9265           case NormalMove:
9266           case WhiteCapturesEnPassant:
9267           case BlackCapturesEnPassant:
9268           case WhiteKingSideCastle:
9269           case WhiteQueenSideCastle:
9270           case BlackKingSideCastle:
9271           case BlackQueenSideCastle:
9272           case WhiteKingSideCastleWild:
9273           case WhiteQueenSideCastleWild:
9274           case BlackKingSideCastleWild:
9275           case BlackQueenSideCastleWild:
9276           /* PUSH Fabien */
9277           case WhiteHSideCastleFR:
9278           case WhiteASideCastleFR:
9279           case BlackHSideCastleFR:
9280           case BlackASideCastleFR:
9281           /* POP Fabien */
9282             fromX = currentMoveString[0] - AAA;
9283             fromY = currentMoveString[1] - ONE;
9284             toX = currentMoveString[2] - AAA;
9285             toY = currentMoveString[3] - ONE;
9286             promoChar = currentMoveString[4];
9287             break;
9288           case WhiteDrop:
9289           case BlackDrop:
9290             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9291             fromX = moveType == WhiteDrop ?
9292               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9293             (int) CharToPiece(ToLower(currentMoveString[0]));
9294             fromY = DROP_RANK;
9295             toX = currentMoveString[2] - AAA;
9296             toY = currentMoveString[3] - ONE;
9297             promoChar = NULLCHAR;
9298             break;
9299           case AmbiguousMove:
9300             /* bug? */
9301             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9302   if (appData.debugMode) {
9303     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9304     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9305     setbuf(debugFP, NULL);
9306   }
9307             DisplayError(buf, 0);
9308             return;
9309           case ImpossibleMove:
9310             /* bug? */
9311             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9312   if (appData.debugMode) {
9313     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9314     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9315     setbuf(debugFP, NULL);
9316   }
9317             DisplayError(buf, 0);
9318             return;
9319           case EndOfFile:
9320             if (boardIndex < backwardMostMove) {
9321                 /* Oops, gap.  How did that happen? */
9322                 DisplayError(_("Gap in move list"), 0);
9323                 return;
9324             }
9325             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9326             if (boardIndex > forwardMostMove) {
9327                 forwardMostMove = boardIndex;
9328             }
9329             return;
9330           case ElapsedTime:
9331             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9332                 strcat(parseList[boardIndex-1], " ");
9333                 strcat(parseList[boardIndex-1], yy_text);
9334             }
9335             continue;
9336           case Comment:
9337           case PGNTag:
9338           case NAG:
9339           default:
9340             /* ignore */
9341             continue;
9342           case WhiteWins:
9343           case BlackWins:
9344           case GameIsDrawn:
9345           case GameUnfinished:
9346             if (gameMode == IcsExamining) {
9347                 if (boardIndex < backwardMostMove) {
9348                     /* Oops, gap.  How did that happen? */
9349                     return;
9350                 }
9351                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9352                 return;
9353             }
9354             gameInfo.result = moveType;
9355             p = strchr(yy_text, '{');
9356             if (p == NULL) p = strchr(yy_text, '(');
9357             if (p == NULL) {
9358                 p = yy_text;
9359                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9360             } else {
9361                 q = strchr(p, *p == '{' ? '}' : ')');
9362                 if (q != NULL) *q = NULLCHAR;
9363                 p++;
9364             }
9365             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9366             gameInfo.resultDetails = StrSave(p);
9367             continue;
9368         }
9369         if (boardIndex >= forwardMostMove &&
9370             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9371             backwardMostMove = blackPlaysFirst ? 1 : 0;
9372             return;
9373         }
9374         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9375                                  fromY, fromX, toY, toX, promoChar,
9376                                  parseList[boardIndex]);
9377         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9378         /* currentMoveString is set as a side-effect of yylex */
9379         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9380         strcat(moveList[boardIndex], "\n");
9381         boardIndex++;
9382         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9383         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9384           case MT_NONE:
9385           case MT_STALEMATE:
9386           default:
9387             break;
9388           case MT_CHECK:
9389             if(gameInfo.variant != VariantShogi)
9390                 strcat(parseList[boardIndex - 1], "+");
9391             break;
9392           case MT_CHECKMATE:
9393           case MT_STAINMATE:
9394             strcat(parseList[boardIndex - 1], "#");
9395             break;
9396         }
9397     }
9398 }
9399
9400
9401 /* Apply a move to the given board  */
9402 void
9403 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9404 {
9405   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9406   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9407
9408     /* [HGM] compute & store e.p. status and castling rights for new position */
9409     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9410
9411       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9412       oldEP = (signed char)board[EP_STATUS];
9413       board[EP_STATUS] = EP_NONE;
9414
9415   if (fromY == DROP_RANK) {
9416         /* must be first */
9417         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9418             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9419             return;
9420         }
9421         piece = board[toY][toX] = (ChessSquare) fromX;
9422   } else {
9423       int i;
9424
9425       if( board[toY][toX] != EmptySquare )
9426            board[EP_STATUS] = EP_CAPTURE;
9427
9428       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9429            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9430                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9431       } else
9432       if( board[fromY][fromX] == WhitePawn ) {
9433            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9434                board[EP_STATUS] = EP_PAWN_MOVE;
9435            if( toY-fromY==2) {
9436                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9437                         gameInfo.variant != VariantBerolina || toX < fromX)
9438                       board[EP_STATUS] = toX | berolina;
9439                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9440                         gameInfo.variant != VariantBerolina || toX > fromX)
9441                       board[EP_STATUS] = toX;
9442            }
9443       } else
9444       if( board[fromY][fromX] == BlackPawn ) {
9445            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9446                board[EP_STATUS] = EP_PAWN_MOVE;
9447            if( toY-fromY== -2) {
9448                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9449                         gameInfo.variant != VariantBerolina || toX < fromX)
9450                       board[EP_STATUS] = toX | berolina;
9451                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9452                         gameInfo.variant != VariantBerolina || toX > fromX)
9453                       board[EP_STATUS] = toX;
9454            }
9455        }
9456
9457        for(i=0; i<nrCastlingRights; i++) {
9458            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9459               board[CASTLING][i] == toX   && castlingRank[i] == toY
9460              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9461        }
9462
9463        if(gameInfo.variant == VariantSChess) { // update virginity
9464            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9465            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9466            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9467            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9468        }
9469
9470      if (fromX == toX && fromY == toY) return;
9471
9472      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9473      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9474      if(gameInfo.variant == VariantKnightmate)
9475          king += (int) WhiteUnicorn - (int) WhiteKing;
9476
9477     /* Code added by Tord: */
9478     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9479     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9480         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9481       board[fromY][fromX] = EmptySquare;
9482       board[toY][toX] = EmptySquare;
9483       if((toX > fromX) != (piece == WhiteRook)) {
9484         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9485       } else {
9486         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9487       }
9488     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9489                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9490       board[fromY][fromX] = EmptySquare;
9491       board[toY][toX] = EmptySquare;
9492       if((toX > fromX) != (piece == BlackRook)) {
9493         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9494       } else {
9495         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9496       }
9497     /* End of code added by Tord */
9498
9499     } else if (board[fromY][fromX] == king
9500         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9501         && toY == fromY && toX > fromX+1) {
9502         board[fromY][fromX] = EmptySquare;
9503         board[toY][toX] = king;
9504         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9505         board[fromY][BOARD_RGHT-1] = EmptySquare;
9506     } else if (board[fromY][fromX] == king
9507         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9508                && toY == fromY && toX < fromX-1) {
9509         board[fromY][fromX] = EmptySquare;
9510         board[toY][toX] = king;
9511         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9512         board[fromY][BOARD_LEFT] = EmptySquare;
9513     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9514                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9515                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9516                ) {
9517         /* white pawn promotion */
9518         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9519         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9520             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9521         board[fromY][fromX] = EmptySquare;
9522     } else if ((fromY >= BOARD_HEIGHT>>1)
9523                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9524                && (toX != fromX)
9525                && gameInfo.variant != VariantXiangqi
9526                && gameInfo.variant != VariantBerolina
9527                && (board[fromY][fromX] == WhitePawn)
9528                && (board[toY][toX] == EmptySquare)) {
9529         board[fromY][fromX] = EmptySquare;
9530         board[toY][toX] = WhitePawn;
9531         captured = board[toY - 1][toX];
9532         board[toY - 1][toX] = EmptySquare;
9533     } else if ((fromY == BOARD_HEIGHT-4)
9534                && (toX == fromX)
9535                && gameInfo.variant == VariantBerolina
9536                && (board[fromY][fromX] == WhitePawn)
9537                && (board[toY][toX] == EmptySquare)) {
9538         board[fromY][fromX] = EmptySquare;
9539         board[toY][toX] = WhitePawn;
9540         if(oldEP & EP_BEROLIN_A) {
9541                 captured = board[fromY][fromX-1];
9542                 board[fromY][fromX-1] = EmptySquare;
9543         }else{  captured = board[fromY][fromX+1];
9544                 board[fromY][fromX+1] = EmptySquare;
9545         }
9546     } else if (board[fromY][fromX] == king
9547         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9548                && toY == fromY && toX > fromX+1) {
9549         board[fromY][fromX] = EmptySquare;
9550         board[toY][toX] = king;
9551         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9552         board[fromY][BOARD_RGHT-1] = EmptySquare;
9553     } else if (board[fromY][fromX] == king
9554         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9555                && toY == fromY && toX < fromX-1) {
9556         board[fromY][fromX] = EmptySquare;
9557         board[toY][toX] = king;
9558         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9559         board[fromY][BOARD_LEFT] = EmptySquare;
9560     } else if (fromY == 7 && fromX == 3
9561                && board[fromY][fromX] == BlackKing
9562                && toY == 7 && toX == 5) {
9563         board[fromY][fromX] = EmptySquare;
9564         board[toY][toX] = BlackKing;
9565         board[fromY][7] = EmptySquare;
9566         board[toY][4] = BlackRook;
9567     } else if (fromY == 7 && fromX == 3
9568                && board[fromY][fromX] == BlackKing
9569                && toY == 7 && toX == 1) {
9570         board[fromY][fromX] = EmptySquare;
9571         board[toY][toX] = BlackKing;
9572         board[fromY][0] = EmptySquare;
9573         board[toY][2] = BlackRook;
9574     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9575                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9576                && toY < promoRank && promoChar
9577                ) {
9578         /* black pawn promotion */
9579         board[toY][toX] = CharToPiece(ToLower(promoChar));
9580         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9581             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9582         board[fromY][fromX] = EmptySquare;
9583     } else if ((fromY < BOARD_HEIGHT>>1)
9584                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9585                && (toX != fromX)
9586                && gameInfo.variant != VariantXiangqi
9587                && gameInfo.variant != VariantBerolina
9588                && (board[fromY][fromX] == BlackPawn)
9589                && (board[toY][toX] == EmptySquare)) {
9590         board[fromY][fromX] = EmptySquare;
9591         board[toY][toX] = BlackPawn;
9592         captured = board[toY + 1][toX];
9593         board[toY + 1][toX] = EmptySquare;
9594     } else if ((fromY == 3)
9595                && (toX == fromX)
9596                && gameInfo.variant == VariantBerolina
9597                && (board[fromY][fromX] == BlackPawn)
9598                && (board[toY][toX] == EmptySquare)) {
9599         board[fromY][fromX] = EmptySquare;
9600         board[toY][toX] = BlackPawn;
9601         if(oldEP & EP_BEROLIN_A) {
9602                 captured = board[fromY][fromX-1];
9603                 board[fromY][fromX-1] = EmptySquare;
9604         }else{  captured = board[fromY][fromX+1];
9605                 board[fromY][fromX+1] = EmptySquare;
9606         }
9607     } else {
9608         board[toY][toX] = board[fromY][fromX];
9609         board[fromY][fromX] = EmptySquare;
9610     }
9611   }
9612
9613     if (gameInfo.holdingsWidth != 0) {
9614
9615       /* !!A lot more code needs to be written to support holdings  */
9616       /* [HGM] OK, so I have written it. Holdings are stored in the */
9617       /* penultimate board files, so they are automaticlly stored   */
9618       /* in the game history.                                       */
9619       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9620                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9621         /* Delete from holdings, by decreasing count */
9622         /* and erasing image if necessary            */
9623         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9624         if(p < (int) BlackPawn) { /* white drop */
9625              p -= (int)WhitePawn;
9626                  p = PieceToNumber((ChessSquare)p);
9627              if(p >= gameInfo.holdingsSize) p = 0;
9628              if(--board[p][BOARD_WIDTH-2] <= 0)
9629                   board[p][BOARD_WIDTH-1] = EmptySquare;
9630              if((int)board[p][BOARD_WIDTH-2] < 0)
9631                         board[p][BOARD_WIDTH-2] = 0;
9632         } else {                  /* black drop */
9633              p -= (int)BlackPawn;
9634                  p = PieceToNumber((ChessSquare)p);
9635              if(p >= gameInfo.holdingsSize) p = 0;
9636              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9637                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9638              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9639                         board[BOARD_HEIGHT-1-p][1] = 0;
9640         }
9641       }
9642       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9643           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9644         /* [HGM] holdings: Add to holdings, if holdings exist */
9645         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9646                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9647                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9648         }
9649         p = (int) captured;
9650         if (p >= (int) BlackPawn) {
9651           p -= (int)BlackPawn;
9652           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9653                   /* in Shogi restore piece to its original  first */
9654                   captured = (ChessSquare) (DEMOTED captured);
9655                   p = DEMOTED p;
9656           }
9657           p = PieceToNumber((ChessSquare)p);
9658           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9659           board[p][BOARD_WIDTH-2]++;
9660           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9661         } else {
9662           p -= (int)WhitePawn;
9663           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9664                   captured = (ChessSquare) (DEMOTED captured);
9665                   p = DEMOTED p;
9666           }
9667           p = PieceToNumber((ChessSquare)p);
9668           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9669           board[BOARD_HEIGHT-1-p][1]++;
9670           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9671         }
9672       }
9673     } else if (gameInfo.variant == VariantAtomic) {
9674       if (captured != EmptySquare) {
9675         int y, x;
9676         for (y = toY-1; y <= toY+1; y++) {
9677           for (x = toX-1; x <= toX+1; x++) {
9678             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9679                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9680               board[y][x] = EmptySquare;
9681             }
9682           }
9683         }
9684         board[toY][toX] = EmptySquare;
9685       }
9686     }
9687     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9688         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9689     } else
9690     if(promoChar == '+') {
9691         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9692         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9693     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9694         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9695         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9696            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9697         board[toY][toX] = newPiece;
9698     }
9699     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9700                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9701         // [HGM] superchess: take promotion piece out of holdings
9702         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9703         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9704             if(!--board[k][BOARD_WIDTH-2])
9705                 board[k][BOARD_WIDTH-1] = EmptySquare;
9706         } else {
9707             if(!--board[BOARD_HEIGHT-1-k][1])
9708                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9709         }
9710     }
9711
9712 }
9713
9714 /* Updates forwardMostMove */
9715 void
9716 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9717 {
9718 //    forwardMostMove++; // [HGM] bare: moved downstream
9719
9720     (void) CoordsToAlgebraic(boards[forwardMostMove],
9721                              PosFlags(forwardMostMove),
9722                              fromY, fromX, toY, toX, promoChar,
9723                              parseList[forwardMostMove]);
9724
9725     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9726         int timeLeft; static int lastLoadFlag=0; int king, piece;
9727         piece = boards[forwardMostMove][fromY][fromX];
9728         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9729         if(gameInfo.variant == VariantKnightmate)
9730             king += (int) WhiteUnicorn - (int) WhiteKing;
9731         if(forwardMostMove == 0) {
9732             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9733                 fprintf(serverMoves, "%s;", UserName());
9734             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9735                 fprintf(serverMoves, "%s;", second.tidy);
9736             fprintf(serverMoves, "%s;", first.tidy);
9737             if(gameMode == MachinePlaysWhite)
9738                 fprintf(serverMoves, "%s;", UserName());
9739             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9740                 fprintf(serverMoves, "%s;", second.tidy);
9741         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9742         lastLoadFlag = loadFlag;
9743         // print base move
9744         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9745         // print castling suffix
9746         if( toY == fromY && piece == king ) {
9747             if(toX-fromX > 1)
9748                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9749             if(fromX-toX >1)
9750                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9751         }
9752         // e.p. suffix
9753         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9754              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9755              boards[forwardMostMove][toY][toX] == EmptySquare
9756              && fromX != toX && fromY != toY)
9757                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9758         // promotion suffix
9759         if(promoChar != NULLCHAR) {
9760             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9761                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9762                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9763             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9764         }
9765         if(!loadFlag) {
9766                 char buf[MOVE_LEN*2], *p; int len;
9767             fprintf(serverMoves, "/%d/%d",
9768                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9769             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9770             else                      timeLeft = blackTimeRemaining/1000;
9771             fprintf(serverMoves, "/%d", timeLeft);
9772                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9773                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9774                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9775                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9776             fprintf(serverMoves, "/%s", buf);
9777         }
9778         fflush(serverMoves);
9779     }
9780
9781     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9782         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9783       return;
9784     }
9785     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9786     if (commentList[forwardMostMove+1] != NULL) {
9787         free(commentList[forwardMostMove+1]);
9788         commentList[forwardMostMove+1] = NULL;
9789     }
9790     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9791     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9792     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9793     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9794     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9795     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9796     adjustedClock = FALSE;
9797     gameInfo.result = GameUnfinished;
9798     if (gameInfo.resultDetails != NULL) {
9799         free(gameInfo.resultDetails);
9800         gameInfo.resultDetails = NULL;
9801     }
9802     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9803                               moveList[forwardMostMove - 1]);
9804     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9805       case MT_NONE:
9806       case MT_STALEMATE:
9807       default:
9808         break;
9809       case MT_CHECK:
9810         if(gameInfo.variant != VariantShogi)
9811             strcat(parseList[forwardMostMove - 1], "+");
9812         break;
9813       case MT_CHECKMATE:
9814       case MT_STAINMATE:
9815         strcat(parseList[forwardMostMove - 1], "#");
9816         break;
9817     }
9818
9819 }
9820
9821 /* Updates currentMove if not pausing */
9822 void
9823 ShowMove (int fromX, int fromY, int toX, int toY)
9824 {
9825     int instant = (gameMode == PlayFromGameFile) ?
9826         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9827     if(appData.noGUI) return;
9828     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9829         if (!instant) {
9830             if (forwardMostMove == currentMove + 1) {
9831                 AnimateMove(boards[forwardMostMove - 1],
9832                             fromX, fromY, toX, toY);
9833             }
9834         }
9835         currentMove = forwardMostMove;
9836     }
9837
9838     if (instant) return;
9839
9840     DisplayMove(currentMove - 1);
9841     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9842             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9843                 SetHighlights(fromX, fromY, toX, toY);
9844             }
9845     }
9846     DrawPosition(FALSE, boards[currentMove]);
9847     DisplayBothClocks();
9848     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9849 }
9850
9851 void
9852 SendEgtPath (ChessProgramState *cps)
9853 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9854         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9855
9856         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9857
9858         while(*p) {
9859             char c, *q = name+1, *r, *s;
9860
9861             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9862             while(*p && *p != ',') *q++ = *p++;
9863             *q++ = ':'; *q = 0;
9864             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9865                 strcmp(name, ",nalimov:") == 0 ) {
9866                 // take nalimov path from the menu-changeable option first, if it is defined
9867               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9868                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9869             } else
9870             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9871                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9872                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9873                 s = r = StrStr(s, ":") + 1; // beginning of path info
9874                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9875                 c = *r; *r = 0;             // temporarily null-terminate path info
9876                     *--q = 0;               // strip of trailig ':' from name
9877                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9878                 *r = c;
9879                 SendToProgram(buf,cps);     // send egtbpath command for this format
9880             }
9881             if(*p == ',') p++; // read away comma to position for next format name
9882         }
9883 }
9884
9885 static int
9886 NonStandardBoardSize ()
9887 {
9888       /* [HGM] Awkward testing. Should really be a table */
9889       int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9890       if( gameInfo.variant == VariantXiangqi )
9891            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9892       if( gameInfo.variant == VariantShogi )
9893            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9894       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9895            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9896       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9897           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9898            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9899       if( gameInfo.variant == VariantCourier )
9900            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9901       if( gameInfo.variant == VariantSuper )
9902            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9903       if( gameInfo.variant == VariantGreat )
9904            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9905       if( gameInfo.variant == VariantSChess )
9906            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9907       if( gameInfo.variant == VariantGrand )
9908            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9909       return overruled;
9910 }
9911
9912 void
9913 InitChessProgram (ChessProgramState *cps, int setup)
9914 /* setup needed to setup FRC opening position */
9915 {
9916     char buf[MSG_SIZ], b[MSG_SIZ];
9917     if (appData.noChessProgram) return;
9918     hintRequested = FALSE;
9919     bookRequested = FALSE;
9920
9921     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9922     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9923     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9924     if(cps->memSize) { /* [HGM] memory */
9925       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9926         SendToProgram(buf, cps);
9927     }
9928     SendEgtPath(cps); /* [HGM] EGT */
9929     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9930       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9931         SendToProgram(buf, cps);
9932     }
9933
9934     SendToProgram(cps->initString, cps);
9935     if (gameInfo.variant != VariantNormal &&
9936         gameInfo.variant != VariantLoadable
9937         /* [HGM] also send variant if board size non-standard */
9938         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9939                                             ) {
9940       char *v = VariantName(gameInfo.variant);
9941       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9942         /* [HGM] in protocol 1 we have to assume all variants valid */
9943         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9944         DisplayFatalError(buf, 0, 1);
9945         return;
9946       }
9947
9948       if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
9949         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9950                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9951            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9952            if(StrStr(cps->variants, b) == NULL) {
9953                // specific sized variant not known, check if general sizing allowed
9954                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9955                    if(StrStr(cps->variants, "boardsize") == NULL) {
9956                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9957                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9958                        DisplayFatalError(buf, 0, 1);
9959                        return;
9960                    }
9961                    /* [HGM] here we really should compare with the maximum supported board size */
9962                }
9963            }
9964       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9965       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9966       SendToProgram(buf, cps);
9967     }
9968     currentlyInitializedVariant = gameInfo.variant;
9969
9970     /* [HGM] send opening position in FRC to first engine */
9971     if(setup) {
9972           SendToProgram("force\n", cps);
9973           SendBoard(cps, 0);
9974           /* engine is now in force mode! Set flag to wake it up after first move. */
9975           setboardSpoiledMachineBlack = 1;
9976     }
9977
9978     if (cps->sendICS) {
9979       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9980       SendToProgram(buf, cps);
9981     }
9982     cps->maybeThinking = FALSE;
9983     cps->offeredDraw = 0;
9984     if (!appData.icsActive) {
9985         SendTimeControl(cps, movesPerSession, timeControl,
9986                         timeIncrement, appData.searchDepth,
9987                         searchTime);
9988     }
9989     if (appData.showThinking
9990         // [HGM] thinking: four options require thinking output to be sent
9991         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9992                                 ) {
9993         SendToProgram("post\n", cps);
9994     }
9995     SendToProgram("hard\n", cps);
9996     if (!appData.ponderNextMove) {
9997         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9998            it without being sure what state we are in first.  "hard"
9999            is not a toggle, so that one is OK.
10000          */
10001         SendToProgram("easy\n", cps);
10002     }
10003     if (cps->usePing) {
10004       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10005       SendToProgram(buf, cps);
10006     }
10007     cps->initDone = TRUE;
10008     ClearEngineOutputPane(cps == &second);
10009 }
10010
10011
10012 void
10013 ResendOptions (ChessProgramState *cps)
10014 { // send the stored value of the options
10015   int i;
10016   char buf[MSG_SIZ];
10017   Option *opt = cps->option;
10018   for(i=0; i<cps->nrOptions; i++, opt++) {
10019       switch(opt->type) {
10020         case Spin:
10021         case Slider:
10022         case CheckBox:
10023             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10024           break;
10025         case ComboBox:
10026           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10027           break;
10028         default:
10029             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10030           break;
10031         case Button:
10032         case SaveButton:
10033           continue;
10034       }
10035       SendToProgram(buf, cps);
10036   }
10037 }
10038
10039 void
10040 StartChessProgram (ChessProgramState *cps)
10041 {
10042     char buf[MSG_SIZ];
10043     int err;
10044
10045     if (appData.noChessProgram) return;
10046     cps->initDone = FALSE;
10047
10048     if (strcmp(cps->host, "localhost") == 0) {
10049         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10050     } else if (*appData.remoteShell == NULLCHAR) {
10051         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10052     } else {
10053         if (*appData.remoteUser == NULLCHAR) {
10054           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10055                     cps->program);
10056         } else {
10057           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10058                     cps->host, appData.remoteUser, cps->program);
10059         }
10060         err = StartChildProcess(buf, "", &cps->pr);
10061     }
10062
10063     if (err != 0) {
10064       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10065         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10066         if(cps != &first) return;
10067         appData.noChessProgram = TRUE;
10068         ThawUI();
10069         SetNCPMode();
10070 //      DisplayFatalError(buf, err, 1);
10071 //      cps->pr = NoProc;
10072 //      cps->isr = NULL;
10073         return;
10074     }
10075
10076     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10077     if (cps->protocolVersion > 1) {
10078       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10079       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10080         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10081         cps->comboCnt = 0;  //                and values of combo boxes
10082       }
10083       SendToProgram(buf, cps);
10084       if(cps->reload) ResendOptions(cps);
10085     } else {
10086       SendToProgram("xboard\n", cps);
10087     }
10088 }
10089
10090 void
10091 TwoMachinesEventIfReady P((void))
10092 {
10093   static int curMess = 0;
10094   if (first.lastPing != first.lastPong) {
10095     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10096     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10097     return;
10098   }
10099   if (second.lastPing != second.lastPong) {
10100     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10101     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10102     return;
10103   }
10104   DisplayMessage("", ""); curMess = 0;
10105   TwoMachinesEvent();
10106 }
10107
10108 char *
10109 MakeName (char *template)
10110 {
10111     time_t clock;
10112     struct tm *tm;
10113     static char buf[MSG_SIZ];
10114     char *p = buf;
10115     int i;
10116
10117     clock = time((time_t *)NULL);
10118     tm = localtime(&clock);
10119
10120     while(*p++ = *template++) if(p[-1] == '%') {
10121         switch(*template++) {
10122           case 0:   *p = 0; return buf;
10123           case 'Y': i = tm->tm_year+1900; break;
10124           case 'y': i = tm->tm_year-100; break;
10125           case 'M': i = tm->tm_mon+1; break;
10126           case 'd': i = tm->tm_mday; break;
10127           case 'h': i = tm->tm_hour; break;
10128           case 'm': i = tm->tm_min; break;
10129           case 's': i = tm->tm_sec; break;
10130           default:  i = 0;
10131         }
10132         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10133     }
10134     return buf;
10135 }
10136
10137 int
10138 CountPlayers (char *p)
10139 {
10140     int n = 0;
10141     while(p = strchr(p, '\n')) p++, n++; // count participants
10142     return n;
10143 }
10144
10145 FILE *
10146 WriteTourneyFile (char *results, FILE *f)
10147 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10148     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10149     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10150         // create a file with tournament description
10151         fprintf(f, "-participants {%s}\n", appData.participants);
10152         fprintf(f, "-seedBase %d\n", appData.seedBase);
10153         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10154         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10155         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10156         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10157         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10158         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10159         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10160         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10161         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10162         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10163         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10164         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10165         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10166         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10167         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10168         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10169         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10170         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10171         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10172         fprintf(f, "-smpCores %d\n", appData.smpCores);
10173         if(searchTime > 0)
10174                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10175         else {
10176                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10177                 fprintf(f, "-tc %s\n", appData.timeControl);
10178                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10179         }
10180         fprintf(f, "-results \"%s\"\n", results);
10181     }
10182     return f;
10183 }
10184
10185 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10186
10187 void
10188 Substitute (char *participants, int expunge)
10189 {
10190     int i, changed, changes=0, nPlayers=0;
10191     char *p, *q, *r, buf[MSG_SIZ];
10192     if(participants == NULL) return;
10193     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10194     r = p = participants; q = appData.participants;
10195     while(*p && *p == *q) {
10196         if(*p == '\n') r = p+1, nPlayers++;
10197         p++; q++;
10198     }
10199     if(*p) { // difference
10200         while(*p && *p++ != '\n');
10201         while(*q && *q++ != '\n');
10202       changed = nPlayers;
10203         changes = 1 + (strcmp(p, q) != 0);
10204     }
10205     if(changes == 1) { // a single engine mnemonic was changed
10206         q = r; while(*q) nPlayers += (*q++ == '\n');
10207         p = buf; while(*r && (*p = *r++) != '\n') p++;
10208         *p = NULLCHAR;
10209         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10210         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10211         if(mnemonic[i]) { // The substitute is valid
10212             FILE *f;
10213             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10214                 flock(fileno(f), LOCK_EX);
10215                 ParseArgsFromFile(f);
10216                 fseek(f, 0, SEEK_SET);
10217                 FREE(appData.participants); appData.participants = participants;
10218                 if(expunge) { // erase results of replaced engine
10219                     int len = strlen(appData.results), w, b, dummy;
10220                     for(i=0; i<len; i++) {
10221                         Pairing(i, nPlayers, &w, &b, &dummy);
10222                         if((w == changed || b == changed) && appData.results[i] == '*') {
10223                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10224                             fclose(f);
10225                             return;
10226                         }
10227                     }
10228                     for(i=0; i<len; i++) {
10229                         Pairing(i, nPlayers, &w, &b, &dummy);
10230                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10231                     }
10232                 }
10233                 WriteTourneyFile(appData.results, f);
10234                 fclose(f); // release lock
10235                 return;
10236             }
10237         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10238     }
10239     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10240     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10241     free(participants);
10242     return;
10243 }
10244
10245 int
10246 CheckPlayers (char *participants)
10247 {
10248         int i;
10249         char buf[MSG_SIZ], *p;
10250         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10251         while(p = strchr(participants, '\n')) {
10252             *p = NULLCHAR;
10253             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10254             if(!mnemonic[i]) {
10255                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10256                 *p = '\n';
10257                 DisplayError(buf, 0);
10258                 return 1;
10259             }
10260             *p = '\n';
10261             participants = p + 1;
10262         }
10263         return 0;
10264 }
10265
10266 int
10267 CreateTourney (char *name)
10268 {
10269         FILE *f;
10270         if(matchMode && strcmp(name, appData.tourneyFile)) {
10271              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10272         }
10273         if(name[0] == NULLCHAR) {
10274             if(appData.participants[0])
10275                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10276             return 0;
10277         }
10278         f = fopen(name, "r");
10279         if(f) { // file exists
10280             ASSIGN(appData.tourneyFile, name);
10281             ParseArgsFromFile(f); // parse it
10282         } else {
10283             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10284             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10285                 DisplayError(_("Not enough participants"), 0);
10286                 return 0;
10287             }
10288             if(CheckPlayers(appData.participants)) return 0;
10289             ASSIGN(appData.tourneyFile, name);
10290             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10291             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10292         }
10293         fclose(f);
10294         appData.noChessProgram = FALSE;
10295         appData.clockMode = TRUE;
10296         SetGNUMode();
10297         return 1;
10298 }
10299
10300 int
10301 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10302 {
10303     char buf[MSG_SIZ], *p, *q;
10304     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10305     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10306     skip = !all && group[0]; // if group requested, we start in skip mode
10307     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10308         p = names; q = buf; header = 0;
10309         while(*p && *p != '\n') *q++ = *p++;
10310         *q = 0;
10311         if(*p == '\n') p++;
10312         if(buf[0] == '#') {
10313             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10314             depth++; // we must be entering a new group
10315             if(all) continue; // suppress printing group headers when complete list requested
10316             header = 1;
10317             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10318         }
10319         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10320         if(engineList[i]) free(engineList[i]);
10321         engineList[i] = strdup(buf);
10322         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10323         if(engineMnemonic[i]) free(engineMnemonic[i]);
10324         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10325             strcat(buf, " (");
10326             sscanf(q + 8, "%s", buf + strlen(buf));
10327             strcat(buf, ")");
10328         }
10329         engineMnemonic[i] = strdup(buf);
10330         i++;
10331     }
10332     engineList[i] = engineMnemonic[i] = NULL;
10333     return i;
10334 }
10335
10336 // following implemented as macro to avoid type limitations
10337 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10338
10339 void
10340 SwapEngines (int n)
10341 {   // swap settings for first engine and other engine (so far only some selected options)
10342     int h;
10343     char *p;
10344     if(n == 0) return;
10345     SWAP(directory, p)
10346     SWAP(chessProgram, p)
10347     SWAP(isUCI, h)
10348     SWAP(hasOwnBookUCI, h)
10349     SWAP(protocolVersion, h)
10350     SWAP(reuse, h)
10351     SWAP(scoreIsAbsolute, h)
10352     SWAP(timeOdds, h)
10353     SWAP(logo, p)
10354     SWAP(pgnName, p)
10355     SWAP(pvSAN, h)
10356     SWAP(engOptions, p)
10357     SWAP(engInitString, p)
10358     SWAP(computerString, p)
10359     SWAP(features, p)
10360     SWAP(fenOverride, p)
10361     SWAP(NPS, h)
10362     SWAP(accumulateTC, h)
10363     SWAP(host, p)
10364 }
10365
10366 int
10367 GetEngineLine (char *s, int n)
10368 {
10369     int i;
10370     char buf[MSG_SIZ];
10371     extern char *icsNames;
10372     if(!s || !*s) return 0;
10373     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10374     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10375     if(!mnemonic[i]) return 0;
10376     if(n == 11) return 1; // just testing if there was a match
10377     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10378     if(n == 1) SwapEngines(n);
10379     ParseArgsFromString(buf);
10380     if(n == 1) SwapEngines(n);
10381     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10382         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10383         ParseArgsFromString(buf);
10384     }
10385     return 1;
10386 }
10387
10388 int
10389 SetPlayer (int player, char *p)
10390 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10391     int i;
10392     char buf[MSG_SIZ], *engineName;
10393     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10394     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10395     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10396     if(mnemonic[i]) {
10397         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10398         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10399         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10400         ParseArgsFromString(buf);
10401     } else { // no engine with this nickname is installed!
10402         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10403         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10404         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10405         ModeHighlight();
10406         DisplayError(buf, 0);
10407         return 0;
10408     }
10409     free(engineName);
10410     return i;
10411 }
10412
10413 char *recentEngines;
10414
10415 void
10416 RecentEngineEvent (int nr)
10417 {
10418     int n;
10419 //    SwapEngines(1); // bump first to second
10420 //    ReplaceEngine(&second, 1); // and load it there
10421     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10422     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10423     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10424         ReplaceEngine(&first, 0);
10425         FloatToFront(&appData.recentEngineList, command[n]);
10426     }
10427 }
10428
10429 int
10430 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10431 {   // determine players from game number
10432     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10433
10434     if(appData.tourneyType == 0) {
10435         roundsPerCycle = (nPlayers - 1) | 1;
10436         pairingsPerRound = nPlayers / 2;
10437     } else if(appData.tourneyType > 0) {
10438         roundsPerCycle = nPlayers - appData.tourneyType;
10439         pairingsPerRound = appData.tourneyType;
10440     }
10441     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10442     gamesPerCycle = gamesPerRound * roundsPerCycle;
10443     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10444     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10445     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10446     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10447     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10448     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10449
10450     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10451     if(appData.roundSync) *syncInterval = gamesPerRound;
10452
10453     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10454
10455     if(appData.tourneyType == 0) {
10456         if(curPairing == (nPlayers-1)/2 ) {
10457             *whitePlayer = curRound;
10458             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10459         } else {
10460             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10461             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10462             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10463             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10464         }
10465     } else if(appData.tourneyType > 1) {
10466         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10467         *whitePlayer = curRound + appData.tourneyType;
10468     } else if(appData.tourneyType > 0) {
10469         *whitePlayer = curPairing;
10470         *blackPlayer = curRound + appData.tourneyType;
10471     }
10472
10473     // take care of white/black alternation per round.
10474     // For cycles and games this is already taken care of by default, derived from matchGame!
10475     return curRound & 1;
10476 }
10477
10478 int
10479 NextTourneyGame (int nr, int *swapColors)
10480 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10481     char *p, *q;
10482     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10483     FILE *tf;
10484     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10485     tf = fopen(appData.tourneyFile, "r");
10486     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10487     ParseArgsFromFile(tf); fclose(tf);
10488     InitTimeControls(); // TC might be altered from tourney file
10489
10490     nPlayers = CountPlayers(appData.participants); // count participants
10491     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10492     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10493
10494     if(syncInterval) {
10495         p = q = appData.results;
10496         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10497         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10498             DisplayMessage(_("Waiting for other game(s)"),"");
10499             waitingForGame = TRUE;
10500             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10501             return 0;
10502         }
10503         waitingForGame = FALSE;
10504     }
10505
10506     if(appData.tourneyType < 0) {
10507         if(nr>=0 && !pairingReceived) {
10508             char buf[1<<16];
10509             if(pairing.pr == NoProc) {
10510                 if(!appData.pairingEngine[0]) {
10511                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10512                     return 0;
10513                 }
10514                 StartChessProgram(&pairing); // starts the pairing engine
10515             }
10516             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10517             SendToProgram(buf, &pairing);
10518             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10519             SendToProgram(buf, &pairing);
10520             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10521         }
10522         pairingReceived = 0;                              // ... so we continue here
10523         *swapColors = 0;
10524         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10525         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10526         matchGame = 1; roundNr = nr / syncInterval + 1;
10527     }
10528
10529     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10530
10531     // redefine engines, engine dir, etc.
10532     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10533     if(first.pr == NoProc) {
10534       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10535       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10536     }
10537     if(second.pr == NoProc) {
10538       SwapEngines(1);
10539       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10540       SwapEngines(1);         // and make that valid for second engine by swapping
10541       InitEngine(&second, 1);
10542     }
10543     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10544     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10545     return OK;
10546 }
10547
10548 void
10549 NextMatchGame ()
10550 {   // performs game initialization that does not invoke engines, and then tries to start the game
10551     int res, firstWhite, swapColors = 0;
10552     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10553     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
10554         char buf[MSG_SIZ];
10555         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10556         if(strcmp(buf, currentDebugFile)) { // name has changed
10557             FILE *f = fopen(buf, "w");
10558             if(f) { // if opening the new file failed, just keep using the old one
10559                 ASSIGN(currentDebugFile, buf);
10560                 fclose(debugFP);
10561                 debugFP = f;
10562             }
10563             if(appData.serverFileName) {
10564                 if(serverFP) fclose(serverFP);
10565                 serverFP = fopen(appData.serverFileName, "w");
10566                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10567                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10568             }
10569         }
10570     }
10571     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10572     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10573     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10574     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10575     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10576     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10577     Reset(FALSE, first.pr != NoProc);
10578     res = LoadGameOrPosition(matchGame); // setup game
10579     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10580     if(!res) return; // abort when bad game/pos file
10581     TwoMachinesEvent();
10582 }
10583
10584 void
10585 UserAdjudicationEvent (int result)
10586 {
10587     ChessMove gameResult = GameIsDrawn;
10588
10589     if( result > 0 ) {
10590         gameResult = WhiteWins;
10591     }
10592     else if( result < 0 ) {
10593         gameResult = BlackWins;
10594     }
10595
10596     if( gameMode == TwoMachinesPlay ) {
10597         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10598     }
10599 }
10600
10601
10602 // [HGM] save: calculate checksum of game to make games easily identifiable
10603 int
10604 StringCheckSum (char *s)
10605 {
10606         int i = 0;
10607         if(s==NULL) return 0;
10608         while(*s) i = i*259 + *s++;
10609         return i;
10610 }
10611
10612 int
10613 GameCheckSum ()
10614 {
10615         int i, sum=0;
10616         for(i=backwardMostMove; i<forwardMostMove; i++) {
10617                 sum += pvInfoList[i].depth;
10618                 sum += StringCheckSum(parseList[i]);
10619                 sum += StringCheckSum(commentList[i]);
10620                 sum *= 261;
10621         }
10622         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10623         return sum + StringCheckSum(commentList[i]);
10624 } // end of save patch
10625
10626 void
10627 GameEnds (ChessMove result, char *resultDetails, int whosays)
10628 {
10629     GameMode nextGameMode;
10630     int isIcsGame;
10631     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10632
10633     if(endingGame) return; /* [HGM] crash: forbid recursion */
10634     endingGame = 1;
10635     if(twoBoards) { // [HGM] dual: switch back to one board
10636         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10637         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10638     }
10639     if (appData.debugMode) {
10640       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10641               result, resultDetails ? resultDetails : "(null)", whosays);
10642     }
10643
10644     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10645
10646     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10647
10648     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10649         /* If we are playing on ICS, the server decides when the
10650            game is over, but the engine can offer to draw, claim
10651            a draw, or resign.
10652          */
10653 #if ZIPPY
10654         if (appData.zippyPlay && first.initDone) {
10655             if (result == GameIsDrawn) {
10656                 /* In case draw still needs to be claimed */
10657                 SendToICS(ics_prefix);
10658                 SendToICS("draw\n");
10659             } else if (StrCaseStr(resultDetails, "resign")) {
10660                 SendToICS(ics_prefix);
10661                 SendToICS("resign\n");
10662             }
10663         }
10664 #endif
10665         endingGame = 0; /* [HGM] crash */
10666         return;
10667     }
10668
10669     /* If we're loading the game from a file, stop */
10670     if (whosays == GE_FILE) {
10671       (void) StopLoadGameTimer();
10672       gameFileFP = NULL;
10673     }
10674
10675     /* Cancel draw offers */
10676     first.offeredDraw = second.offeredDraw = 0;
10677
10678     /* If this is an ICS game, only ICS can really say it's done;
10679        if not, anyone can. */
10680     isIcsGame = (gameMode == IcsPlayingWhite ||
10681                  gameMode == IcsPlayingBlack ||
10682                  gameMode == IcsObserving    ||
10683                  gameMode == IcsExamining);
10684
10685     if (!isIcsGame || whosays == GE_ICS) {
10686         /* OK -- not an ICS game, or ICS said it was done */
10687         StopClocks();
10688         if (!isIcsGame && !appData.noChessProgram)
10689           SetUserThinkingEnables();
10690
10691         /* [HGM] if a machine claims the game end we verify this claim */
10692         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10693             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10694                 char claimer;
10695                 ChessMove trueResult = (ChessMove) -1;
10696
10697                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10698                                             first.twoMachinesColor[0] :
10699                                             second.twoMachinesColor[0] ;
10700
10701                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10702                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10703                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10704                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10705                 } else
10706                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10707                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10708                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10709                 } else
10710                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10711                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10712                 }
10713
10714                 // now verify win claims, but not in drop games, as we don't understand those yet
10715                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10716                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10717                     (result == WhiteWins && claimer == 'w' ||
10718                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10719                       if (appData.debugMode) {
10720                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10721                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10722                       }
10723                       if(result != trueResult) {
10724                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10725                               result = claimer == 'w' ? BlackWins : WhiteWins;
10726                               resultDetails = buf;
10727                       }
10728                 } else
10729                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10730                     && (forwardMostMove <= backwardMostMove ||
10731                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10732                         (claimer=='b')==(forwardMostMove&1))
10733                                                                                   ) {
10734                       /* [HGM] verify: draws that were not flagged are false claims */
10735                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10736                       result = claimer == 'w' ? BlackWins : WhiteWins;
10737                       resultDetails = buf;
10738                 }
10739                 /* (Claiming a loss is accepted no questions asked!) */
10740             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10741                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10742                 result = GameUnfinished;
10743                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10744             }
10745             /* [HGM] bare: don't allow bare King to win */
10746             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10747                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10748                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10749                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10750                && result != GameIsDrawn)
10751             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10752                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10753                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10754                         if(p >= 0 && p <= (int)WhiteKing) k++;
10755                 }
10756                 if (appData.debugMode) {
10757                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10758                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10759                 }
10760                 if(k <= 1) {
10761                         result = GameIsDrawn;
10762                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10763                         resultDetails = buf;
10764                 }
10765             }
10766         }
10767
10768
10769         if(serverMoves != NULL && !loadFlag) { char c = '=';
10770             if(result==WhiteWins) c = '+';
10771             if(result==BlackWins) c = '-';
10772             if(resultDetails != NULL)
10773                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10774         }
10775         if (resultDetails != NULL) {
10776             gameInfo.result = result;
10777             gameInfo.resultDetails = StrSave(resultDetails);
10778
10779             /* display last move only if game was not loaded from file */
10780             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10781                 DisplayMove(currentMove - 1);
10782
10783             if (forwardMostMove != 0) {
10784                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10785                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10786                                                                 ) {
10787                     if (*appData.saveGameFile != NULLCHAR) {
10788                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10789                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10790                         else
10791                         SaveGameToFile(appData.saveGameFile, TRUE);
10792                     } else if (appData.autoSaveGames) {
10793                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
10794                     }
10795                     if (*appData.savePositionFile != NULLCHAR) {
10796                         SavePositionToFile(appData.savePositionFile);
10797                     }
10798                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10799                 }
10800             }
10801
10802             /* Tell program how game ended in case it is learning */
10803             /* [HGM] Moved this to after saving the PGN, just in case */
10804             /* engine died and we got here through time loss. In that */
10805             /* case we will get a fatal error writing the pipe, which */
10806             /* would otherwise lose us the PGN.                       */
10807             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10808             /* output during GameEnds should never be fatal anymore   */
10809             if (gameMode == MachinePlaysWhite ||
10810                 gameMode == MachinePlaysBlack ||
10811                 gameMode == TwoMachinesPlay ||
10812                 gameMode == IcsPlayingWhite ||
10813                 gameMode == IcsPlayingBlack ||
10814                 gameMode == BeginningOfGame) {
10815                 char buf[MSG_SIZ];
10816                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10817                         resultDetails);
10818                 if (first.pr != NoProc) {
10819                     SendToProgram(buf, &first);
10820                 }
10821                 if (second.pr != NoProc &&
10822                     gameMode == TwoMachinesPlay) {
10823                     SendToProgram(buf, &second);
10824                 }
10825             }
10826         }
10827
10828         if (appData.icsActive) {
10829             if (appData.quietPlay &&
10830                 (gameMode == IcsPlayingWhite ||
10831                  gameMode == IcsPlayingBlack)) {
10832                 SendToICS(ics_prefix);
10833                 SendToICS("set shout 1\n");
10834             }
10835             nextGameMode = IcsIdle;
10836             ics_user_moved = FALSE;
10837             /* clean up premove.  It's ugly when the game has ended and the
10838              * premove highlights are still on the board.
10839              */
10840             if (gotPremove) {
10841               gotPremove = FALSE;
10842               ClearPremoveHighlights();
10843               DrawPosition(FALSE, boards[currentMove]);
10844             }
10845             if (whosays == GE_ICS) {
10846                 switch (result) {
10847                 case WhiteWins:
10848                     if (gameMode == IcsPlayingWhite)
10849                         PlayIcsWinSound();
10850                     else if(gameMode == IcsPlayingBlack)
10851                         PlayIcsLossSound();
10852                     break;
10853                 case BlackWins:
10854                     if (gameMode == IcsPlayingBlack)
10855                         PlayIcsWinSound();
10856                     else if(gameMode == IcsPlayingWhite)
10857                         PlayIcsLossSound();
10858                     break;
10859                 case GameIsDrawn:
10860                     PlayIcsDrawSound();
10861                     break;
10862                 default:
10863                     PlayIcsUnfinishedSound();
10864                 }
10865             }
10866             if(appData.quitNext) { ExitEvent(0); return; }
10867         } else if (gameMode == EditGame ||
10868                    gameMode == PlayFromGameFile ||
10869                    gameMode == AnalyzeMode ||
10870                    gameMode == AnalyzeFile) {
10871             nextGameMode = gameMode;
10872         } else {
10873             nextGameMode = EndOfGame;
10874         }
10875         pausing = FALSE;
10876         ModeHighlight();
10877     } else {
10878         nextGameMode = gameMode;
10879     }
10880
10881     if (appData.noChessProgram) {
10882         gameMode = nextGameMode;
10883         ModeHighlight();
10884         endingGame = 0; /* [HGM] crash */
10885         return;
10886     }
10887
10888     if (first.reuse) {
10889         /* Put first chess program into idle state */
10890         if (first.pr != NoProc &&
10891             (gameMode == MachinePlaysWhite ||
10892              gameMode == MachinePlaysBlack ||
10893              gameMode == TwoMachinesPlay ||
10894              gameMode == IcsPlayingWhite ||
10895              gameMode == IcsPlayingBlack ||
10896              gameMode == BeginningOfGame)) {
10897             SendToProgram("force\n", &first);
10898             if (first.usePing) {
10899               char buf[MSG_SIZ];
10900               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10901               SendToProgram(buf, &first);
10902             }
10903         }
10904     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10905         /* Kill off first chess program */
10906         if (first.isr != NULL)
10907           RemoveInputSource(first.isr);
10908         first.isr = NULL;
10909
10910         if (first.pr != NoProc) {
10911             ExitAnalyzeMode();
10912             DoSleep( appData.delayBeforeQuit );
10913             SendToProgram("quit\n", &first);
10914             DoSleep( appData.delayAfterQuit );
10915             DestroyChildProcess(first.pr, first.useSigterm);
10916             first.reload = TRUE;
10917         }
10918         first.pr = NoProc;
10919     }
10920     if (second.reuse) {
10921         /* Put second chess program into idle state */
10922         if (second.pr != NoProc &&
10923             gameMode == TwoMachinesPlay) {
10924             SendToProgram("force\n", &second);
10925             if (second.usePing) {
10926               char buf[MSG_SIZ];
10927               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10928               SendToProgram(buf, &second);
10929             }
10930         }
10931     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10932         /* Kill off second chess program */
10933         if (second.isr != NULL)
10934           RemoveInputSource(second.isr);
10935         second.isr = NULL;
10936
10937         if (second.pr != NoProc) {
10938             DoSleep( appData.delayBeforeQuit );
10939             SendToProgram("quit\n", &second);
10940             DoSleep( appData.delayAfterQuit );
10941             DestroyChildProcess(second.pr, second.useSigterm);
10942             second.reload = TRUE;
10943         }
10944         second.pr = NoProc;
10945     }
10946
10947     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
10948         char resChar = '=';
10949         switch (result) {
10950         case WhiteWins:
10951           resChar = '+';
10952           if (first.twoMachinesColor[0] == 'w') {
10953             first.matchWins++;
10954           } else {
10955             second.matchWins++;
10956           }
10957           break;
10958         case BlackWins:
10959           resChar = '-';
10960           if (first.twoMachinesColor[0] == 'b') {
10961             first.matchWins++;
10962           } else {
10963             second.matchWins++;
10964           }
10965           break;
10966         case GameUnfinished:
10967           resChar = ' ';
10968         default:
10969           break;
10970         }
10971
10972         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10973         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10974             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10975             ReserveGame(nextGame, resChar); // sets nextGame
10976             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10977             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10978         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10979
10980         if (nextGame <= appData.matchGames && !abortMatch) {
10981             gameMode = nextGameMode;
10982             matchGame = nextGame; // this will be overruled in tourney mode!
10983             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10984             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10985             endingGame = 0; /* [HGM] crash */
10986             return;
10987         } else {
10988             gameMode = nextGameMode;
10989             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10990                      first.tidy, second.tidy,
10991                      first.matchWins, second.matchWins,
10992                      appData.matchGames - (first.matchWins + second.matchWins));
10993             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10994             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10995             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10996             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10997                 first.twoMachinesColor = "black\n";
10998                 second.twoMachinesColor = "white\n";
10999             } else {
11000                 first.twoMachinesColor = "white\n";
11001                 second.twoMachinesColor = "black\n";
11002             }
11003         }
11004     }
11005     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11006         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11007       ExitAnalyzeMode();
11008     gameMode = nextGameMode;
11009     ModeHighlight();
11010     endingGame = 0;  /* [HGM] crash */
11011     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11012         if(matchMode == TRUE) { // match through command line: exit with or without popup
11013             if(ranking) {
11014                 ToNrEvent(forwardMostMove);
11015                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11016                 else ExitEvent(0);
11017             } else DisplayFatalError(buf, 0, 0);
11018         } else { // match through menu; just stop, with or without popup
11019             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11020             ModeHighlight();
11021             if(ranking){
11022                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11023             } else DisplayNote(buf);
11024       }
11025       if(ranking) free(ranking);
11026     }
11027 }
11028
11029 /* Assumes program was just initialized (initString sent).
11030    Leaves program in force mode. */
11031 void
11032 FeedMovesToProgram (ChessProgramState *cps, int upto)
11033 {
11034     int i;
11035
11036     if (appData.debugMode)
11037       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11038               startedFromSetupPosition ? "position and " : "",
11039               backwardMostMove, upto, cps->which);
11040     if(currentlyInitializedVariant != gameInfo.variant) {
11041       char buf[MSG_SIZ];
11042         // [HGM] variantswitch: make engine aware of new variant
11043         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11044                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11045         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11046         SendToProgram(buf, cps);
11047         currentlyInitializedVariant = gameInfo.variant;
11048     }
11049     SendToProgram("force\n", cps);
11050     if (startedFromSetupPosition) {
11051         SendBoard(cps, backwardMostMove);
11052     if (appData.debugMode) {
11053         fprintf(debugFP, "feedMoves\n");
11054     }
11055     }
11056     for (i = backwardMostMove; i < upto; i++) {
11057         SendMoveToProgram(i, cps);
11058     }
11059 }
11060
11061
11062 int
11063 ResurrectChessProgram ()
11064 {
11065      /* The chess program may have exited.
11066         If so, restart it and feed it all the moves made so far. */
11067     static int doInit = 0;
11068
11069     if (appData.noChessProgram) return 1;
11070
11071     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11072         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11073         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11074         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11075     } else {
11076         if (first.pr != NoProc) return 1;
11077         StartChessProgram(&first);
11078     }
11079     InitChessProgram(&first, FALSE);
11080     FeedMovesToProgram(&first, currentMove);
11081
11082     if (!first.sendTime) {
11083         /* can't tell gnuchess what its clock should read,
11084            so we bow to its notion. */
11085         ResetClocks();
11086         timeRemaining[0][currentMove] = whiteTimeRemaining;
11087         timeRemaining[1][currentMove] = blackTimeRemaining;
11088     }
11089
11090     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11091                 appData.icsEngineAnalyze) && first.analysisSupport) {
11092       SendToProgram("analyze\n", &first);
11093       first.analyzing = TRUE;
11094     }
11095     return 1;
11096 }
11097
11098 /*
11099  * Button procedures
11100  */
11101 void
11102 Reset (int redraw, int init)
11103 {
11104     int i;
11105
11106     if (appData.debugMode) {
11107         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11108                 redraw, init, gameMode);
11109     }
11110     CleanupTail(); // [HGM] vari: delete any stored variations
11111     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11112     pausing = pauseExamInvalid = FALSE;
11113     startedFromSetupPosition = blackPlaysFirst = FALSE;
11114     firstMove = TRUE;
11115     whiteFlag = blackFlag = FALSE;
11116     userOfferedDraw = FALSE;
11117     hintRequested = bookRequested = FALSE;
11118     first.maybeThinking = FALSE;
11119     second.maybeThinking = FALSE;
11120     first.bookSuspend = FALSE; // [HGM] book
11121     second.bookSuspend = FALSE;
11122     thinkOutput[0] = NULLCHAR;
11123     lastHint[0] = NULLCHAR;
11124     ClearGameInfo(&gameInfo);
11125     gameInfo.variant = StringToVariant(appData.variant);
11126     ics_user_moved = ics_clock_paused = FALSE;
11127     ics_getting_history = H_FALSE;
11128     ics_gamenum = -1;
11129     white_holding[0] = black_holding[0] = NULLCHAR;
11130     ClearProgramStats();
11131     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11132
11133     ResetFrontEnd();
11134     ClearHighlights();
11135     flipView = appData.flipView;
11136     ClearPremoveHighlights();
11137     gotPremove = FALSE;
11138     alarmSounded = FALSE;
11139
11140     GameEnds(EndOfFile, NULL, GE_PLAYER);
11141     if(appData.serverMovesName != NULL) {
11142         /* [HGM] prepare to make moves file for broadcasting */
11143         clock_t t = clock();
11144         if(serverMoves != NULL) fclose(serverMoves);
11145         serverMoves = fopen(appData.serverMovesName, "r");
11146         if(serverMoves != NULL) {
11147             fclose(serverMoves);
11148             /* delay 15 sec before overwriting, so all clients can see end */
11149             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11150         }
11151         serverMoves = fopen(appData.serverMovesName, "w");
11152     }
11153
11154     ExitAnalyzeMode();
11155     gameMode = BeginningOfGame;
11156     ModeHighlight();
11157     if(appData.icsActive) gameInfo.variant = VariantNormal;
11158     currentMove = forwardMostMove = backwardMostMove = 0;
11159     MarkTargetSquares(1);
11160     InitPosition(redraw);
11161     for (i = 0; i < MAX_MOVES; i++) {
11162         if (commentList[i] != NULL) {
11163             free(commentList[i]);
11164             commentList[i] = NULL;
11165         }
11166     }
11167     ResetClocks();
11168     timeRemaining[0][0] = whiteTimeRemaining;
11169     timeRemaining[1][0] = blackTimeRemaining;
11170
11171     if (first.pr == NoProc) {
11172         StartChessProgram(&first);
11173     }
11174     if (init) {
11175             InitChessProgram(&first, startedFromSetupPosition);
11176     }
11177     DisplayTitle("");
11178     DisplayMessage("", "");
11179     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11180     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11181     ClearMap();        // [HGM] exclude: invalidate map
11182 }
11183
11184 void
11185 AutoPlayGameLoop ()
11186 {
11187     for (;;) {
11188         if (!AutoPlayOneMove())
11189           return;
11190         if (matchMode || appData.timeDelay == 0)
11191           continue;
11192         if (appData.timeDelay < 0)
11193           return;
11194         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11195         break;
11196     }
11197 }
11198
11199 void
11200 AnalyzeNextGame()
11201 {
11202     ReloadGame(1); // next game
11203 }
11204
11205 int
11206 AutoPlayOneMove ()
11207 {
11208     int fromX, fromY, toX, toY;
11209
11210     if (appData.debugMode) {
11211       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11212     }
11213
11214     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11215       return FALSE;
11216
11217     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11218       pvInfoList[currentMove].depth = programStats.depth;
11219       pvInfoList[currentMove].score = programStats.score;
11220       pvInfoList[currentMove].time  = 0;
11221       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11222       else { // append analysis of final position as comment
11223         char buf[MSG_SIZ];
11224         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11225         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11226       }
11227       programStats.depth = 0;
11228     }
11229
11230     if (currentMove >= forwardMostMove) {
11231       if(gameMode == AnalyzeFile) {
11232           if(appData.loadGameIndex == -1) {
11233             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11234           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11235           } else {
11236           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11237         }
11238       }
11239 //      gameMode = EndOfGame;
11240 //      ModeHighlight();
11241
11242       /* [AS] Clear current move marker at the end of a game */
11243       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11244
11245       return FALSE;
11246     }
11247
11248     toX = moveList[currentMove][2] - AAA;
11249     toY = moveList[currentMove][3] - ONE;
11250
11251     if (moveList[currentMove][1] == '@') {
11252         if (appData.highlightLastMove) {
11253             SetHighlights(-1, -1, toX, toY);
11254         }
11255     } else {
11256         fromX = moveList[currentMove][0] - AAA;
11257         fromY = moveList[currentMove][1] - ONE;
11258
11259         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11260
11261         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11262
11263         if (appData.highlightLastMove) {
11264             SetHighlights(fromX, fromY, toX, toY);
11265         }
11266     }
11267     DisplayMove(currentMove);
11268     SendMoveToProgram(currentMove++, &first);
11269     DisplayBothClocks();
11270     DrawPosition(FALSE, boards[currentMove]);
11271     // [HGM] PV info: always display, routine tests if empty
11272     DisplayComment(currentMove - 1, commentList[currentMove]);
11273     return TRUE;
11274 }
11275
11276
11277 int
11278 LoadGameOneMove (ChessMove readAhead)
11279 {
11280     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11281     char promoChar = NULLCHAR;
11282     ChessMove moveType;
11283     char move[MSG_SIZ];
11284     char *p, *q;
11285
11286     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11287         gameMode != AnalyzeMode && gameMode != Training) {
11288         gameFileFP = NULL;
11289         return FALSE;
11290     }
11291
11292     yyboardindex = forwardMostMove;
11293     if (readAhead != EndOfFile) {
11294       moveType = readAhead;
11295     } else {
11296       if (gameFileFP == NULL)
11297           return FALSE;
11298       moveType = (ChessMove) Myylex();
11299     }
11300
11301     done = FALSE;
11302     switch (moveType) {
11303       case Comment:
11304         if (appData.debugMode)
11305           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11306         p = yy_text;
11307
11308         /* append the comment but don't display it */
11309         AppendComment(currentMove, p, FALSE);
11310         return TRUE;
11311
11312       case WhiteCapturesEnPassant:
11313       case BlackCapturesEnPassant:
11314       case WhitePromotion:
11315       case BlackPromotion:
11316       case WhiteNonPromotion:
11317       case BlackNonPromotion:
11318       case NormalMove:
11319       case WhiteKingSideCastle:
11320       case WhiteQueenSideCastle:
11321       case BlackKingSideCastle:
11322       case BlackQueenSideCastle:
11323       case WhiteKingSideCastleWild:
11324       case WhiteQueenSideCastleWild:
11325       case BlackKingSideCastleWild:
11326       case BlackQueenSideCastleWild:
11327       /* PUSH Fabien */
11328       case WhiteHSideCastleFR:
11329       case WhiteASideCastleFR:
11330       case BlackHSideCastleFR:
11331       case BlackASideCastleFR:
11332       /* POP Fabien */
11333         if (appData.debugMode)
11334           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11335         fromX = currentMoveString[0] - AAA;
11336         fromY = currentMoveString[1] - ONE;
11337         toX = currentMoveString[2] - AAA;
11338         toY = currentMoveString[3] - ONE;
11339         promoChar = currentMoveString[4];
11340         break;
11341
11342       case WhiteDrop:
11343       case BlackDrop:
11344         if (appData.debugMode)
11345           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11346         fromX = moveType == WhiteDrop ?
11347           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11348         (int) CharToPiece(ToLower(currentMoveString[0]));
11349         fromY = DROP_RANK;
11350         toX = currentMoveString[2] - AAA;
11351         toY = currentMoveString[3] - ONE;
11352         break;
11353
11354       case WhiteWins:
11355       case BlackWins:
11356       case GameIsDrawn:
11357       case GameUnfinished:
11358         if (appData.debugMode)
11359           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11360         p = strchr(yy_text, '{');
11361         if (p == NULL) p = strchr(yy_text, '(');
11362         if (p == NULL) {
11363             p = yy_text;
11364             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11365         } else {
11366             q = strchr(p, *p == '{' ? '}' : ')');
11367             if (q != NULL) *q = NULLCHAR;
11368             p++;
11369         }
11370         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11371         GameEnds(moveType, p, GE_FILE);
11372         done = TRUE;
11373         if (cmailMsgLoaded) {
11374             ClearHighlights();
11375             flipView = WhiteOnMove(currentMove);
11376             if (moveType == GameUnfinished) flipView = !flipView;
11377             if (appData.debugMode)
11378               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11379         }
11380         break;
11381
11382       case EndOfFile:
11383         if (appData.debugMode)
11384           fprintf(debugFP, "Parser hit end of file\n");
11385         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11386           case MT_NONE:
11387           case MT_CHECK:
11388             break;
11389           case MT_CHECKMATE:
11390           case MT_STAINMATE:
11391             if (WhiteOnMove(currentMove)) {
11392                 GameEnds(BlackWins, "Black mates", GE_FILE);
11393             } else {
11394                 GameEnds(WhiteWins, "White mates", GE_FILE);
11395             }
11396             break;
11397           case MT_STALEMATE:
11398             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11399             break;
11400         }
11401         done = TRUE;
11402         break;
11403
11404       case MoveNumberOne:
11405         if (lastLoadGameStart == GNUChessGame) {
11406             /* GNUChessGames have numbers, but they aren't move numbers */
11407             if (appData.debugMode)
11408               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11409                       yy_text, (int) moveType);
11410             return LoadGameOneMove(EndOfFile); /* tail recursion */
11411         }
11412         /* else fall thru */
11413
11414       case XBoardGame:
11415       case GNUChessGame:
11416       case PGNTag:
11417         /* Reached start of next game in file */
11418         if (appData.debugMode)
11419           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11420         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11421           case MT_NONE:
11422           case MT_CHECK:
11423             break;
11424           case MT_CHECKMATE:
11425           case MT_STAINMATE:
11426             if (WhiteOnMove(currentMove)) {
11427                 GameEnds(BlackWins, "Black mates", GE_FILE);
11428             } else {
11429                 GameEnds(WhiteWins, "White mates", GE_FILE);
11430             }
11431             break;
11432           case MT_STALEMATE:
11433             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11434             break;
11435         }
11436         done = TRUE;
11437         break;
11438
11439       case PositionDiagram:     /* should not happen; ignore */
11440       case ElapsedTime:         /* ignore */
11441       case NAG:                 /* ignore */
11442         if (appData.debugMode)
11443           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11444                   yy_text, (int) moveType);
11445         return LoadGameOneMove(EndOfFile); /* tail recursion */
11446
11447       case IllegalMove:
11448         if (appData.testLegality) {
11449             if (appData.debugMode)
11450               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11451             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11452                     (forwardMostMove / 2) + 1,
11453                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11454             DisplayError(move, 0);
11455             done = TRUE;
11456         } else {
11457             if (appData.debugMode)
11458               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11459                       yy_text, currentMoveString);
11460             fromX = currentMoveString[0] - AAA;
11461             fromY = currentMoveString[1] - ONE;
11462             toX = currentMoveString[2] - AAA;
11463             toY = currentMoveString[3] - ONE;
11464             promoChar = currentMoveString[4];
11465         }
11466         break;
11467
11468       case AmbiguousMove:
11469         if (appData.debugMode)
11470           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11471         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11472                 (forwardMostMove / 2) + 1,
11473                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11474         DisplayError(move, 0);
11475         done = TRUE;
11476         break;
11477
11478       default:
11479       case ImpossibleMove:
11480         if (appData.debugMode)
11481           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11482         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11483                 (forwardMostMove / 2) + 1,
11484                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11485         DisplayError(move, 0);
11486         done = TRUE;
11487         break;
11488     }
11489
11490     if (done) {
11491         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11492             DrawPosition(FALSE, boards[currentMove]);
11493             DisplayBothClocks();
11494             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11495               DisplayComment(currentMove - 1, commentList[currentMove]);
11496         }
11497         (void) StopLoadGameTimer();
11498         gameFileFP = NULL;
11499         cmailOldMove = forwardMostMove;
11500         return FALSE;
11501     } else {
11502         /* currentMoveString is set as a side-effect of yylex */
11503
11504         thinkOutput[0] = NULLCHAR;
11505         MakeMove(fromX, fromY, toX, toY, promoChar);
11506         currentMove = forwardMostMove;
11507         return TRUE;
11508     }
11509 }
11510
11511 /* Load the nth game from the given file */
11512 int
11513 LoadGameFromFile (char *filename, int n, char *title, int useList)
11514 {
11515     FILE *f;
11516     char buf[MSG_SIZ];
11517
11518     if (strcmp(filename, "-") == 0) {
11519         f = stdin;
11520         title = "stdin";
11521     } else {
11522         f = fopen(filename, "rb");
11523         if (f == NULL) {
11524           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11525             DisplayError(buf, errno);
11526             return FALSE;
11527         }
11528     }
11529     if (fseek(f, 0, 0) == -1) {
11530         /* f is not seekable; probably a pipe */
11531         useList = FALSE;
11532     }
11533     if (useList && n == 0) {
11534         int error = GameListBuild(f);
11535         if (error) {
11536             DisplayError(_("Cannot build game list"), error);
11537         } else if (!ListEmpty(&gameList) &&
11538                    ((ListGame *) gameList.tailPred)->number > 1) {
11539             GameListPopUp(f, title);
11540             return TRUE;
11541         }
11542         GameListDestroy();
11543         n = 1;
11544     }
11545     if (n == 0) n = 1;
11546     return LoadGame(f, n, title, FALSE);
11547 }
11548
11549
11550 void
11551 MakeRegisteredMove ()
11552 {
11553     int fromX, fromY, toX, toY;
11554     char promoChar;
11555     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11556         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11557           case CMAIL_MOVE:
11558           case CMAIL_DRAW:
11559             if (appData.debugMode)
11560               fprintf(debugFP, "Restoring %s for game %d\n",
11561                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11562
11563             thinkOutput[0] = NULLCHAR;
11564             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11565             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11566             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11567             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11568             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11569             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11570             MakeMove(fromX, fromY, toX, toY, promoChar);
11571             ShowMove(fromX, fromY, toX, toY);
11572
11573             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11574               case MT_NONE:
11575               case MT_CHECK:
11576                 break;
11577
11578               case MT_CHECKMATE:
11579               case MT_STAINMATE:
11580                 if (WhiteOnMove(currentMove)) {
11581                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11582                 } else {
11583                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11584                 }
11585                 break;
11586
11587               case MT_STALEMATE:
11588                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11589                 break;
11590             }
11591
11592             break;
11593
11594           case CMAIL_RESIGN:
11595             if (WhiteOnMove(currentMove)) {
11596                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11597             } else {
11598                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11599             }
11600             break;
11601
11602           case CMAIL_ACCEPT:
11603             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11604             break;
11605
11606           default:
11607             break;
11608         }
11609     }
11610
11611     return;
11612 }
11613
11614 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11615 int
11616 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11617 {
11618     int retVal;
11619
11620     if (gameNumber > nCmailGames) {
11621         DisplayError(_("No more games in this message"), 0);
11622         return FALSE;
11623     }
11624     if (f == lastLoadGameFP) {
11625         int offset = gameNumber - lastLoadGameNumber;
11626         if (offset == 0) {
11627             cmailMsg[0] = NULLCHAR;
11628             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11629                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11630                 nCmailMovesRegistered--;
11631             }
11632             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11633             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11634                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11635             }
11636         } else {
11637             if (! RegisterMove()) return FALSE;
11638         }
11639     }
11640
11641     retVal = LoadGame(f, gameNumber, title, useList);
11642
11643     /* Make move registered during previous look at this game, if any */
11644     MakeRegisteredMove();
11645
11646     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11647         commentList[currentMove]
11648           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11649         DisplayComment(currentMove - 1, commentList[currentMove]);
11650     }
11651
11652     return retVal;
11653 }
11654
11655 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11656 int
11657 ReloadGame (int offset)
11658 {
11659     int gameNumber = lastLoadGameNumber + offset;
11660     if (lastLoadGameFP == NULL) {
11661         DisplayError(_("No game has been loaded yet"), 0);
11662         return FALSE;
11663     }
11664     if (gameNumber <= 0) {
11665         DisplayError(_("Can't back up any further"), 0);
11666         return FALSE;
11667     }
11668     if (cmailMsgLoaded) {
11669         return CmailLoadGame(lastLoadGameFP, gameNumber,
11670                              lastLoadGameTitle, lastLoadGameUseList);
11671     } else {
11672         return LoadGame(lastLoadGameFP, gameNumber,
11673                         lastLoadGameTitle, lastLoadGameUseList);
11674     }
11675 }
11676
11677 int keys[EmptySquare+1];
11678
11679 int
11680 PositionMatches (Board b1, Board b2)
11681 {
11682     int r, f, sum=0;
11683     switch(appData.searchMode) {
11684         case 1: return CompareWithRights(b1, b2);
11685         case 2:
11686             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11687                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11688             }
11689             return TRUE;
11690         case 3:
11691             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11692               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11693                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11694             }
11695             return sum==0;
11696         case 4:
11697             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11698                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11699             }
11700             return sum==0;
11701     }
11702     return TRUE;
11703 }
11704
11705 #define Q_PROMO  4
11706 #define Q_EP     3
11707 #define Q_BCASTL 2
11708 #define Q_WCASTL 1
11709
11710 int pieceList[256], quickBoard[256];
11711 ChessSquare pieceType[256] = { EmptySquare };
11712 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11713 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11714 int soughtTotal, turn;
11715 Boolean epOK, flipSearch;
11716
11717 typedef struct {
11718     unsigned char piece, to;
11719 } Move;
11720
11721 #define DSIZE (250000)
11722
11723 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11724 Move *moveDatabase = initialSpace;
11725 unsigned int movePtr, dataSize = DSIZE;
11726
11727 int
11728 MakePieceList (Board board, int *counts)
11729 {
11730     int r, f, n=Q_PROMO, total=0;
11731     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11732     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11733         int sq = f + (r<<4);
11734         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11735             quickBoard[sq] = ++n;
11736             pieceList[n] = sq;
11737             pieceType[n] = board[r][f];
11738             counts[board[r][f]]++;
11739             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11740             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11741             total++;
11742         }
11743     }
11744     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11745     return total;
11746 }
11747
11748 void
11749 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11750 {
11751     int sq = fromX + (fromY<<4);
11752     int piece = quickBoard[sq];
11753     quickBoard[sq] = 0;
11754     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11755     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11756         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11757         moveDatabase[movePtr++].piece = Q_WCASTL;
11758         quickBoard[sq] = piece;
11759         piece = quickBoard[from]; quickBoard[from] = 0;
11760         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11761     } else
11762     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11763         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11764         moveDatabase[movePtr++].piece = Q_BCASTL;
11765         quickBoard[sq] = piece;
11766         piece = quickBoard[from]; quickBoard[from] = 0;
11767         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11768     } else
11769     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11770         quickBoard[(fromY<<4)+toX] = 0;
11771         moveDatabase[movePtr].piece = Q_EP;
11772         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11773         moveDatabase[movePtr].to = sq;
11774     } else
11775     if(promoPiece != pieceType[piece]) {
11776         moveDatabase[movePtr++].piece = Q_PROMO;
11777         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11778     }
11779     moveDatabase[movePtr].piece = piece;
11780     quickBoard[sq] = piece;
11781     movePtr++;
11782 }
11783
11784 int
11785 PackGame (Board board)
11786 {
11787     Move *newSpace = NULL;
11788     moveDatabase[movePtr].piece = 0; // terminate previous game
11789     if(movePtr > dataSize) {
11790         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11791         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11792         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11793         if(newSpace) {
11794             int i;
11795             Move *p = moveDatabase, *q = newSpace;
11796             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11797             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11798             moveDatabase = newSpace;
11799         } else { // calloc failed, we must be out of memory. Too bad...
11800             dataSize = 0; // prevent calloc events for all subsequent games
11801             return 0;     // and signal this one isn't cached
11802         }
11803     }
11804     movePtr++;
11805     MakePieceList(board, counts);
11806     return movePtr;
11807 }
11808
11809 int
11810 QuickCompare (Board board, int *minCounts, int *maxCounts)
11811 {   // compare according to search mode
11812     int r, f;
11813     switch(appData.searchMode)
11814     {
11815       case 1: // exact position match
11816         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11817         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11818             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11819         }
11820         break;
11821       case 2: // can have extra material on empty squares
11822         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11823             if(board[r][f] == EmptySquare) continue;
11824             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11825         }
11826         break;
11827       case 3: // material with exact Pawn structure
11828         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11829             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11830             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11831         } // fall through to material comparison
11832       case 4: // exact material
11833         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11834         break;
11835       case 6: // material range with given imbalance
11836         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11837         // fall through to range comparison
11838       case 5: // material range
11839         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11840     }
11841     return TRUE;
11842 }
11843
11844 int
11845 QuickScan (Board board, Move *move)
11846 {   // reconstruct game,and compare all positions in it
11847     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11848     do {
11849         int piece = move->piece;
11850         int to = move->to, from = pieceList[piece];
11851         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11852           if(!piece) return -1;
11853           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11854             piece = (++move)->piece;
11855             from = pieceList[piece];
11856             counts[pieceType[piece]]--;
11857             pieceType[piece] = (ChessSquare) move->to;
11858             counts[move->to]++;
11859           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11860             counts[pieceType[quickBoard[to]]]--;
11861             quickBoard[to] = 0; total--;
11862             move++;
11863             continue;
11864           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11865             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11866             from  = pieceList[piece]; // so this must be King
11867             quickBoard[from] = 0;
11868             pieceList[piece] = to;
11869             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11870             quickBoard[from] = 0; // rook
11871             quickBoard[to] = piece;
11872             to = move->to; piece = move->piece;
11873             goto aftercastle;
11874           }
11875         }
11876         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11877         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11878         quickBoard[from] = 0;
11879       aftercastle:
11880         quickBoard[to] = piece;
11881         pieceList[piece] = to;
11882         cnt++; turn ^= 3;
11883         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11884            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11885            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11886                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11887           ) {
11888             static int lastCounts[EmptySquare+1];
11889             int i;
11890             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11891             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11892         } else stretch = 0;
11893         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11894         move++;
11895     } while(1);
11896 }
11897
11898 void
11899 InitSearch ()
11900 {
11901     int r, f;
11902     flipSearch = FALSE;
11903     CopyBoard(soughtBoard, boards[currentMove]);
11904     soughtTotal = MakePieceList(soughtBoard, maxSought);
11905     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11906     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11907     CopyBoard(reverseBoard, boards[currentMove]);
11908     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11909         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11910         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11911         reverseBoard[r][f] = piece;
11912     }
11913     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11914     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11915     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11916                  || (boards[currentMove][CASTLING][2] == NoRights ||
11917                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11918                  && (boards[currentMove][CASTLING][5] == NoRights ||
11919                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11920       ) {
11921         flipSearch = TRUE;
11922         CopyBoard(flipBoard, soughtBoard);
11923         CopyBoard(rotateBoard, reverseBoard);
11924         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11925             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11926             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11927         }
11928     }
11929     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11930     if(appData.searchMode >= 5) {
11931         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11932         MakePieceList(soughtBoard, minSought);
11933         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11934     }
11935     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11936         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11937 }
11938
11939 GameInfo dummyInfo;
11940 static int creatingBook;
11941
11942 int
11943 GameContainsPosition (FILE *f, ListGame *lg)
11944 {
11945     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11946     int fromX, fromY, toX, toY;
11947     char promoChar;
11948     static int initDone=FALSE;
11949
11950     // weed out games based on numerical tag comparison
11951     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11952     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11953     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11954     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11955     if(!initDone) {
11956         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11957         initDone = TRUE;
11958     }
11959     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11960     else CopyBoard(boards[scratch], initialPosition); // default start position
11961     if(lg->moves) {
11962         turn = btm + 1;
11963         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11964         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11965     }
11966     if(btm) plyNr++;
11967     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11968     fseek(f, lg->offset, 0);
11969     yynewfile(f);
11970     while(1) {
11971         yyboardindex = scratch;
11972         quickFlag = plyNr+1;
11973         next = Myylex();
11974         quickFlag = 0;
11975         switch(next) {
11976             case PGNTag:
11977                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11978             default:
11979                 continue;
11980
11981             case XBoardGame:
11982             case GNUChessGame:
11983                 if(plyNr) return -1; // after we have seen moves, this is for new game
11984               continue;
11985
11986             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11987             case ImpossibleMove:
11988             case WhiteWins: // game ends here with these four
11989             case BlackWins:
11990             case GameIsDrawn:
11991             case GameUnfinished:
11992                 return -1;
11993
11994             case IllegalMove:
11995                 if(appData.testLegality) return -1;
11996             case WhiteCapturesEnPassant:
11997             case BlackCapturesEnPassant:
11998             case WhitePromotion:
11999             case BlackPromotion:
12000             case WhiteNonPromotion:
12001             case BlackNonPromotion:
12002             case NormalMove:
12003             case WhiteKingSideCastle:
12004             case WhiteQueenSideCastle:
12005             case BlackKingSideCastle:
12006             case BlackQueenSideCastle:
12007             case WhiteKingSideCastleWild:
12008             case WhiteQueenSideCastleWild:
12009             case BlackKingSideCastleWild:
12010             case BlackQueenSideCastleWild:
12011             case WhiteHSideCastleFR:
12012             case WhiteASideCastleFR:
12013             case BlackHSideCastleFR:
12014             case BlackASideCastleFR:
12015                 fromX = currentMoveString[0] - AAA;
12016                 fromY = currentMoveString[1] - ONE;
12017                 toX = currentMoveString[2] - AAA;
12018                 toY = currentMoveString[3] - ONE;
12019                 promoChar = currentMoveString[4];
12020                 break;
12021             case WhiteDrop:
12022             case BlackDrop:
12023                 fromX = next == WhiteDrop ?
12024                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12025                   (int) CharToPiece(ToLower(currentMoveString[0]));
12026                 fromY = DROP_RANK;
12027                 toX = currentMoveString[2] - AAA;
12028                 toY = currentMoveString[3] - ONE;
12029                 promoChar = 0;
12030                 break;
12031         }
12032         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12033         plyNr++;
12034         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12035         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12036         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12037         if(appData.findMirror) {
12038             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12039             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12040         }
12041     }
12042 }
12043
12044 /* Load the nth game from open file f */
12045 int
12046 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12047 {
12048     ChessMove cm;
12049     char buf[MSG_SIZ];
12050     int gn = gameNumber;
12051     ListGame *lg = NULL;
12052     int numPGNTags = 0;
12053     int err, pos = -1;
12054     GameMode oldGameMode;
12055     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12056
12057     if (appData.debugMode)
12058         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12059
12060     if (gameMode == Training )
12061         SetTrainingModeOff();
12062
12063     oldGameMode = gameMode;
12064     if (gameMode != BeginningOfGame) {
12065       Reset(FALSE, TRUE);
12066     }
12067
12068     gameFileFP = f;
12069     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12070         fclose(lastLoadGameFP);
12071     }
12072
12073     if (useList) {
12074         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12075
12076         if (lg) {
12077             fseek(f, lg->offset, 0);
12078             GameListHighlight(gameNumber);
12079             pos = lg->position;
12080             gn = 1;
12081         }
12082         else {
12083             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12084               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12085             else
12086             DisplayError(_("Game number out of range"), 0);
12087             return FALSE;
12088         }
12089     } else {
12090         GameListDestroy();
12091         if (fseek(f, 0, 0) == -1) {
12092             if (f == lastLoadGameFP ?
12093                 gameNumber == lastLoadGameNumber + 1 :
12094                 gameNumber == 1) {
12095                 gn = 1;
12096             } else {
12097                 DisplayError(_("Can't seek on game file"), 0);
12098                 return FALSE;
12099             }
12100         }
12101     }
12102     lastLoadGameFP = f;
12103     lastLoadGameNumber = gameNumber;
12104     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12105     lastLoadGameUseList = useList;
12106
12107     yynewfile(f);
12108
12109     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12110       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12111                 lg->gameInfo.black);
12112             DisplayTitle(buf);
12113     } else if (*title != NULLCHAR) {
12114         if (gameNumber > 1) {
12115           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12116             DisplayTitle(buf);
12117         } else {
12118             DisplayTitle(title);
12119         }
12120     }
12121
12122     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12123         gameMode = PlayFromGameFile;
12124         ModeHighlight();
12125     }
12126
12127     currentMove = forwardMostMove = backwardMostMove = 0;
12128     CopyBoard(boards[0], initialPosition);
12129     StopClocks();
12130
12131     /*
12132      * Skip the first gn-1 games in the file.
12133      * Also skip over anything that precedes an identifiable
12134      * start of game marker, to avoid being confused by
12135      * garbage at the start of the file.  Currently
12136      * recognized start of game markers are the move number "1",
12137      * the pattern "gnuchess .* game", the pattern
12138      * "^[#;%] [^ ]* game file", and a PGN tag block.
12139      * A game that starts with one of the latter two patterns
12140      * will also have a move number 1, possibly
12141      * following a position diagram.
12142      * 5-4-02: Let's try being more lenient and allowing a game to
12143      * start with an unnumbered move.  Does that break anything?
12144      */
12145     cm = lastLoadGameStart = EndOfFile;
12146     while (gn > 0) {
12147         yyboardindex = forwardMostMove;
12148         cm = (ChessMove) Myylex();
12149         switch (cm) {
12150           case EndOfFile:
12151             if (cmailMsgLoaded) {
12152                 nCmailGames = CMAIL_MAX_GAMES - gn;
12153             } else {
12154                 Reset(TRUE, TRUE);
12155                 DisplayError(_("Game not found in file"), 0);
12156             }
12157             return FALSE;
12158
12159           case GNUChessGame:
12160           case XBoardGame:
12161             gn--;
12162             lastLoadGameStart = cm;
12163             break;
12164
12165           case MoveNumberOne:
12166             switch (lastLoadGameStart) {
12167               case GNUChessGame:
12168               case XBoardGame:
12169               case PGNTag:
12170                 break;
12171               case MoveNumberOne:
12172               case EndOfFile:
12173                 gn--;           /* count this game */
12174                 lastLoadGameStart = cm;
12175                 break;
12176               default:
12177                 /* impossible */
12178                 break;
12179             }
12180             break;
12181
12182           case PGNTag:
12183             switch (lastLoadGameStart) {
12184               case GNUChessGame:
12185               case PGNTag:
12186               case MoveNumberOne:
12187               case EndOfFile:
12188                 gn--;           /* count this game */
12189                 lastLoadGameStart = cm;
12190                 break;
12191               case XBoardGame:
12192                 lastLoadGameStart = cm; /* game counted already */
12193                 break;
12194               default:
12195                 /* impossible */
12196                 break;
12197             }
12198             if (gn > 0) {
12199                 do {
12200                     yyboardindex = forwardMostMove;
12201                     cm = (ChessMove) Myylex();
12202                 } while (cm == PGNTag || cm == Comment);
12203             }
12204             break;
12205
12206           case WhiteWins:
12207           case BlackWins:
12208           case GameIsDrawn:
12209             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12210                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12211                     != CMAIL_OLD_RESULT) {
12212                     nCmailResults ++ ;
12213                     cmailResult[  CMAIL_MAX_GAMES
12214                                 - gn - 1] = CMAIL_OLD_RESULT;
12215                 }
12216             }
12217             break;
12218
12219           case NormalMove:
12220             /* Only a NormalMove can be at the start of a game
12221              * without a position diagram. */
12222             if (lastLoadGameStart == EndOfFile ) {
12223               gn--;
12224               lastLoadGameStart = MoveNumberOne;
12225             }
12226             break;
12227
12228           default:
12229             break;
12230         }
12231     }
12232
12233     if (appData.debugMode)
12234       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12235
12236     if (cm == XBoardGame) {
12237         /* Skip any header junk before position diagram and/or move 1 */
12238         for (;;) {
12239             yyboardindex = forwardMostMove;
12240             cm = (ChessMove) Myylex();
12241
12242             if (cm == EndOfFile ||
12243                 cm == GNUChessGame || cm == XBoardGame) {
12244                 /* Empty game; pretend end-of-file and handle later */
12245                 cm = EndOfFile;
12246                 break;
12247             }
12248
12249             if (cm == MoveNumberOne || cm == PositionDiagram ||
12250                 cm == PGNTag || cm == Comment)
12251               break;
12252         }
12253     } else if (cm == GNUChessGame) {
12254         if (gameInfo.event != NULL) {
12255             free(gameInfo.event);
12256         }
12257         gameInfo.event = StrSave(yy_text);
12258     }
12259
12260     startedFromSetupPosition = FALSE;
12261     while (cm == PGNTag) {
12262         if (appData.debugMode)
12263           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12264         err = ParsePGNTag(yy_text, &gameInfo);
12265         if (!err) numPGNTags++;
12266
12267         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12268         if(gameInfo.variant != oldVariant) {
12269             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12270             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12271             InitPosition(TRUE);
12272             oldVariant = gameInfo.variant;
12273             if (appData.debugMode)
12274               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12275         }
12276
12277
12278         if (gameInfo.fen != NULL) {
12279           Board initial_position;
12280           startedFromSetupPosition = TRUE;
12281           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12282             Reset(TRUE, TRUE);
12283             DisplayError(_("Bad FEN position in file"), 0);
12284             return FALSE;
12285           }
12286           CopyBoard(boards[0], initial_position);
12287           if (blackPlaysFirst) {
12288             currentMove = forwardMostMove = backwardMostMove = 1;
12289             CopyBoard(boards[1], initial_position);
12290             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12291             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12292             timeRemaining[0][1] = whiteTimeRemaining;
12293             timeRemaining[1][1] = blackTimeRemaining;
12294             if (commentList[0] != NULL) {
12295               commentList[1] = commentList[0];
12296               commentList[0] = NULL;
12297             }
12298           } else {
12299             currentMove = forwardMostMove = backwardMostMove = 0;
12300           }
12301           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12302           {   int i;
12303               initialRulePlies = FENrulePlies;
12304               for( i=0; i< nrCastlingRights; i++ )
12305                   initialRights[i] = initial_position[CASTLING][i];
12306           }
12307           yyboardindex = forwardMostMove;
12308           free(gameInfo.fen);
12309           gameInfo.fen = NULL;
12310         }
12311
12312         yyboardindex = forwardMostMove;
12313         cm = (ChessMove) Myylex();
12314
12315         /* Handle comments interspersed among the tags */
12316         while (cm == Comment) {
12317             char *p;
12318             if (appData.debugMode)
12319               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12320             p = yy_text;
12321             AppendComment(currentMove, p, FALSE);
12322             yyboardindex = forwardMostMove;
12323             cm = (ChessMove) Myylex();
12324         }
12325     }
12326
12327     /* don't rely on existence of Event tag since if game was
12328      * pasted from clipboard the Event tag may not exist
12329      */
12330     if (numPGNTags > 0){
12331         char *tags;
12332         if (gameInfo.variant == VariantNormal) {
12333           VariantClass v = StringToVariant(gameInfo.event);
12334           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12335           if(v < VariantShogi) gameInfo.variant = v;
12336         }
12337         if (!matchMode) {
12338           if( appData.autoDisplayTags ) {
12339             tags = PGNTags(&gameInfo);
12340             TagsPopUp(tags, CmailMsg());
12341             free(tags);
12342           }
12343         }
12344     } else {
12345         /* Make something up, but don't display it now */
12346         SetGameInfo();
12347         TagsPopDown();
12348     }
12349
12350     if (cm == PositionDiagram) {
12351         int i, j;
12352         char *p;
12353         Board initial_position;
12354
12355         if (appData.debugMode)
12356           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12357
12358         if (!startedFromSetupPosition) {
12359             p = yy_text;
12360             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12361               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12362                 switch (*p) {
12363                   case '{':
12364                   case '[':
12365                   case '-':
12366                   case ' ':
12367                   case '\t':
12368                   case '\n':
12369                   case '\r':
12370                     break;
12371                   default:
12372                     initial_position[i][j++] = CharToPiece(*p);
12373                     break;
12374                 }
12375             while (*p == ' ' || *p == '\t' ||
12376                    *p == '\n' || *p == '\r') p++;
12377
12378             if (strncmp(p, "black", strlen("black"))==0)
12379               blackPlaysFirst = TRUE;
12380             else
12381               blackPlaysFirst = FALSE;
12382             startedFromSetupPosition = TRUE;
12383
12384             CopyBoard(boards[0], initial_position);
12385             if (blackPlaysFirst) {
12386                 currentMove = forwardMostMove = backwardMostMove = 1;
12387                 CopyBoard(boards[1], initial_position);
12388                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12389                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12390                 timeRemaining[0][1] = whiteTimeRemaining;
12391                 timeRemaining[1][1] = blackTimeRemaining;
12392                 if (commentList[0] != NULL) {
12393                     commentList[1] = commentList[0];
12394                     commentList[0] = NULL;
12395                 }
12396             } else {
12397                 currentMove = forwardMostMove = backwardMostMove = 0;
12398             }
12399         }
12400         yyboardindex = forwardMostMove;
12401         cm = (ChessMove) Myylex();
12402     }
12403
12404   if(!creatingBook) {
12405     if (first.pr == NoProc) {
12406         StartChessProgram(&first);
12407     }
12408     InitChessProgram(&first, FALSE);
12409     SendToProgram("force\n", &first);
12410     if (startedFromSetupPosition) {
12411         SendBoard(&first, forwardMostMove);
12412     if (appData.debugMode) {
12413         fprintf(debugFP, "Load Game\n");
12414     }
12415         DisplayBothClocks();
12416     }
12417   }
12418
12419     /* [HGM] server: flag to write setup moves in broadcast file as one */
12420     loadFlag = appData.suppressLoadMoves;
12421
12422     while (cm == Comment) {
12423         char *p;
12424         if (appData.debugMode)
12425           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12426         p = yy_text;
12427         AppendComment(currentMove, p, FALSE);
12428         yyboardindex = forwardMostMove;
12429         cm = (ChessMove) Myylex();
12430     }
12431
12432     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12433         cm == WhiteWins || cm == BlackWins ||
12434         cm == GameIsDrawn || cm == GameUnfinished) {
12435         DisplayMessage("", _("No moves in game"));
12436         if (cmailMsgLoaded) {
12437             if (appData.debugMode)
12438               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12439             ClearHighlights();
12440             flipView = FALSE;
12441         }
12442         DrawPosition(FALSE, boards[currentMove]);
12443         DisplayBothClocks();
12444         gameMode = EditGame;
12445         ModeHighlight();
12446         gameFileFP = NULL;
12447         cmailOldMove = 0;
12448         return TRUE;
12449     }
12450
12451     // [HGM] PV info: routine tests if comment empty
12452     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12453         DisplayComment(currentMove - 1, commentList[currentMove]);
12454     }
12455     if (!matchMode && appData.timeDelay != 0)
12456       DrawPosition(FALSE, boards[currentMove]);
12457
12458     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12459       programStats.ok_to_send = 1;
12460     }
12461
12462     /* if the first token after the PGN tags is a move
12463      * and not move number 1, retrieve it from the parser
12464      */
12465     if (cm != MoveNumberOne)
12466         LoadGameOneMove(cm);
12467
12468     /* load the remaining moves from the file */
12469     while (LoadGameOneMove(EndOfFile)) {
12470       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12471       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12472     }
12473
12474     /* rewind to the start of the game */
12475     currentMove = backwardMostMove;
12476
12477     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12478
12479     if (oldGameMode == AnalyzeFile) {
12480       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12481       AnalyzeFileEvent();
12482     } else
12483     if (oldGameMode == AnalyzeMode) {
12484       AnalyzeFileEvent();
12485     }
12486
12487     if(creatingBook) return TRUE;
12488     if (!matchMode && pos > 0) {
12489         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12490     } else
12491     if (matchMode || appData.timeDelay == 0) {
12492       ToEndEvent();
12493     } else if (appData.timeDelay > 0) {
12494       AutoPlayGameLoop();
12495     }
12496
12497     if (appData.debugMode)
12498         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12499
12500     loadFlag = 0; /* [HGM] true game starts */
12501     return TRUE;
12502 }
12503
12504 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12505 int
12506 ReloadPosition (int offset)
12507 {
12508     int positionNumber = lastLoadPositionNumber + offset;
12509     if (lastLoadPositionFP == NULL) {
12510         DisplayError(_("No position has been loaded yet"), 0);
12511         return FALSE;
12512     }
12513     if (positionNumber <= 0) {
12514         DisplayError(_("Can't back up any further"), 0);
12515         return FALSE;
12516     }
12517     return LoadPosition(lastLoadPositionFP, positionNumber,
12518                         lastLoadPositionTitle);
12519 }
12520
12521 /* Load the nth position from the given file */
12522 int
12523 LoadPositionFromFile (char *filename, int n, char *title)
12524 {
12525     FILE *f;
12526     char buf[MSG_SIZ];
12527
12528     if (strcmp(filename, "-") == 0) {
12529         return LoadPosition(stdin, n, "stdin");
12530     } else {
12531         f = fopen(filename, "rb");
12532         if (f == NULL) {
12533             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12534             DisplayError(buf, errno);
12535             return FALSE;
12536         } else {
12537             return LoadPosition(f, n, title);
12538         }
12539     }
12540 }
12541
12542 /* Load the nth position from the given open file, and close it */
12543 int
12544 LoadPosition (FILE *f, int positionNumber, char *title)
12545 {
12546     char *p, line[MSG_SIZ];
12547     Board initial_position;
12548     int i, j, fenMode, pn;
12549
12550     if (gameMode == Training )
12551         SetTrainingModeOff();
12552
12553     if (gameMode != BeginningOfGame) {
12554         Reset(FALSE, TRUE);
12555     }
12556     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12557         fclose(lastLoadPositionFP);
12558     }
12559     if (positionNumber == 0) positionNumber = 1;
12560     lastLoadPositionFP = f;
12561     lastLoadPositionNumber = positionNumber;
12562     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12563     if (first.pr == NoProc && !appData.noChessProgram) {
12564       StartChessProgram(&first);
12565       InitChessProgram(&first, FALSE);
12566     }
12567     pn = positionNumber;
12568     if (positionNumber < 0) {
12569         /* Negative position number means to seek to that byte offset */
12570         if (fseek(f, -positionNumber, 0) == -1) {
12571             DisplayError(_("Can't seek on position file"), 0);
12572             return FALSE;
12573         };
12574         pn = 1;
12575     } else {
12576         if (fseek(f, 0, 0) == -1) {
12577             if (f == lastLoadPositionFP ?
12578                 positionNumber == lastLoadPositionNumber + 1 :
12579                 positionNumber == 1) {
12580                 pn = 1;
12581             } else {
12582                 DisplayError(_("Can't seek on position file"), 0);
12583                 return FALSE;
12584             }
12585         }
12586     }
12587     /* See if this file is FEN or old-style xboard */
12588     if (fgets(line, MSG_SIZ, f) == NULL) {
12589         DisplayError(_("Position not found in file"), 0);
12590         return FALSE;
12591     }
12592     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12593     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12594
12595     if (pn >= 2) {
12596         if (fenMode || line[0] == '#') pn--;
12597         while (pn > 0) {
12598             /* skip positions before number pn */
12599             if (fgets(line, MSG_SIZ, f) == NULL) {
12600                 Reset(TRUE, TRUE);
12601                 DisplayError(_("Position not found in file"), 0);
12602                 return FALSE;
12603             }
12604             if (fenMode || line[0] == '#') pn--;
12605         }
12606     }
12607
12608     if (fenMode) {
12609         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12610             DisplayError(_("Bad FEN position in file"), 0);
12611             return FALSE;
12612         }
12613     } else {
12614         (void) fgets(line, MSG_SIZ, f);
12615         (void) fgets(line, MSG_SIZ, f);
12616
12617         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12618             (void) fgets(line, MSG_SIZ, f);
12619             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12620                 if (*p == ' ')
12621                   continue;
12622                 initial_position[i][j++] = CharToPiece(*p);
12623             }
12624         }
12625
12626         blackPlaysFirst = FALSE;
12627         if (!feof(f)) {
12628             (void) fgets(line, MSG_SIZ, f);
12629             if (strncmp(line, "black", strlen("black"))==0)
12630               blackPlaysFirst = TRUE;
12631         }
12632     }
12633     startedFromSetupPosition = TRUE;
12634
12635     CopyBoard(boards[0], initial_position);
12636     if (blackPlaysFirst) {
12637         currentMove = forwardMostMove = backwardMostMove = 1;
12638         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12639         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12640         CopyBoard(boards[1], initial_position);
12641         DisplayMessage("", _("Black to play"));
12642     } else {
12643         currentMove = forwardMostMove = backwardMostMove = 0;
12644         DisplayMessage("", _("White to play"));
12645     }
12646     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12647     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12648         SendToProgram("force\n", &first);
12649         SendBoard(&first, forwardMostMove);
12650     }
12651     if (appData.debugMode) {
12652 int i, j;
12653   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12654   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12655         fprintf(debugFP, "Load Position\n");
12656     }
12657
12658     if (positionNumber > 1) {
12659       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12660         DisplayTitle(line);
12661     } else {
12662         DisplayTitle(title);
12663     }
12664     gameMode = EditGame;
12665     ModeHighlight();
12666     ResetClocks();
12667     timeRemaining[0][1] = whiteTimeRemaining;
12668     timeRemaining[1][1] = blackTimeRemaining;
12669     DrawPosition(FALSE, boards[currentMove]);
12670
12671     return TRUE;
12672 }
12673
12674
12675 void
12676 CopyPlayerNameIntoFileName (char **dest, char *src)
12677 {
12678     while (*src != NULLCHAR && *src != ',') {
12679         if (*src == ' ') {
12680             *(*dest)++ = '_';
12681             src++;
12682         } else {
12683             *(*dest)++ = *src++;
12684         }
12685     }
12686 }
12687
12688 char *
12689 DefaultFileName (char *ext)
12690 {
12691     static char def[MSG_SIZ];
12692     char *p;
12693
12694     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12695         p = def;
12696         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12697         *p++ = '-';
12698         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12699         *p++ = '.';
12700         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12701     } else {
12702         def[0] = NULLCHAR;
12703     }
12704     return def;
12705 }
12706
12707 /* Save the current game to the given file */
12708 int
12709 SaveGameToFile (char *filename, int append)
12710 {
12711     FILE *f;
12712     char buf[MSG_SIZ];
12713     int result, i, t,tot=0;
12714
12715     if (strcmp(filename, "-") == 0) {
12716         return SaveGame(stdout, 0, NULL);
12717     } else {
12718         for(i=0; i<10; i++) { // upto 10 tries
12719              f = fopen(filename, append ? "a" : "w");
12720              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12721              if(f || errno != 13) break;
12722              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12723              tot += t;
12724         }
12725         if (f == NULL) {
12726             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12727             DisplayError(buf, errno);
12728             return FALSE;
12729         } else {
12730             safeStrCpy(buf, lastMsg, MSG_SIZ);
12731             DisplayMessage(_("Waiting for access to save file"), "");
12732             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12733             DisplayMessage(_("Saving game"), "");
12734             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12735             result = SaveGame(f, 0, NULL);
12736             DisplayMessage(buf, "");
12737             return result;
12738         }
12739     }
12740 }
12741
12742 char *
12743 SavePart (char *str)
12744 {
12745     static char buf[MSG_SIZ];
12746     char *p;
12747
12748     p = strchr(str, ' ');
12749     if (p == NULL) return str;
12750     strncpy(buf, str, p - str);
12751     buf[p - str] = NULLCHAR;
12752     return buf;
12753 }
12754
12755 #define PGN_MAX_LINE 75
12756
12757 #define PGN_SIDE_WHITE  0
12758 #define PGN_SIDE_BLACK  1
12759
12760 static int
12761 FindFirstMoveOutOfBook (int side)
12762 {
12763     int result = -1;
12764
12765     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12766         int index = backwardMostMove;
12767         int has_book_hit = 0;
12768
12769         if( (index % 2) != side ) {
12770             index++;
12771         }
12772
12773         while( index < forwardMostMove ) {
12774             /* Check to see if engine is in book */
12775             int depth = pvInfoList[index].depth;
12776             int score = pvInfoList[index].score;
12777             int in_book = 0;
12778
12779             if( depth <= 2 ) {
12780                 in_book = 1;
12781             }
12782             else if( score == 0 && depth == 63 ) {
12783                 in_book = 1; /* Zappa */
12784             }
12785             else if( score == 2 && depth == 99 ) {
12786                 in_book = 1; /* Abrok */
12787             }
12788
12789             has_book_hit += in_book;
12790
12791             if( ! in_book ) {
12792                 result = index;
12793
12794                 break;
12795             }
12796
12797             index += 2;
12798         }
12799     }
12800
12801     return result;
12802 }
12803
12804 void
12805 GetOutOfBookInfo (char * buf)
12806 {
12807     int oob[2];
12808     int i;
12809     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12810
12811     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12812     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12813
12814     *buf = '\0';
12815
12816     if( oob[0] >= 0 || oob[1] >= 0 ) {
12817         for( i=0; i<2; i++ ) {
12818             int idx = oob[i];
12819
12820             if( idx >= 0 ) {
12821                 if( i > 0 && oob[0] >= 0 ) {
12822                     strcat( buf, "   " );
12823                 }
12824
12825                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12826                 sprintf( buf+strlen(buf), "%s%.2f",
12827                     pvInfoList[idx].score >= 0 ? "+" : "",
12828                     pvInfoList[idx].score / 100.0 );
12829             }
12830         }
12831     }
12832 }
12833
12834 /* Save game in PGN style and close the file */
12835 int
12836 SaveGamePGN (FILE *f)
12837 {
12838     int i, offset, linelen, newblock;
12839 //    char *movetext;
12840     char numtext[32];
12841     int movelen, numlen, blank;
12842     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12843
12844     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12845
12846     PrintPGNTags(f, &gameInfo);
12847
12848     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12849
12850     if (backwardMostMove > 0 || startedFromSetupPosition) {
12851         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
12852         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12853         fprintf(f, "\n{--------------\n");
12854         PrintPosition(f, backwardMostMove);
12855         fprintf(f, "--------------}\n");
12856         free(fen);
12857     }
12858     else {
12859         /* [AS] Out of book annotation */
12860         if( appData.saveOutOfBookInfo ) {
12861             char buf[64];
12862
12863             GetOutOfBookInfo( buf );
12864
12865             if( buf[0] != '\0' ) {
12866                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12867             }
12868         }
12869
12870         fprintf(f, "\n");
12871     }
12872
12873     i = backwardMostMove;
12874     linelen = 0;
12875     newblock = TRUE;
12876
12877     while (i < forwardMostMove) {
12878         /* Print comments preceding this move */
12879         if (commentList[i] != NULL) {
12880             if (linelen > 0) fprintf(f, "\n");
12881             fprintf(f, "%s", commentList[i]);
12882             linelen = 0;
12883             newblock = TRUE;
12884         }
12885
12886         /* Format move number */
12887         if ((i % 2) == 0)
12888           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12889         else
12890           if (newblock)
12891             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12892           else
12893             numtext[0] = NULLCHAR;
12894
12895         numlen = strlen(numtext);
12896         newblock = FALSE;
12897
12898         /* Print move number */
12899         blank = linelen > 0 && numlen > 0;
12900         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12901             fprintf(f, "\n");
12902             linelen = 0;
12903             blank = 0;
12904         }
12905         if (blank) {
12906             fprintf(f, " ");
12907             linelen++;
12908         }
12909         fprintf(f, "%s", numtext);
12910         linelen += numlen;
12911
12912         /* Get move */
12913         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12914         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12915
12916         /* Print move */
12917         blank = linelen > 0 && movelen > 0;
12918         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12919             fprintf(f, "\n");
12920             linelen = 0;
12921             blank = 0;
12922         }
12923         if (blank) {
12924             fprintf(f, " ");
12925             linelen++;
12926         }
12927         fprintf(f, "%s", move_buffer);
12928         linelen += movelen;
12929
12930         /* [AS] Add PV info if present */
12931         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12932             /* [HGM] add time */
12933             char buf[MSG_SIZ]; int seconds;
12934
12935             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12936
12937             if( seconds <= 0)
12938               buf[0] = 0;
12939             else
12940               if( seconds < 30 )
12941                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12942               else
12943                 {
12944                   seconds = (seconds + 4)/10; // round to full seconds
12945                   if( seconds < 60 )
12946                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12947                   else
12948                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12949                 }
12950
12951             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12952                       pvInfoList[i].score >= 0 ? "+" : "",
12953                       pvInfoList[i].score / 100.0,
12954                       pvInfoList[i].depth,
12955                       buf );
12956
12957             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12958
12959             /* Print score/depth */
12960             blank = linelen > 0 && movelen > 0;
12961             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12962                 fprintf(f, "\n");
12963                 linelen = 0;
12964                 blank = 0;
12965             }
12966             if (blank) {
12967                 fprintf(f, " ");
12968                 linelen++;
12969             }
12970             fprintf(f, "%s", move_buffer);
12971             linelen += movelen;
12972         }
12973
12974         i++;
12975     }
12976
12977     /* Start a new line */
12978     if (linelen > 0) fprintf(f, "\n");
12979
12980     /* Print comments after last move */
12981     if (commentList[i] != NULL) {
12982         fprintf(f, "%s\n", commentList[i]);
12983     }
12984
12985     /* Print result */
12986     if (gameInfo.resultDetails != NULL &&
12987         gameInfo.resultDetails[0] != NULLCHAR) {
12988         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12989                 PGNResult(gameInfo.result));
12990     } else {
12991         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12992     }
12993
12994     fclose(f);
12995     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12996     return TRUE;
12997 }
12998
12999 /* Save game in old style and close the file */
13000 int
13001 SaveGameOldStyle (FILE *f)
13002 {
13003     int i, offset;
13004     time_t tm;
13005
13006     tm = time((time_t *) NULL);
13007
13008     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13009     PrintOpponents(f);
13010
13011     if (backwardMostMove > 0 || startedFromSetupPosition) {
13012         fprintf(f, "\n[--------------\n");
13013         PrintPosition(f, backwardMostMove);
13014         fprintf(f, "--------------]\n");
13015     } else {
13016         fprintf(f, "\n");
13017     }
13018
13019     i = backwardMostMove;
13020     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13021
13022     while (i < forwardMostMove) {
13023         if (commentList[i] != NULL) {
13024             fprintf(f, "[%s]\n", commentList[i]);
13025         }
13026
13027         if ((i % 2) == 1) {
13028             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13029             i++;
13030         } else {
13031             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13032             i++;
13033             if (commentList[i] != NULL) {
13034                 fprintf(f, "\n");
13035                 continue;
13036             }
13037             if (i >= forwardMostMove) {
13038                 fprintf(f, "\n");
13039                 break;
13040             }
13041             fprintf(f, "%s\n", parseList[i]);
13042             i++;
13043         }
13044     }
13045
13046     if (commentList[i] != NULL) {
13047         fprintf(f, "[%s]\n", commentList[i]);
13048     }
13049
13050     /* This isn't really the old style, but it's close enough */
13051     if (gameInfo.resultDetails != NULL &&
13052         gameInfo.resultDetails[0] != NULLCHAR) {
13053         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13054                 gameInfo.resultDetails);
13055     } else {
13056         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13057     }
13058
13059     fclose(f);
13060     return TRUE;
13061 }
13062
13063 /* Save the current game to open file f and close the file */
13064 int
13065 SaveGame (FILE *f, int dummy, char *dummy2)
13066 {
13067     if (gameMode == EditPosition) EditPositionDone(TRUE);
13068     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13069     if (appData.oldSaveStyle)
13070       return SaveGameOldStyle(f);
13071     else
13072       return SaveGamePGN(f);
13073 }
13074
13075 /* Save the current position to the given file */
13076 int
13077 SavePositionToFile (char *filename)
13078 {
13079     FILE *f;
13080     char buf[MSG_SIZ];
13081
13082     if (strcmp(filename, "-") == 0) {
13083         return SavePosition(stdout, 0, NULL);
13084     } else {
13085         f = fopen(filename, "a");
13086         if (f == NULL) {
13087             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13088             DisplayError(buf, errno);
13089             return FALSE;
13090         } else {
13091             safeStrCpy(buf, lastMsg, MSG_SIZ);
13092             DisplayMessage(_("Waiting for access to save file"), "");
13093             flock(fileno(f), LOCK_EX); // [HGM] lock
13094             DisplayMessage(_("Saving position"), "");
13095             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13096             SavePosition(f, 0, NULL);
13097             DisplayMessage(buf, "");
13098             return TRUE;
13099         }
13100     }
13101 }
13102
13103 /* Save the current position to the given open file and close the file */
13104 int
13105 SavePosition (FILE *f, int dummy, char *dummy2)
13106 {
13107     time_t tm;
13108     char *fen;
13109
13110     if (gameMode == EditPosition) EditPositionDone(TRUE);
13111     if (appData.oldSaveStyle) {
13112         tm = time((time_t *) NULL);
13113
13114         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13115         PrintOpponents(f);
13116         fprintf(f, "[--------------\n");
13117         PrintPosition(f, currentMove);
13118         fprintf(f, "--------------]\n");
13119     } else {
13120         fen = PositionToFEN(currentMove, NULL, 1);
13121         fprintf(f, "%s\n", fen);
13122         free(fen);
13123     }
13124     fclose(f);
13125     return TRUE;
13126 }
13127
13128 void
13129 ReloadCmailMsgEvent (int unregister)
13130 {
13131 #if !WIN32
13132     static char *inFilename = NULL;
13133     static char *outFilename;
13134     int i;
13135     struct stat inbuf, outbuf;
13136     int status;
13137
13138     /* Any registered moves are unregistered if unregister is set, */
13139     /* i.e. invoked by the signal handler */
13140     if (unregister) {
13141         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13142             cmailMoveRegistered[i] = FALSE;
13143             if (cmailCommentList[i] != NULL) {
13144                 free(cmailCommentList[i]);
13145                 cmailCommentList[i] = NULL;
13146             }
13147         }
13148         nCmailMovesRegistered = 0;
13149     }
13150
13151     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13152         cmailResult[i] = CMAIL_NOT_RESULT;
13153     }
13154     nCmailResults = 0;
13155
13156     if (inFilename == NULL) {
13157         /* Because the filenames are static they only get malloced once  */
13158         /* and they never get freed                                      */
13159         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13160         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13161
13162         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13163         sprintf(outFilename, "%s.out", appData.cmailGameName);
13164     }
13165
13166     status = stat(outFilename, &outbuf);
13167     if (status < 0) {
13168         cmailMailedMove = FALSE;
13169     } else {
13170         status = stat(inFilename, &inbuf);
13171         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13172     }
13173
13174     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13175        counts the games, notes how each one terminated, etc.
13176
13177        It would be nice to remove this kludge and instead gather all
13178        the information while building the game list.  (And to keep it
13179        in the game list nodes instead of having a bunch of fixed-size
13180        parallel arrays.)  Note this will require getting each game's
13181        termination from the PGN tags, as the game list builder does
13182        not process the game moves.  --mann
13183        */
13184     cmailMsgLoaded = TRUE;
13185     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13186
13187     /* Load first game in the file or popup game menu */
13188     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13189
13190 #endif /* !WIN32 */
13191     return;
13192 }
13193
13194 int
13195 RegisterMove ()
13196 {
13197     FILE *f;
13198     char string[MSG_SIZ];
13199
13200     if (   cmailMailedMove
13201         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13202         return TRUE;            /* Allow free viewing  */
13203     }
13204
13205     /* Unregister move to ensure that we don't leave RegisterMove        */
13206     /* with the move registered when the conditions for registering no   */
13207     /* longer hold                                                       */
13208     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13209         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13210         nCmailMovesRegistered --;
13211
13212         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13213           {
13214               free(cmailCommentList[lastLoadGameNumber - 1]);
13215               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13216           }
13217     }
13218
13219     if (cmailOldMove == -1) {
13220         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13221         return FALSE;
13222     }
13223
13224     if (currentMove > cmailOldMove + 1) {
13225         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13226         return FALSE;
13227     }
13228
13229     if (currentMove < cmailOldMove) {
13230         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13231         return FALSE;
13232     }
13233
13234     if (forwardMostMove > currentMove) {
13235         /* Silently truncate extra moves */
13236         TruncateGame();
13237     }
13238
13239     if (   (currentMove == cmailOldMove + 1)
13240         || (   (currentMove == cmailOldMove)
13241             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13242                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13243         if (gameInfo.result != GameUnfinished) {
13244             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13245         }
13246
13247         if (commentList[currentMove] != NULL) {
13248             cmailCommentList[lastLoadGameNumber - 1]
13249               = StrSave(commentList[currentMove]);
13250         }
13251         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13252
13253         if (appData.debugMode)
13254           fprintf(debugFP, "Saving %s for game %d\n",
13255                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13256
13257         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13258
13259         f = fopen(string, "w");
13260         if (appData.oldSaveStyle) {
13261             SaveGameOldStyle(f); /* also closes the file */
13262
13263             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13264             f = fopen(string, "w");
13265             SavePosition(f, 0, NULL); /* also closes the file */
13266         } else {
13267             fprintf(f, "{--------------\n");
13268             PrintPosition(f, currentMove);
13269             fprintf(f, "--------------}\n\n");
13270
13271             SaveGame(f, 0, NULL); /* also closes the file*/
13272         }
13273
13274         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13275         nCmailMovesRegistered ++;
13276     } else if (nCmailGames == 1) {
13277         DisplayError(_("You have not made a move yet"), 0);
13278         return FALSE;
13279     }
13280
13281     return TRUE;
13282 }
13283
13284 void
13285 MailMoveEvent ()
13286 {
13287 #if !WIN32
13288     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13289     FILE *commandOutput;
13290     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13291     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13292     int nBuffers;
13293     int i;
13294     int archived;
13295     char *arcDir;
13296
13297     if (! cmailMsgLoaded) {
13298         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13299         return;
13300     }
13301
13302     if (nCmailGames == nCmailResults) {
13303         DisplayError(_("No unfinished games"), 0);
13304         return;
13305     }
13306
13307 #if CMAIL_PROHIBIT_REMAIL
13308     if (cmailMailedMove) {
13309       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);
13310         DisplayError(msg, 0);
13311         return;
13312     }
13313 #endif
13314
13315     if (! (cmailMailedMove || RegisterMove())) return;
13316
13317     if (   cmailMailedMove
13318         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13319       snprintf(string, MSG_SIZ, partCommandString,
13320                appData.debugMode ? " -v" : "", appData.cmailGameName);
13321         commandOutput = popen(string, "r");
13322
13323         if (commandOutput == NULL) {
13324             DisplayError(_("Failed to invoke cmail"), 0);
13325         } else {
13326             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13327                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13328             }
13329             if (nBuffers > 1) {
13330                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13331                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13332                 nBytes = MSG_SIZ - 1;
13333             } else {
13334                 (void) memcpy(msg, buffer, nBytes);
13335             }
13336             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13337
13338             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13339                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13340
13341                 archived = TRUE;
13342                 for (i = 0; i < nCmailGames; i ++) {
13343                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13344                         archived = FALSE;
13345                     }
13346                 }
13347                 if (   archived
13348                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13349                         != NULL)) {
13350                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13351                            arcDir,
13352                            appData.cmailGameName,
13353                            gameInfo.date);
13354                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13355                     cmailMsgLoaded = FALSE;
13356                 }
13357             }
13358
13359             DisplayInformation(msg);
13360             pclose(commandOutput);
13361         }
13362     } else {
13363         if ((*cmailMsg) != '\0') {
13364             DisplayInformation(cmailMsg);
13365         }
13366     }
13367
13368     return;
13369 #endif /* !WIN32 */
13370 }
13371
13372 char *
13373 CmailMsg ()
13374 {
13375 #if WIN32
13376     return NULL;
13377 #else
13378     int  prependComma = 0;
13379     char number[5];
13380     char string[MSG_SIZ];       /* Space for game-list */
13381     int  i;
13382
13383     if (!cmailMsgLoaded) return "";
13384
13385     if (cmailMailedMove) {
13386       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13387     } else {
13388         /* Create a list of games left */
13389       snprintf(string, MSG_SIZ, "[");
13390         for (i = 0; i < nCmailGames; i ++) {
13391             if (! (   cmailMoveRegistered[i]
13392                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13393                 if (prependComma) {
13394                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13395                 } else {
13396                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13397                     prependComma = 1;
13398                 }
13399
13400                 strcat(string, number);
13401             }
13402         }
13403         strcat(string, "]");
13404
13405         if (nCmailMovesRegistered + nCmailResults == 0) {
13406             switch (nCmailGames) {
13407               case 1:
13408                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13409                 break;
13410
13411               case 2:
13412                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13413                 break;
13414
13415               default:
13416                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13417                          nCmailGames);
13418                 break;
13419             }
13420         } else {
13421             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13422               case 1:
13423                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13424                          string);
13425                 break;
13426
13427               case 0:
13428                 if (nCmailResults == nCmailGames) {
13429                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13430                 } else {
13431                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13432                 }
13433                 break;
13434
13435               default:
13436                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13437                          string);
13438             }
13439         }
13440     }
13441     return cmailMsg;
13442 #endif /* WIN32 */
13443 }
13444
13445 void
13446 ResetGameEvent ()
13447 {
13448     if (gameMode == Training)
13449       SetTrainingModeOff();
13450
13451     Reset(TRUE, TRUE);
13452     cmailMsgLoaded = FALSE;
13453     if (appData.icsActive) {
13454       SendToICS(ics_prefix);
13455       SendToICS("refresh\n");
13456     }
13457 }
13458
13459 void
13460 ExitEvent (int status)
13461 {
13462     exiting++;
13463     if (exiting > 2) {
13464       /* Give up on clean exit */
13465       exit(status);
13466     }
13467     if (exiting > 1) {
13468       /* Keep trying for clean exit */
13469       return;
13470     }
13471
13472     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13473
13474     if (telnetISR != NULL) {
13475       RemoveInputSource(telnetISR);
13476     }
13477     if (icsPR != NoProc) {
13478       DestroyChildProcess(icsPR, TRUE);
13479     }
13480
13481     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13482     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13483
13484     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13485     /* make sure this other one finishes before killing it!                  */
13486     if(endingGame) { int count = 0;
13487         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13488         while(endingGame && count++ < 10) DoSleep(1);
13489         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13490     }
13491
13492     /* Kill off chess programs */
13493     if (first.pr != NoProc) {
13494         ExitAnalyzeMode();
13495
13496         DoSleep( appData.delayBeforeQuit );
13497         SendToProgram("quit\n", &first);
13498         DoSleep( appData.delayAfterQuit );
13499         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13500     }
13501     if (second.pr != NoProc) {
13502         DoSleep( appData.delayBeforeQuit );
13503         SendToProgram("quit\n", &second);
13504         DoSleep( appData.delayAfterQuit );
13505         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13506     }
13507     if (first.isr != NULL) {
13508         RemoveInputSource(first.isr);
13509     }
13510     if (second.isr != NULL) {
13511         RemoveInputSource(second.isr);
13512     }
13513
13514     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13515     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13516
13517     ShutDownFrontEnd();
13518     exit(status);
13519 }
13520
13521 void
13522 PauseEngine (ChessProgramState *cps)
13523 {
13524     SendToProgram("pause\n", cps);
13525     cps->pause = 2;
13526 }
13527
13528 void
13529 UnPauseEngine (ChessProgramState *cps)
13530 {
13531     SendToProgram("resume\n", cps);
13532     cps->pause = 1;
13533 }
13534
13535 void
13536 PauseEvent ()
13537 {
13538     if (appData.debugMode)
13539         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13540     if (pausing) {
13541         pausing = FALSE;
13542         ModeHighlight();
13543         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13544             StartClocks();
13545             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13546                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13547                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13548             }
13549             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13550             HandleMachineMove(stashedInputMove, stalledEngine);
13551             stalledEngine = NULL;
13552             return;
13553         }
13554         if (gameMode == MachinePlaysWhite ||
13555             gameMode == TwoMachinesPlay   ||
13556             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13557             if(first.pause)  UnPauseEngine(&first);
13558             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13559             if(second.pause) UnPauseEngine(&second);
13560             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13561             StartClocks();
13562         } else {
13563             DisplayBothClocks();
13564         }
13565         if (gameMode == PlayFromGameFile) {
13566             if (appData.timeDelay >= 0)
13567                 AutoPlayGameLoop();
13568         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13569             Reset(FALSE, TRUE);
13570             SendToICS(ics_prefix);
13571             SendToICS("refresh\n");
13572         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13573             ForwardInner(forwardMostMove);
13574         }
13575         pauseExamInvalid = FALSE;
13576     } else {
13577         switch (gameMode) {
13578           default:
13579             return;
13580           case IcsExamining:
13581             pauseExamForwardMostMove = forwardMostMove;
13582             pauseExamInvalid = FALSE;
13583             /* fall through */
13584           case IcsObserving:
13585           case IcsPlayingWhite:
13586           case IcsPlayingBlack:
13587             pausing = TRUE;
13588             ModeHighlight();
13589             return;
13590           case PlayFromGameFile:
13591             (void) StopLoadGameTimer();
13592             pausing = TRUE;
13593             ModeHighlight();
13594             break;
13595           case BeginningOfGame:
13596             if (appData.icsActive) return;
13597             /* else fall through */
13598           case MachinePlaysWhite:
13599           case MachinePlaysBlack:
13600           case TwoMachinesPlay:
13601             if (forwardMostMove == 0)
13602               return;           /* don't pause if no one has moved */
13603             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13604                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13605                 if(onMove->pause) {           // thinking engine can be paused
13606                     PauseEngine(onMove);      // do it
13607                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13608                         PauseEngine(onMove->other);
13609                     else
13610                         SendToProgram("easy\n", onMove->other);
13611                     StopClocks();
13612                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13613             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13614                 if(first.pause) {
13615                     PauseEngine(&first);
13616                     StopClocks();
13617                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13618             } else { // human on move, pause pondering by either method
13619                 if(first.pause)
13620                     PauseEngine(&first);
13621                 else if(appData.ponderNextMove)
13622                     SendToProgram("easy\n", &first);
13623                 StopClocks();
13624             }
13625             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13626           case AnalyzeMode:
13627             pausing = TRUE;
13628             ModeHighlight();
13629             break;
13630         }
13631     }
13632 }
13633
13634 void
13635 EditCommentEvent ()
13636 {
13637     char title[MSG_SIZ];
13638
13639     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13640       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13641     } else {
13642       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13643                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13644                parseList[currentMove - 1]);
13645     }
13646
13647     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13648 }
13649
13650
13651 void
13652 EditTagsEvent ()
13653 {
13654     char *tags = PGNTags(&gameInfo);
13655     bookUp = FALSE;
13656     EditTagsPopUp(tags, NULL);
13657     free(tags);
13658 }
13659
13660 void
13661 ToggleSecond ()
13662 {
13663   if(second.analyzing) {
13664     SendToProgram("exit\n", &second);
13665     second.analyzing = FALSE;
13666   } else {
13667     if (second.pr == NoProc) StartChessProgram(&second);
13668     InitChessProgram(&second, FALSE);
13669     FeedMovesToProgram(&second, currentMove);
13670
13671     SendToProgram("analyze\n", &second);
13672     second.analyzing = TRUE;
13673   }
13674 }
13675
13676 /* Toggle ShowThinking */
13677 void
13678 ToggleShowThinking()
13679 {
13680   appData.showThinking = !appData.showThinking;
13681   ShowThinkingEvent();
13682 }
13683
13684 int
13685 AnalyzeModeEvent ()
13686 {
13687     char buf[MSG_SIZ];
13688
13689     if (!first.analysisSupport) {
13690       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13691       DisplayError(buf, 0);
13692       return 0;
13693     }
13694     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13695     if (appData.icsActive) {
13696         if (gameMode != IcsObserving) {
13697           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13698             DisplayError(buf, 0);
13699             /* secure check */
13700             if (appData.icsEngineAnalyze) {
13701                 if (appData.debugMode)
13702                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13703                 ExitAnalyzeMode();
13704                 ModeHighlight();
13705             }
13706             return 0;
13707         }
13708         /* if enable, user wants to disable icsEngineAnalyze */
13709         if (appData.icsEngineAnalyze) {
13710                 ExitAnalyzeMode();
13711                 ModeHighlight();
13712                 return 0;
13713         }
13714         appData.icsEngineAnalyze = TRUE;
13715         if (appData.debugMode)
13716             fprintf(debugFP, "ICS engine analyze starting... \n");
13717     }
13718
13719     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13720     if (appData.noChessProgram || gameMode == AnalyzeMode)
13721       return 0;
13722
13723     if (gameMode != AnalyzeFile) {
13724         if (!appData.icsEngineAnalyze) {
13725                EditGameEvent();
13726                if (gameMode != EditGame) return 0;
13727         }
13728         if (!appData.showThinking) ToggleShowThinking();
13729         ResurrectChessProgram();
13730         SendToProgram("analyze\n", &first);
13731         first.analyzing = TRUE;
13732         /*first.maybeThinking = TRUE;*/
13733         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13734         EngineOutputPopUp();
13735     }
13736     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13737     pausing = FALSE;
13738     ModeHighlight();
13739     SetGameInfo();
13740
13741     StartAnalysisClock();
13742     GetTimeMark(&lastNodeCountTime);
13743     lastNodeCount = 0;
13744     return 1;
13745 }
13746
13747 void
13748 AnalyzeFileEvent ()
13749 {
13750     if (appData.noChessProgram || gameMode == AnalyzeFile)
13751       return;
13752
13753     if (!first.analysisSupport) {
13754       char buf[MSG_SIZ];
13755       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13756       DisplayError(buf, 0);
13757       return;
13758     }
13759
13760     if (gameMode != AnalyzeMode) {
13761         keepInfo = 1; // mere annotating should not alter PGN tags
13762         EditGameEvent();
13763         keepInfo = 0;
13764         if (gameMode != EditGame) return;
13765         if (!appData.showThinking) ToggleShowThinking();
13766         ResurrectChessProgram();
13767         SendToProgram("analyze\n", &first);
13768         first.analyzing = TRUE;
13769         /*first.maybeThinking = TRUE;*/
13770         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13771         EngineOutputPopUp();
13772     }
13773     gameMode = AnalyzeFile;
13774     pausing = FALSE;
13775     ModeHighlight();
13776
13777     StartAnalysisClock();
13778     GetTimeMark(&lastNodeCountTime);
13779     lastNodeCount = 0;
13780     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13781     AnalysisPeriodicEvent(1);
13782 }
13783
13784 void
13785 MachineWhiteEvent ()
13786 {
13787     char buf[MSG_SIZ];
13788     char *bookHit = NULL;
13789
13790     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13791       return;
13792
13793
13794     if (gameMode == PlayFromGameFile ||
13795         gameMode == TwoMachinesPlay  ||
13796         gameMode == Training         ||
13797         gameMode == AnalyzeMode      ||
13798         gameMode == EndOfGame)
13799         EditGameEvent();
13800
13801     if (gameMode == EditPosition)
13802         EditPositionDone(TRUE);
13803
13804     if (!WhiteOnMove(currentMove)) {
13805         DisplayError(_("It is not White's turn"), 0);
13806         return;
13807     }
13808
13809     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13810       ExitAnalyzeMode();
13811
13812     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13813         gameMode == AnalyzeFile)
13814         TruncateGame();
13815
13816     ResurrectChessProgram();    /* in case it isn't running */
13817     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13818         gameMode = MachinePlaysWhite;
13819         ResetClocks();
13820     } else
13821     gameMode = MachinePlaysWhite;
13822     pausing = FALSE;
13823     ModeHighlight();
13824     SetGameInfo();
13825     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13826     DisplayTitle(buf);
13827     if (first.sendName) {
13828       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13829       SendToProgram(buf, &first);
13830     }
13831     if (first.sendTime) {
13832       if (first.useColors) {
13833         SendToProgram("black\n", &first); /*gnu kludge*/
13834       }
13835       SendTimeRemaining(&first, TRUE);
13836     }
13837     if (first.useColors) {
13838       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13839     }
13840     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13841     SetMachineThinkingEnables();
13842     first.maybeThinking = TRUE;
13843     StartClocks();
13844     firstMove = FALSE;
13845
13846     if (appData.autoFlipView && !flipView) {
13847       flipView = !flipView;
13848       DrawPosition(FALSE, NULL);
13849       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13850     }
13851
13852     if(bookHit) { // [HGM] book: simulate book reply
13853         static char bookMove[MSG_SIZ]; // a bit generous?
13854
13855         programStats.nodes = programStats.depth = programStats.time =
13856         programStats.score = programStats.got_only_move = 0;
13857         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13858
13859         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13860         strcat(bookMove, bookHit);
13861         HandleMachineMove(bookMove, &first);
13862     }
13863 }
13864
13865 void
13866 MachineBlackEvent ()
13867 {
13868   char buf[MSG_SIZ];
13869   char *bookHit = NULL;
13870
13871     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13872         return;
13873
13874
13875     if (gameMode == PlayFromGameFile ||
13876         gameMode == TwoMachinesPlay  ||
13877         gameMode == Training         ||
13878         gameMode == AnalyzeMode      ||
13879         gameMode == EndOfGame)
13880         EditGameEvent();
13881
13882     if (gameMode == EditPosition)
13883         EditPositionDone(TRUE);
13884
13885     if (WhiteOnMove(currentMove)) {
13886         DisplayError(_("It is not Black's turn"), 0);
13887         return;
13888     }
13889
13890     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13891       ExitAnalyzeMode();
13892
13893     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13894         gameMode == AnalyzeFile)
13895         TruncateGame();
13896
13897     ResurrectChessProgram();    /* in case it isn't running */
13898     gameMode = MachinePlaysBlack;
13899     pausing = FALSE;
13900     ModeHighlight();
13901     SetGameInfo();
13902     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13903     DisplayTitle(buf);
13904     if (first.sendName) {
13905       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13906       SendToProgram(buf, &first);
13907     }
13908     if (first.sendTime) {
13909       if (first.useColors) {
13910         SendToProgram("white\n", &first); /*gnu kludge*/
13911       }
13912       SendTimeRemaining(&first, FALSE);
13913     }
13914     if (first.useColors) {
13915       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13916     }
13917     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13918     SetMachineThinkingEnables();
13919     first.maybeThinking = TRUE;
13920     StartClocks();
13921
13922     if (appData.autoFlipView && flipView) {
13923       flipView = !flipView;
13924       DrawPosition(FALSE, NULL);
13925       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13926     }
13927     if(bookHit) { // [HGM] book: simulate book reply
13928         static char bookMove[MSG_SIZ]; // a bit generous?
13929
13930         programStats.nodes = programStats.depth = programStats.time =
13931         programStats.score = programStats.got_only_move = 0;
13932         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13933
13934         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13935         strcat(bookMove, bookHit);
13936         HandleMachineMove(bookMove, &first);
13937     }
13938 }
13939
13940
13941 void
13942 DisplayTwoMachinesTitle ()
13943 {
13944     char buf[MSG_SIZ];
13945     if (appData.matchGames > 0) {
13946         if(appData.tourneyFile[0]) {
13947           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13948                    gameInfo.white, _("vs."), gameInfo.black,
13949                    nextGame+1, appData.matchGames+1,
13950                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13951         } else
13952         if (first.twoMachinesColor[0] == 'w') {
13953           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13954                    gameInfo.white, _("vs."),  gameInfo.black,
13955                    first.matchWins, second.matchWins,
13956                    matchGame - 1 - (first.matchWins + second.matchWins));
13957         } else {
13958           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13959                    gameInfo.white, _("vs."), gameInfo.black,
13960                    second.matchWins, first.matchWins,
13961                    matchGame - 1 - (first.matchWins + second.matchWins));
13962         }
13963     } else {
13964       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13965     }
13966     DisplayTitle(buf);
13967 }
13968
13969 void
13970 SettingsMenuIfReady ()
13971 {
13972   if (second.lastPing != second.lastPong) {
13973     DisplayMessage("", _("Waiting for second chess program"));
13974     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13975     return;
13976   }
13977   ThawUI();
13978   DisplayMessage("", "");
13979   SettingsPopUp(&second);
13980 }
13981
13982 int
13983 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13984 {
13985     char buf[MSG_SIZ];
13986     if (cps->pr == NoProc) {
13987         StartChessProgram(cps);
13988         if (cps->protocolVersion == 1) {
13989           retry();
13990           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
13991         } else {
13992           /* kludge: allow timeout for initial "feature" command */
13993           if(retry != TwoMachinesEventIfReady) FreezeUI();
13994           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13995           DisplayMessage("", buf);
13996           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13997         }
13998         return 1;
13999     }
14000     return 0;
14001 }
14002
14003 void
14004 TwoMachinesEvent P((void))
14005 {
14006     int i;
14007     char buf[MSG_SIZ];
14008     ChessProgramState *onmove;
14009     char *bookHit = NULL;
14010     static int stalling = 0;
14011     TimeMark now;
14012     long wait;
14013
14014     if (appData.noChessProgram) return;
14015
14016     switch (gameMode) {
14017       case TwoMachinesPlay:
14018         return;
14019       case MachinePlaysWhite:
14020       case MachinePlaysBlack:
14021         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14022             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14023             return;
14024         }
14025         /* fall through */
14026       case BeginningOfGame:
14027       case PlayFromGameFile:
14028       case EndOfGame:
14029         EditGameEvent();
14030         if (gameMode != EditGame) return;
14031         break;
14032       case EditPosition:
14033         EditPositionDone(TRUE);
14034         break;
14035       case AnalyzeMode:
14036       case AnalyzeFile:
14037         ExitAnalyzeMode();
14038         break;
14039       case EditGame:
14040       default:
14041         break;
14042     }
14043
14044 //    forwardMostMove = currentMove;
14045     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14046     startingEngine = TRUE;
14047
14048     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14049
14050     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14051     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14052       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14053       return;
14054     }
14055     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14056
14057     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14058         startingEngine = FALSE;
14059         DisplayError("second engine does not play this", 0);
14060         return;
14061     }
14062
14063     if(!stalling) {
14064       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14065       SendToProgram("force\n", &second);
14066       stalling = 1;
14067       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14068       return;
14069     }
14070     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14071     if(appData.matchPause>10000 || appData.matchPause<10)
14072                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14073     wait = SubtractTimeMarks(&now, &pauseStart);
14074     if(wait < appData.matchPause) {
14075         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14076         return;
14077     }
14078     // we are now committed to starting the game
14079     stalling = 0;
14080     DisplayMessage("", "");
14081     if (startedFromSetupPosition) {
14082         SendBoard(&second, backwardMostMove);
14083     if (appData.debugMode) {
14084         fprintf(debugFP, "Two Machines\n");
14085     }
14086     }
14087     for (i = backwardMostMove; i < forwardMostMove; i++) {
14088         SendMoveToProgram(i, &second);
14089     }
14090
14091     gameMode = TwoMachinesPlay;
14092     pausing = startingEngine = FALSE;
14093     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14094     SetGameInfo();
14095     DisplayTwoMachinesTitle();
14096     firstMove = TRUE;
14097     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14098         onmove = &first;
14099     } else {
14100         onmove = &second;
14101     }
14102     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14103     SendToProgram(first.computerString, &first);
14104     if (first.sendName) {
14105       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14106       SendToProgram(buf, &first);
14107     }
14108     SendToProgram(second.computerString, &second);
14109     if (second.sendName) {
14110       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14111       SendToProgram(buf, &second);
14112     }
14113
14114     ResetClocks();
14115     if (!first.sendTime || !second.sendTime) {
14116         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14117         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14118     }
14119     if (onmove->sendTime) {
14120       if (onmove->useColors) {
14121         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14122       }
14123       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14124     }
14125     if (onmove->useColors) {
14126       SendToProgram(onmove->twoMachinesColor, onmove);
14127     }
14128     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14129 //    SendToProgram("go\n", onmove);
14130     onmove->maybeThinking = TRUE;
14131     SetMachineThinkingEnables();
14132
14133     StartClocks();
14134
14135     if(bookHit) { // [HGM] book: simulate book reply
14136         static char bookMove[MSG_SIZ]; // a bit generous?
14137
14138         programStats.nodes = programStats.depth = programStats.time =
14139         programStats.score = programStats.got_only_move = 0;
14140         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14141
14142         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14143         strcat(bookMove, bookHit);
14144         savedMessage = bookMove; // args for deferred call
14145         savedState = onmove;
14146         ScheduleDelayedEvent(DeferredBookMove, 1);
14147     }
14148 }
14149
14150 void
14151 TrainingEvent ()
14152 {
14153     if (gameMode == Training) {
14154       SetTrainingModeOff();
14155       gameMode = PlayFromGameFile;
14156       DisplayMessage("", _("Training mode off"));
14157     } else {
14158       gameMode = Training;
14159       animateTraining = appData.animate;
14160
14161       /* make sure we are not already at the end of the game */
14162       if (currentMove < forwardMostMove) {
14163         SetTrainingModeOn();
14164         DisplayMessage("", _("Training mode on"));
14165       } else {
14166         gameMode = PlayFromGameFile;
14167         DisplayError(_("Already at end of game"), 0);
14168       }
14169     }
14170     ModeHighlight();
14171 }
14172
14173 void
14174 IcsClientEvent ()
14175 {
14176     if (!appData.icsActive) return;
14177     switch (gameMode) {
14178       case IcsPlayingWhite:
14179       case IcsPlayingBlack:
14180       case IcsObserving:
14181       case IcsIdle:
14182       case BeginningOfGame:
14183       case IcsExamining:
14184         return;
14185
14186       case EditGame:
14187         break;
14188
14189       case EditPosition:
14190         EditPositionDone(TRUE);
14191         break;
14192
14193       case AnalyzeMode:
14194       case AnalyzeFile:
14195         ExitAnalyzeMode();
14196         break;
14197
14198       default:
14199         EditGameEvent();
14200         break;
14201     }
14202
14203     gameMode = IcsIdle;
14204     ModeHighlight();
14205     return;
14206 }
14207
14208 void
14209 EditGameEvent ()
14210 {
14211     int i;
14212
14213     switch (gameMode) {
14214       case Training:
14215         SetTrainingModeOff();
14216         break;
14217       case MachinePlaysWhite:
14218       case MachinePlaysBlack:
14219       case BeginningOfGame:
14220         SendToProgram("force\n", &first);
14221         SetUserThinkingEnables();
14222         break;
14223       case PlayFromGameFile:
14224         (void) StopLoadGameTimer();
14225         if (gameFileFP != NULL) {
14226             gameFileFP = NULL;
14227         }
14228         break;
14229       case EditPosition:
14230         EditPositionDone(TRUE);
14231         break;
14232       case AnalyzeMode:
14233       case AnalyzeFile:
14234         ExitAnalyzeMode();
14235         SendToProgram("force\n", &first);
14236         break;
14237       case TwoMachinesPlay:
14238         GameEnds(EndOfFile, NULL, GE_PLAYER);
14239         ResurrectChessProgram();
14240         SetUserThinkingEnables();
14241         break;
14242       case EndOfGame:
14243         ResurrectChessProgram();
14244         break;
14245       case IcsPlayingBlack:
14246       case IcsPlayingWhite:
14247         DisplayError(_("Warning: You are still playing a game"), 0);
14248         break;
14249       case IcsObserving:
14250         DisplayError(_("Warning: You are still observing a game"), 0);
14251         break;
14252       case IcsExamining:
14253         DisplayError(_("Warning: You are still examining a game"), 0);
14254         break;
14255       case IcsIdle:
14256         break;
14257       case EditGame:
14258       default:
14259         return;
14260     }
14261
14262     pausing = FALSE;
14263     StopClocks();
14264     first.offeredDraw = second.offeredDraw = 0;
14265
14266     if (gameMode == PlayFromGameFile) {
14267         whiteTimeRemaining = timeRemaining[0][currentMove];
14268         blackTimeRemaining = timeRemaining[1][currentMove];
14269         DisplayTitle("");
14270     }
14271
14272     if (gameMode == MachinePlaysWhite ||
14273         gameMode == MachinePlaysBlack ||
14274         gameMode == TwoMachinesPlay ||
14275         gameMode == EndOfGame) {
14276         i = forwardMostMove;
14277         while (i > currentMove) {
14278             SendToProgram("undo\n", &first);
14279             i--;
14280         }
14281         if(!adjustedClock) {
14282         whiteTimeRemaining = timeRemaining[0][currentMove];
14283         blackTimeRemaining = timeRemaining[1][currentMove];
14284         DisplayBothClocks();
14285         }
14286         if (whiteFlag || blackFlag) {
14287             whiteFlag = blackFlag = 0;
14288         }
14289         DisplayTitle("");
14290     }
14291
14292     gameMode = EditGame;
14293     ModeHighlight();
14294     SetGameInfo();
14295 }
14296
14297
14298 void
14299 EditPositionEvent ()
14300 {
14301     if (gameMode == EditPosition) {
14302         EditGameEvent();
14303         return;
14304     }
14305
14306     EditGameEvent();
14307     if (gameMode != EditGame) return;
14308
14309     gameMode = EditPosition;
14310     ModeHighlight();
14311     SetGameInfo();
14312     if (currentMove > 0)
14313       CopyBoard(boards[0], boards[currentMove]);
14314
14315     blackPlaysFirst = !WhiteOnMove(currentMove);
14316     ResetClocks();
14317     currentMove = forwardMostMove = backwardMostMove = 0;
14318     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14319     DisplayMove(-1);
14320     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14321 }
14322
14323 void
14324 ExitAnalyzeMode ()
14325 {
14326     /* [DM] icsEngineAnalyze - possible call from other functions */
14327     if (appData.icsEngineAnalyze) {
14328         appData.icsEngineAnalyze = FALSE;
14329
14330         DisplayMessage("",_("Close ICS engine analyze..."));
14331     }
14332     if (first.analysisSupport && first.analyzing) {
14333       SendToBoth("exit\n");
14334       first.analyzing = second.analyzing = FALSE;
14335     }
14336     thinkOutput[0] = NULLCHAR;
14337 }
14338
14339 void
14340 EditPositionDone (Boolean fakeRights)
14341 {
14342     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14343
14344     startedFromSetupPosition = TRUE;
14345     InitChessProgram(&first, FALSE);
14346     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14347       boards[0][EP_STATUS] = EP_NONE;
14348       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14349       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14350         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14351         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14352       } else boards[0][CASTLING][2] = NoRights;
14353       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14354         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14355         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14356       } else boards[0][CASTLING][5] = NoRights;
14357       if(gameInfo.variant == VariantSChess) {
14358         int i;
14359         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14360           boards[0][VIRGIN][i] = 0;
14361           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14362           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14363         }
14364       }
14365     }
14366     SendToProgram("force\n", &first);
14367     if (blackPlaysFirst) {
14368         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14369         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14370         currentMove = forwardMostMove = backwardMostMove = 1;
14371         CopyBoard(boards[1], boards[0]);
14372     } else {
14373         currentMove = forwardMostMove = backwardMostMove = 0;
14374     }
14375     SendBoard(&first, forwardMostMove);
14376     if (appData.debugMode) {
14377         fprintf(debugFP, "EditPosDone\n");
14378     }
14379     DisplayTitle("");
14380     DisplayMessage("", "");
14381     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14382     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14383     gameMode = EditGame;
14384     ModeHighlight();
14385     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14386     ClearHighlights(); /* [AS] */
14387 }
14388
14389 /* Pause for `ms' milliseconds */
14390 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14391 void
14392 TimeDelay (long ms)
14393 {
14394     TimeMark m1, m2;
14395
14396     GetTimeMark(&m1);
14397     do {
14398         GetTimeMark(&m2);
14399     } while (SubtractTimeMarks(&m2, &m1) < ms);
14400 }
14401
14402 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14403 void
14404 SendMultiLineToICS (char *buf)
14405 {
14406     char temp[MSG_SIZ+1], *p;
14407     int len;
14408
14409     len = strlen(buf);
14410     if (len > MSG_SIZ)
14411       len = MSG_SIZ;
14412
14413     strncpy(temp, buf, len);
14414     temp[len] = 0;
14415
14416     p = temp;
14417     while (*p) {
14418         if (*p == '\n' || *p == '\r')
14419           *p = ' ';
14420         ++p;
14421     }
14422
14423     strcat(temp, "\n");
14424     SendToICS(temp);
14425     SendToPlayer(temp, strlen(temp));
14426 }
14427
14428 void
14429 SetWhiteToPlayEvent ()
14430 {
14431     if (gameMode == EditPosition) {
14432         blackPlaysFirst = FALSE;
14433         DisplayBothClocks();    /* works because currentMove is 0 */
14434     } else if (gameMode == IcsExamining) {
14435         SendToICS(ics_prefix);
14436         SendToICS("tomove white\n");
14437     }
14438 }
14439
14440 void
14441 SetBlackToPlayEvent ()
14442 {
14443     if (gameMode == EditPosition) {
14444         blackPlaysFirst = TRUE;
14445         currentMove = 1;        /* kludge */
14446         DisplayBothClocks();
14447         currentMove = 0;
14448     } else if (gameMode == IcsExamining) {
14449         SendToICS(ics_prefix);
14450         SendToICS("tomove black\n");
14451     }
14452 }
14453
14454 void
14455 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14456 {
14457     char buf[MSG_SIZ];
14458     ChessSquare piece = boards[0][y][x];
14459
14460     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14461
14462     switch (selection) {
14463       case ClearBoard:
14464         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14465             SendToICS(ics_prefix);
14466             SendToICS("bsetup clear\n");
14467         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14468             SendToICS(ics_prefix);
14469             SendToICS("clearboard\n");
14470         } else {
14471             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14472                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14473                 for (y = 0; y < BOARD_HEIGHT; y++) {
14474                     if (gameMode == IcsExamining) {
14475                         if (boards[currentMove][y][x] != EmptySquare) {
14476                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14477                                     AAA + x, ONE + y);
14478                             SendToICS(buf);
14479                         }
14480                     } else {
14481                         boards[0][y][x] = p;
14482                     }
14483                 }
14484             }
14485         }
14486         if (gameMode == EditPosition) {
14487             DrawPosition(FALSE, boards[0]);
14488         }
14489         break;
14490
14491       case WhitePlay:
14492         SetWhiteToPlayEvent();
14493         break;
14494
14495       case BlackPlay:
14496         SetBlackToPlayEvent();
14497         break;
14498
14499       case EmptySquare:
14500         if (gameMode == IcsExamining) {
14501             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14502             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14503             SendToICS(buf);
14504         } else {
14505             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14506                 if(x == BOARD_LEFT-2) {
14507                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14508                     boards[0][y][1] = 0;
14509                 } else
14510                 if(x == BOARD_RGHT+1) {
14511                     if(y >= gameInfo.holdingsSize) break;
14512                     boards[0][y][BOARD_WIDTH-2] = 0;
14513                 } else break;
14514             }
14515             boards[0][y][x] = EmptySquare;
14516             DrawPosition(FALSE, boards[0]);
14517         }
14518         break;
14519
14520       case PromotePiece:
14521         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14522            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14523             selection = (ChessSquare) (PROMOTED piece);
14524         } else if(piece == EmptySquare) selection = WhiteSilver;
14525         else selection = (ChessSquare)((int)piece - 1);
14526         goto defaultlabel;
14527
14528       case DemotePiece:
14529         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14530            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14531             selection = (ChessSquare) (DEMOTED piece);
14532         } else if(piece == EmptySquare) selection = BlackSilver;
14533         else selection = (ChessSquare)((int)piece + 1);
14534         goto defaultlabel;
14535
14536       case WhiteQueen:
14537       case BlackQueen:
14538         if(gameInfo.variant == VariantShatranj ||
14539            gameInfo.variant == VariantXiangqi  ||
14540            gameInfo.variant == VariantCourier  ||
14541            gameInfo.variant == VariantASEAN    ||
14542            gameInfo.variant == VariantMakruk     )
14543             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14544         goto defaultlabel;
14545
14546       case WhiteKing:
14547       case BlackKing:
14548         if(gameInfo.variant == VariantXiangqi)
14549             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14550         if(gameInfo.variant == VariantKnightmate)
14551             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14552       default:
14553         defaultlabel:
14554         if (gameMode == IcsExamining) {
14555             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14556             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14557                      PieceToChar(selection), AAA + x, ONE + y);
14558             SendToICS(buf);
14559         } else {
14560             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14561                 int n;
14562                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14563                     n = PieceToNumber(selection - BlackPawn);
14564                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14565                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14566                     boards[0][BOARD_HEIGHT-1-n][1]++;
14567                 } else
14568                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14569                     n = PieceToNumber(selection);
14570                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14571                     boards[0][n][BOARD_WIDTH-1] = selection;
14572                     boards[0][n][BOARD_WIDTH-2]++;
14573                 }
14574             } else
14575             boards[0][y][x] = selection;
14576             DrawPosition(TRUE, boards[0]);
14577             ClearHighlights();
14578             fromX = fromY = -1;
14579         }
14580         break;
14581     }
14582 }
14583
14584
14585 void
14586 DropMenuEvent (ChessSquare selection, int x, int y)
14587 {
14588     ChessMove moveType;
14589
14590     switch (gameMode) {
14591       case IcsPlayingWhite:
14592       case MachinePlaysBlack:
14593         if (!WhiteOnMove(currentMove)) {
14594             DisplayMoveError(_("It is Black's turn"));
14595             return;
14596         }
14597         moveType = WhiteDrop;
14598         break;
14599       case IcsPlayingBlack:
14600       case MachinePlaysWhite:
14601         if (WhiteOnMove(currentMove)) {
14602             DisplayMoveError(_("It is White's turn"));
14603             return;
14604         }
14605         moveType = BlackDrop;
14606         break;
14607       case EditGame:
14608         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14609         break;
14610       default:
14611         return;
14612     }
14613
14614     if (moveType == BlackDrop && selection < BlackPawn) {
14615       selection = (ChessSquare) ((int) selection
14616                                  + (int) BlackPawn - (int) WhitePawn);
14617     }
14618     if (boards[currentMove][y][x] != EmptySquare) {
14619         DisplayMoveError(_("That square is occupied"));
14620         return;
14621     }
14622
14623     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14624 }
14625
14626 void
14627 AcceptEvent ()
14628 {
14629     /* Accept a pending offer of any kind from opponent */
14630
14631     if (appData.icsActive) {
14632         SendToICS(ics_prefix);
14633         SendToICS("accept\n");
14634     } else if (cmailMsgLoaded) {
14635         if (currentMove == cmailOldMove &&
14636             commentList[cmailOldMove] != NULL &&
14637             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14638                    "Black offers a draw" : "White offers a draw")) {
14639             TruncateGame();
14640             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14641             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14642         } else {
14643             DisplayError(_("There is no pending offer on this move"), 0);
14644             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14645         }
14646     } else {
14647         /* Not used for offers from chess program */
14648     }
14649 }
14650
14651 void
14652 DeclineEvent ()
14653 {
14654     /* Decline a pending offer of any kind from opponent */
14655
14656     if (appData.icsActive) {
14657         SendToICS(ics_prefix);
14658         SendToICS("decline\n");
14659     } else if (cmailMsgLoaded) {
14660         if (currentMove == cmailOldMove &&
14661             commentList[cmailOldMove] != NULL &&
14662             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14663                    "Black offers a draw" : "White offers a draw")) {
14664 #ifdef NOTDEF
14665             AppendComment(cmailOldMove, "Draw declined", TRUE);
14666             DisplayComment(cmailOldMove - 1, "Draw declined");
14667 #endif /*NOTDEF*/
14668         } else {
14669             DisplayError(_("There is no pending offer on this move"), 0);
14670         }
14671     } else {
14672         /* Not used for offers from chess program */
14673     }
14674 }
14675
14676 void
14677 RematchEvent ()
14678 {
14679     /* Issue ICS rematch command */
14680     if (appData.icsActive) {
14681         SendToICS(ics_prefix);
14682         SendToICS("rematch\n");
14683     }
14684 }
14685
14686 void
14687 CallFlagEvent ()
14688 {
14689     /* Call your opponent's flag (claim a win on time) */
14690     if (appData.icsActive) {
14691         SendToICS(ics_prefix);
14692         SendToICS("flag\n");
14693     } else {
14694         switch (gameMode) {
14695           default:
14696             return;
14697           case MachinePlaysWhite:
14698             if (whiteFlag) {
14699                 if (blackFlag)
14700                   GameEnds(GameIsDrawn, "Both players ran out of time",
14701                            GE_PLAYER);
14702                 else
14703                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14704             } else {
14705                 DisplayError(_("Your opponent is not out of time"), 0);
14706             }
14707             break;
14708           case MachinePlaysBlack:
14709             if (blackFlag) {
14710                 if (whiteFlag)
14711                   GameEnds(GameIsDrawn, "Both players ran out of time",
14712                            GE_PLAYER);
14713                 else
14714                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14715             } else {
14716                 DisplayError(_("Your opponent is not out of time"), 0);
14717             }
14718             break;
14719         }
14720     }
14721 }
14722
14723 void
14724 ClockClick (int which)
14725 {       // [HGM] code moved to back-end from winboard.c
14726         if(which) { // black clock
14727           if (gameMode == EditPosition || gameMode == IcsExamining) {
14728             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14729             SetBlackToPlayEvent();
14730           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14731           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14732           } else if (shiftKey) {
14733             AdjustClock(which, -1);
14734           } else if (gameMode == IcsPlayingWhite ||
14735                      gameMode == MachinePlaysBlack) {
14736             CallFlagEvent();
14737           }
14738         } else { // white clock
14739           if (gameMode == EditPosition || gameMode == IcsExamining) {
14740             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14741             SetWhiteToPlayEvent();
14742           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14743           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14744           } else if (shiftKey) {
14745             AdjustClock(which, -1);
14746           } else if (gameMode == IcsPlayingBlack ||
14747                    gameMode == MachinePlaysWhite) {
14748             CallFlagEvent();
14749           }
14750         }
14751 }
14752
14753 void
14754 DrawEvent ()
14755 {
14756     /* Offer draw or accept pending draw offer from opponent */
14757
14758     if (appData.icsActive) {
14759         /* Note: tournament rules require draw offers to be
14760            made after you make your move but before you punch
14761            your clock.  Currently ICS doesn't let you do that;
14762            instead, you immediately punch your clock after making
14763            a move, but you can offer a draw at any time. */
14764
14765         SendToICS(ics_prefix);
14766         SendToICS("draw\n");
14767         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14768     } else if (cmailMsgLoaded) {
14769         if (currentMove == cmailOldMove &&
14770             commentList[cmailOldMove] != NULL &&
14771             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14772                    "Black offers a draw" : "White offers a draw")) {
14773             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14774             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14775         } else if (currentMove == cmailOldMove + 1) {
14776             char *offer = WhiteOnMove(cmailOldMove) ?
14777               "White offers a draw" : "Black offers a draw";
14778             AppendComment(currentMove, offer, TRUE);
14779             DisplayComment(currentMove - 1, offer);
14780             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14781         } else {
14782             DisplayError(_("You must make your move before offering a draw"), 0);
14783             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14784         }
14785     } else if (first.offeredDraw) {
14786         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14787     } else {
14788         if (first.sendDrawOffers) {
14789             SendToProgram("draw\n", &first);
14790             userOfferedDraw = TRUE;
14791         }
14792     }
14793 }
14794
14795 void
14796 AdjournEvent ()
14797 {
14798     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14799
14800     if (appData.icsActive) {
14801         SendToICS(ics_prefix);
14802         SendToICS("adjourn\n");
14803     } else {
14804         /* Currently GNU Chess doesn't offer or accept Adjourns */
14805     }
14806 }
14807
14808
14809 void
14810 AbortEvent ()
14811 {
14812     /* Offer Abort or accept pending Abort offer from opponent */
14813
14814     if (appData.icsActive) {
14815         SendToICS(ics_prefix);
14816         SendToICS("abort\n");
14817     } else {
14818         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14819     }
14820 }
14821
14822 void
14823 ResignEvent ()
14824 {
14825     /* Resign.  You can do this even if it's not your turn. */
14826
14827     if (appData.icsActive) {
14828         SendToICS(ics_prefix);
14829         SendToICS("resign\n");
14830     } else {
14831         switch (gameMode) {
14832           case MachinePlaysWhite:
14833             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14834             break;
14835           case MachinePlaysBlack:
14836             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14837             break;
14838           case EditGame:
14839             if (cmailMsgLoaded) {
14840                 TruncateGame();
14841                 if (WhiteOnMove(cmailOldMove)) {
14842                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14843                 } else {
14844                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14845                 }
14846                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14847             }
14848             break;
14849           default:
14850             break;
14851         }
14852     }
14853 }
14854
14855
14856 void
14857 StopObservingEvent ()
14858 {
14859     /* Stop observing current games */
14860     SendToICS(ics_prefix);
14861     SendToICS("unobserve\n");
14862 }
14863
14864 void
14865 StopExaminingEvent ()
14866 {
14867     /* Stop observing current game */
14868     SendToICS(ics_prefix);
14869     SendToICS("unexamine\n");
14870 }
14871
14872 void
14873 ForwardInner (int target)
14874 {
14875     int limit; int oldSeekGraphUp = seekGraphUp;
14876
14877     if (appData.debugMode)
14878         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14879                 target, currentMove, forwardMostMove);
14880
14881     if (gameMode == EditPosition)
14882       return;
14883
14884     seekGraphUp = FALSE;
14885     MarkTargetSquares(1);
14886
14887     if (gameMode == PlayFromGameFile && !pausing)
14888       PauseEvent();
14889
14890     if (gameMode == IcsExamining && pausing)
14891       limit = pauseExamForwardMostMove;
14892     else
14893       limit = forwardMostMove;
14894
14895     if (target > limit) target = limit;
14896
14897     if (target > 0 && moveList[target - 1][0]) {
14898         int fromX, fromY, toX, toY;
14899         toX = moveList[target - 1][2] - AAA;
14900         toY = moveList[target - 1][3] - ONE;
14901         if (moveList[target - 1][1] == '@') {
14902             if (appData.highlightLastMove) {
14903                 SetHighlights(-1, -1, toX, toY);
14904             }
14905         } else {
14906             fromX = moveList[target - 1][0] - AAA;
14907             fromY = moveList[target - 1][1] - ONE;
14908             if (target == currentMove + 1) {
14909                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14910             }
14911             if (appData.highlightLastMove) {
14912                 SetHighlights(fromX, fromY, toX, toY);
14913             }
14914         }
14915     }
14916     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14917         gameMode == Training || gameMode == PlayFromGameFile ||
14918         gameMode == AnalyzeFile) {
14919         while (currentMove < target) {
14920             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14921             SendMoveToProgram(currentMove++, &first);
14922         }
14923     } else {
14924         currentMove = target;
14925     }
14926
14927     if (gameMode == EditGame || gameMode == EndOfGame) {
14928         whiteTimeRemaining = timeRemaining[0][currentMove];
14929         blackTimeRemaining = timeRemaining[1][currentMove];
14930     }
14931     DisplayBothClocks();
14932     DisplayMove(currentMove - 1);
14933     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14934     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14935     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14936         DisplayComment(currentMove - 1, commentList[currentMove]);
14937     }
14938     ClearMap(); // [HGM] exclude: invalidate map
14939 }
14940
14941
14942 void
14943 ForwardEvent ()
14944 {
14945     if (gameMode == IcsExamining && !pausing) {
14946         SendToICS(ics_prefix);
14947         SendToICS("forward\n");
14948     } else {
14949         ForwardInner(currentMove + 1);
14950     }
14951 }
14952
14953 void
14954 ToEndEvent ()
14955 {
14956     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14957         /* to optimze, we temporarily turn off analysis mode while we feed
14958          * the remaining moves to the engine. Otherwise we get analysis output
14959          * after each move.
14960          */
14961         if (first.analysisSupport) {
14962           SendToProgram("exit\nforce\n", &first);
14963           first.analyzing = FALSE;
14964         }
14965     }
14966
14967     if (gameMode == IcsExamining && !pausing) {
14968         SendToICS(ics_prefix);
14969         SendToICS("forward 999999\n");
14970     } else {
14971         ForwardInner(forwardMostMove);
14972     }
14973
14974     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14975         /* we have fed all the moves, so reactivate analysis mode */
14976         SendToProgram("analyze\n", &first);
14977         first.analyzing = TRUE;
14978         /*first.maybeThinking = TRUE;*/
14979         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14980     }
14981 }
14982
14983 void
14984 BackwardInner (int target)
14985 {
14986     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14987
14988     if (appData.debugMode)
14989         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14990                 target, currentMove, forwardMostMove);
14991
14992     if (gameMode == EditPosition) return;
14993     seekGraphUp = FALSE;
14994     MarkTargetSquares(1);
14995     if (currentMove <= backwardMostMove) {
14996         ClearHighlights();
14997         DrawPosition(full_redraw, boards[currentMove]);
14998         return;
14999     }
15000     if (gameMode == PlayFromGameFile && !pausing)
15001       PauseEvent();
15002
15003     if (moveList[target][0]) {
15004         int fromX, fromY, toX, toY;
15005         toX = moveList[target][2] - AAA;
15006         toY = moveList[target][3] - ONE;
15007         if (moveList[target][1] == '@') {
15008             if (appData.highlightLastMove) {
15009                 SetHighlights(-1, -1, toX, toY);
15010             }
15011         } else {
15012             fromX = moveList[target][0] - AAA;
15013             fromY = moveList[target][1] - ONE;
15014             if (target == currentMove - 1) {
15015                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15016             }
15017             if (appData.highlightLastMove) {
15018                 SetHighlights(fromX, fromY, toX, toY);
15019             }
15020         }
15021     }
15022     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15023         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15024         while (currentMove > target) {
15025             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15026                 // null move cannot be undone. Reload program with move history before it.
15027                 int i;
15028                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15029                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15030                 }
15031                 SendBoard(&first, i);
15032               if(second.analyzing) SendBoard(&second, i);
15033                 for(currentMove=i; currentMove<target; currentMove++) {
15034                     SendMoveToProgram(currentMove, &first);
15035                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15036                 }
15037                 break;
15038             }
15039             SendToBoth("undo\n");
15040             currentMove--;
15041         }
15042     } else {
15043         currentMove = target;
15044     }
15045
15046     if (gameMode == EditGame || gameMode == EndOfGame) {
15047         whiteTimeRemaining = timeRemaining[0][currentMove];
15048         blackTimeRemaining = timeRemaining[1][currentMove];
15049     }
15050     DisplayBothClocks();
15051     DisplayMove(currentMove - 1);
15052     DrawPosition(full_redraw, boards[currentMove]);
15053     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15054     // [HGM] PV info: routine tests if comment empty
15055     DisplayComment(currentMove - 1, commentList[currentMove]);
15056     ClearMap(); // [HGM] exclude: invalidate map
15057 }
15058
15059 void
15060 BackwardEvent ()
15061 {
15062     if (gameMode == IcsExamining && !pausing) {
15063         SendToICS(ics_prefix);
15064         SendToICS("backward\n");
15065     } else {
15066         BackwardInner(currentMove - 1);
15067     }
15068 }
15069
15070 void
15071 ToStartEvent ()
15072 {
15073     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15074         /* to optimize, we temporarily turn off analysis mode while we undo
15075          * all the moves. Otherwise we get analysis output after each undo.
15076          */
15077         if (first.analysisSupport) {
15078           SendToProgram("exit\nforce\n", &first);
15079           first.analyzing = FALSE;
15080         }
15081     }
15082
15083     if (gameMode == IcsExamining && !pausing) {
15084         SendToICS(ics_prefix);
15085         SendToICS("backward 999999\n");
15086     } else {
15087         BackwardInner(backwardMostMove);
15088     }
15089
15090     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15091         /* we have fed all the moves, so reactivate analysis mode */
15092         SendToProgram("analyze\n", &first);
15093         first.analyzing = TRUE;
15094         /*first.maybeThinking = TRUE;*/
15095         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15096     }
15097 }
15098
15099 void
15100 ToNrEvent (int to)
15101 {
15102   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15103   if (to >= forwardMostMove) to = forwardMostMove;
15104   if (to <= backwardMostMove) to = backwardMostMove;
15105   if (to < currentMove) {
15106     BackwardInner(to);
15107   } else {
15108     ForwardInner(to);
15109   }
15110 }
15111
15112 void
15113 RevertEvent (Boolean annotate)
15114 {
15115     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15116         return;
15117     }
15118     if (gameMode != IcsExamining) {
15119         DisplayError(_("You are not examining a game"), 0);
15120         return;
15121     }
15122     if (pausing) {
15123         DisplayError(_("You can't revert while pausing"), 0);
15124         return;
15125     }
15126     SendToICS(ics_prefix);
15127     SendToICS("revert\n");
15128 }
15129
15130 void
15131 RetractMoveEvent ()
15132 {
15133     switch (gameMode) {
15134       case MachinePlaysWhite:
15135       case MachinePlaysBlack:
15136         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15137             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15138             return;
15139         }
15140         if (forwardMostMove < 2) return;
15141         currentMove = forwardMostMove = forwardMostMove - 2;
15142         whiteTimeRemaining = timeRemaining[0][currentMove];
15143         blackTimeRemaining = timeRemaining[1][currentMove];
15144         DisplayBothClocks();
15145         DisplayMove(currentMove - 1);
15146         ClearHighlights();/*!! could figure this out*/
15147         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15148         SendToProgram("remove\n", &first);
15149         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15150         break;
15151
15152       case BeginningOfGame:
15153       default:
15154         break;
15155
15156       case IcsPlayingWhite:
15157       case IcsPlayingBlack:
15158         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15159             SendToICS(ics_prefix);
15160             SendToICS("takeback 2\n");
15161         } else {
15162             SendToICS(ics_prefix);
15163             SendToICS("takeback 1\n");
15164         }
15165         break;
15166     }
15167 }
15168
15169 void
15170 MoveNowEvent ()
15171 {
15172     ChessProgramState *cps;
15173
15174     switch (gameMode) {
15175       case MachinePlaysWhite:
15176         if (!WhiteOnMove(forwardMostMove)) {
15177             DisplayError(_("It is your turn"), 0);
15178             return;
15179         }
15180         cps = &first;
15181         break;
15182       case MachinePlaysBlack:
15183         if (WhiteOnMove(forwardMostMove)) {
15184             DisplayError(_("It is your turn"), 0);
15185             return;
15186         }
15187         cps = &first;
15188         break;
15189       case TwoMachinesPlay:
15190         if (WhiteOnMove(forwardMostMove) ==
15191             (first.twoMachinesColor[0] == 'w')) {
15192             cps = &first;
15193         } else {
15194             cps = &second;
15195         }
15196         break;
15197       case BeginningOfGame:
15198       default:
15199         return;
15200     }
15201     SendToProgram("?\n", cps);
15202 }
15203
15204 void
15205 TruncateGameEvent ()
15206 {
15207     EditGameEvent();
15208     if (gameMode != EditGame) return;
15209     TruncateGame();
15210 }
15211
15212 void
15213 TruncateGame ()
15214 {
15215     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15216     if (forwardMostMove > currentMove) {
15217         if (gameInfo.resultDetails != NULL) {
15218             free(gameInfo.resultDetails);
15219             gameInfo.resultDetails = NULL;
15220             gameInfo.result = GameUnfinished;
15221         }
15222         forwardMostMove = currentMove;
15223         HistorySet(parseList, backwardMostMove, forwardMostMove,
15224                    currentMove-1);
15225     }
15226 }
15227
15228 void
15229 HintEvent ()
15230 {
15231     if (appData.noChessProgram) return;
15232     switch (gameMode) {
15233       case MachinePlaysWhite:
15234         if (WhiteOnMove(forwardMostMove)) {
15235             DisplayError(_("Wait until your turn"), 0);
15236             return;
15237         }
15238         break;
15239       case BeginningOfGame:
15240       case MachinePlaysBlack:
15241         if (!WhiteOnMove(forwardMostMove)) {
15242             DisplayError(_("Wait until your turn"), 0);
15243             return;
15244         }
15245         break;
15246       default:
15247         DisplayError(_("No hint available"), 0);
15248         return;
15249     }
15250     SendToProgram("hint\n", &first);
15251     hintRequested = TRUE;
15252 }
15253
15254 void
15255 CreateBookEvent ()
15256 {
15257     ListGame * lg = (ListGame *) gameList.head;
15258     FILE *f, *g;
15259     int nItem;
15260     static int secondTime = FALSE;
15261
15262     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15263         DisplayError(_("Game list not loaded or empty"), 0);
15264         return;
15265     }
15266
15267     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15268         fclose(g);
15269         secondTime++;
15270         DisplayNote(_("Book file exists! Try again for overwrite."));
15271         return;
15272     }
15273
15274     creatingBook = TRUE;
15275     secondTime = FALSE;
15276
15277     /* Get list size */
15278     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15279         LoadGame(f, nItem, "", TRUE);
15280         AddGameToBook(TRUE);
15281         lg = (ListGame *) lg->node.succ;
15282     }
15283
15284     creatingBook = FALSE;
15285     FlushBook();
15286 }
15287
15288 void
15289 BookEvent ()
15290 {
15291     if (appData.noChessProgram) return;
15292     switch (gameMode) {
15293       case MachinePlaysWhite:
15294         if (WhiteOnMove(forwardMostMove)) {
15295             DisplayError(_("Wait until your turn"), 0);
15296             return;
15297         }
15298         break;
15299       case BeginningOfGame:
15300       case MachinePlaysBlack:
15301         if (!WhiteOnMove(forwardMostMove)) {
15302             DisplayError(_("Wait until your turn"), 0);
15303             return;
15304         }
15305         break;
15306       case EditPosition:
15307         EditPositionDone(TRUE);
15308         break;
15309       case TwoMachinesPlay:
15310         return;
15311       default:
15312         break;
15313     }
15314     SendToProgram("bk\n", &first);
15315     bookOutput[0] = NULLCHAR;
15316     bookRequested = TRUE;
15317 }
15318
15319 void
15320 AboutGameEvent ()
15321 {
15322     char *tags = PGNTags(&gameInfo);
15323     TagsPopUp(tags, CmailMsg());
15324     free(tags);
15325 }
15326
15327 /* end button procedures */
15328
15329 void
15330 PrintPosition (FILE *fp, int move)
15331 {
15332     int i, j;
15333
15334     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15335         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15336             char c = PieceToChar(boards[move][i][j]);
15337             fputc(c == 'x' ? '.' : c, fp);
15338             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15339         }
15340     }
15341     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15342       fprintf(fp, "white to play\n");
15343     else
15344       fprintf(fp, "black to play\n");
15345 }
15346
15347 void
15348 PrintOpponents (FILE *fp)
15349 {
15350     if (gameInfo.white != NULL) {
15351         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15352     } else {
15353         fprintf(fp, "\n");
15354     }
15355 }
15356
15357 /* Find last component of program's own name, using some heuristics */
15358 void
15359 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15360 {
15361     char *p, *q, c;
15362     int local = (strcmp(host, "localhost") == 0);
15363     while (!local && (p = strchr(prog, ';')) != NULL) {
15364         p++;
15365         while (*p == ' ') p++;
15366         prog = p;
15367     }
15368     if (*prog == '"' || *prog == '\'') {
15369         q = strchr(prog + 1, *prog);
15370     } else {
15371         q = strchr(prog, ' ');
15372     }
15373     if (q == NULL) q = prog + strlen(prog);
15374     p = q;
15375     while (p >= prog && *p != '/' && *p != '\\') p--;
15376     p++;
15377     if(p == prog && *p == '"') p++;
15378     c = *q; *q = 0;
15379     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15380     memcpy(buf, p, q - p);
15381     buf[q - p] = NULLCHAR;
15382     if (!local) {
15383         strcat(buf, "@");
15384         strcat(buf, host);
15385     }
15386 }
15387
15388 char *
15389 TimeControlTagValue ()
15390 {
15391     char buf[MSG_SIZ];
15392     if (!appData.clockMode) {
15393       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15394     } else if (movesPerSession > 0) {
15395       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15396     } else if (timeIncrement == 0) {
15397       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15398     } else {
15399       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15400     }
15401     return StrSave(buf);
15402 }
15403
15404 void
15405 SetGameInfo ()
15406 {
15407     /* This routine is used only for certain modes */
15408     VariantClass v = gameInfo.variant;
15409     ChessMove r = GameUnfinished;
15410     char *p = NULL;
15411
15412     if(keepInfo) return;
15413
15414     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15415         r = gameInfo.result;
15416         p = gameInfo.resultDetails;
15417         gameInfo.resultDetails = NULL;
15418     }
15419     ClearGameInfo(&gameInfo);
15420     gameInfo.variant = v;
15421
15422     switch (gameMode) {
15423       case MachinePlaysWhite:
15424         gameInfo.event = StrSave( appData.pgnEventHeader );
15425         gameInfo.site = StrSave(HostName());
15426         gameInfo.date = PGNDate();
15427         gameInfo.round = StrSave("-");
15428         gameInfo.white = StrSave(first.tidy);
15429         gameInfo.black = StrSave(UserName());
15430         gameInfo.timeControl = TimeControlTagValue();
15431         break;
15432
15433       case MachinePlaysBlack:
15434         gameInfo.event = StrSave( appData.pgnEventHeader );
15435         gameInfo.site = StrSave(HostName());
15436         gameInfo.date = PGNDate();
15437         gameInfo.round = StrSave("-");
15438         gameInfo.white = StrSave(UserName());
15439         gameInfo.black = StrSave(first.tidy);
15440         gameInfo.timeControl = TimeControlTagValue();
15441         break;
15442
15443       case TwoMachinesPlay:
15444         gameInfo.event = StrSave( appData.pgnEventHeader );
15445         gameInfo.site = StrSave(HostName());
15446         gameInfo.date = PGNDate();
15447         if (roundNr > 0) {
15448             char buf[MSG_SIZ];
15449             snprintf(buf, MSG_SIZ, "%d", roundNr);
15450             gameInfo.round = StrSave(buf);
15451         } else {
15452             gameInfo.round = StrSave("-");
15453         }
15454         if (first.twoMachinesColor[0] == 'w') {
15455             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15456             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15457         } else {
15458             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15459             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15460         }
15461         gameInfo.timeControl = TimeControlTagValue();
15462         break;
15463
15464       case EditGame:
15465         gameInfo.event = StrSave("Edited game");
15466         gameInfo.site = StrSave(HostName());
15467         gameInfo.date = PGNDate();
15468         gameInfo.round = StrSave("-");
15469         gameInfo.white = StrSave("-");
15470         gameInfo.black = StrSave("-");
15471         gameInfo.result = r;
15472         gameInfo.resultDetails = p;
15473         break;
15474
15475       case EditPosition:
15476         gameInfo.event = StrSave("Edited position");
15477         gameInfo.site = StrSave(HostName());
15478         gameInfo.date = PGNDate();
15479         gameInfo.round = StrSave("-");
15480         gameInfo.white = StrSave("-");
15481         gameInfo.black = StrSave("-");
15482         break;
15483
15484       case IcsPlayingWhite:
15485       case IcsPlayingBlack:
15486       case IcsObserving:
15487       case IcsExamining:
15488         break;
15489
15490       case PlayFromGameFile:
15491         gameInfo.event = StrSave("Game from non-PGN file");
15492         gameInfo.site = StrSave(HostName());
15493         gameInfo.date = PGNDate();
15494         gameInfo.round = StrSave("-");
15495         gameInfo.white = StrSave("?");
15496         gameInfo.black = StrSave("?");
15497         break;
15498
15499       default:
15500         break;
15501     }
15502 }
15503
15504 void
15505 ReplaceComment (int index, char *text)
15506 {
15507     int len;
15508     char *p;
15509     float score;
15510
15511     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15512        pvInfoList[index-1].depth == len &&
15513        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15514        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15515     while (*text == '\n') text++;
15516     len = strlen(text);
15517     while (len > 0 && text[len - 1] == '\n') len--;
15518
15519     if (commentList[index] != NULL)
15520       free(commentList[index]);
15521
15522     if (len == 0) {
15523         commentList[index] = NULL;
15524         return;
15525     }
15526   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15527       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15528       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15529     commentList[index] = (char *) malloc(len + 2);
15530     strncpy(commentList[index], text, len);
15531     commentList[index][len] = '\n';
15532     commentList[index][len + 1] = NULLCHAR;
15533   } else {
15534     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15535     char *p;
15536     commentList[index] = (char *) malloc(len + 7);
15537     safeStrCpy(commentList[index], "{\n", 3);
15538     safeStrCpy(commentList[index]+2, text, len+1);
15539     commentList[index][len+2] = NULLCHAR;
15540     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15541     strcat(commentList[index], "\n}\n");
15542   }
15543 }
15544
15545 void
15546 CrushCRs (char *text)
15547 {
15548   char *p = text;
15549   char *q = text;
15550   char ch;
15551
15552   do {
15553     ch = *p++;
15554     if (ch == '\r') continue;
15555     *q++ = ch;
15556   } while (ch != '\0');
15557 }
15558
15559 void
15560 AppendComment (int index, char *text, Boolean addBraces)
15561 /* addBraces  tells if we should add {} */
15562 {
15563     int oldlen, len;
15564     char *old;
15565
15566 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15567     if(addBraces == 3) addBraces = 0; else // force appending literally
15568     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15569
15570     CrushCRs(text);
15571     while (*text == '\n') text++;
15572     len = strlen(text);
15573     while (len > 0 && text[len - 1] == '\n') len--;
15574     text[len] = NULLCHAR;
15575
15576     if (len == 0) return;
15577
15578     if (commentList[index] != NULL) {
15579       Boolean addClosingBrace = addBraces;
15580         old = commentList[index];
15581         oldlen = strlen(old);
15582         while(commentList[index][oldlen-1] ==  '\n')
15583           commentList[index][--oldlen] = NULLCHAR;
15584         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15585         safeStrCpy(commentList[index], old, oldlen + len + 6);
15586         free(old);
15587         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15588         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15589           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15590           while (*text == '\n') { text++; len--; }
15591           commentList[index][--oldlen] = NULLCHAR;
15592       }
15593         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15594         else          strcat(commentList[index], "\n");
15595         strcat(commentList[index], text);
15596         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15597         else          strcat(commentList[index], "\n");
15598     } else {
15599         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15600         if(addBraces)
15601           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15602         else commentList[index][0] = NULLCHAR;
15603         strcat(commentList[index], text);
15604         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15605         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15606     }
15607 }
15608
15609 static char *
15610 FindStr (char * text, char * sub_text)
15611 {
15612     char * result = strstr( text, sub_text );
15613
15614     if( result != NULL ) {
15615         result += strlen( sub_text );
15616     }
15617
15618     return result;
15619 }
15620
15621 /* [AS] Try to extract PV info from PGN comment */
15622 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15623 char *
15624 GetInfoFromComment (int index, char * text)
15625 {
15626     char * sep = text, *p;
15627
15628     if( text != NULL && index > 0 ) {
15629         int score = 0;
15630         int depth = 0;
15631         int time = -1, sec = 0, deci;
15632         char * s_eval = FindStr( text, "[%eval " );
15633         char * s_emt = FindStr( text, "[%emt " );
15634 #if 0
15635         if( s_eval != NULL || s_emt != NULL ) {
15636 #else
15637         if(0) { // [HGM] this code is not finished, and could actually be detrimental
15638 #endif
15639             /* New style */
15640             char delim;
15641
15642             if( s_eval != NULL ) {
15643                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15644                     return text;
15645                 }
15646
15647                 if( delim != ']' ) {
15648                     return text;
15649                 }
15650             }
15651
15652             if( s_emt != NULL ) {
15653             }
15654                 return text;
15655         }
15656         else {
15657             /* We expect something like: [+|-]nnn.nn/dd */
15658             int score_lo = 0;
15659
15660             if(*text != '{') return text; // [HGM] braces: must be normal comment
15661
15662             sep = strchr( text, '/' );
15663             if( sep == NULL || sep < (text+4) ) {
15664                 return text;
15665             }
15666
15667             p = text;
15668             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15669             if(p[1] == '(') { // comment starts with PV
15670                p = strchr(p, ')'); // locate end of PV
15671                if(p == NULL || sep < p+5) return text;
15672                // at this point we have something like "{(.*) +0.23/6 ..."
15673                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15674                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15675                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15676             }
15677             time = -1; sec = -1; deci = -1;
15678             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15679                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15680                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15681                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15682                 return text;
15683             }
15684
15685             if( score_lo < 0 || score_lo >= 100 ) {
15686                 return text;
15687             }
15688
15689             if(sec >= 0) time = 600*time + 10*sec; else
15690             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15691
15692             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
15693
15694             /* [HGM] PV time: now locate end of PV info */
15695             while( *++sep >= '0' && *sep <= '9'); // strip depth
15696             if(time >= 0)
15697             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15698             if(sec >= 0)
15699             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15700             if(deci >= 0)
15701             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15702             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15703         }
15704
15705         if( depth <= 0 ) {
15706             return text;
15707         }
15708
15709         if( time < 0 ) {
15710             time = -1;
15711         }
15712
15713         pvInfoList[index-1].depth = depth;
15714         pvInfoList[index-1].score = score;
15715         pvInfoList[index-1].time  = 10*time; // centi-sec
15716         if(*sep == '}') *sep = 0; else *--sep = '{';
15717         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15718     }
15719     return sep;
15720 }
15721
15722 void
15723 SendToProgram (char *message, ChessProgramState *cps)
15724 {
15725     int count, outCount, error;
15726     char buf[MSG_SIZ];
15727
15728     if (cps->pr == NoProc) return;
15729     Attention(cps);
15730
15731     if (appData.debugMode) {
15732         TimeMark now;
15733         GetTimeMark(&now);
15734         fprintf(debugFP, "%ld >%-6s: %s",
15735                 SubtractTimeMarks(&now, &programStartTime),
15736                 cps->which, message);
15737         if(serverFP)
15738             fprintf(serverFP, "%ld >%-6s: %s",
15739                 SubtractTimeMarks(&now, &programStartTime),
15740                 cps->which, message), fflush(serverFP);
15741     }
15742
15743     count = strlen(message);
15744     outCount = OutputToProcess(cps->pr, message, count, &error);
15745     if (outCount < count && !exiting
15746                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15747       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15748       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15749         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15750             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15751                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15752                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15753                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15754             } else {
15755                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15756                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15757                 gameInfo.result = res;
15758             }
15759             gameInfo.resultDetails = StrSave(buf);
15760         }
15761         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15762         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15763     }
15764 }
15765
15766 void
15767 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15768 {
15769     char *end_str;
15770     char buf[MSG_SIZ];
15771     ChessProgramState *cps = (ChessProgramState *)closure;
15772
15773     if (isr != cps->isr) return; /* Killed intentionally */
15774     if (count <= 0) {
15775         if (count == 0) {
15776             RemoveInputSource(cps->isr);
15777             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15778                     _(cps->which), cps->program);
15779             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15780             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15781                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15782                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15783                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15784                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15785                 } else {
15786                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15787                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15788                     gameInfo.result = res;
15789                 }
15790                 gameInfo.resultDetails = StrSave(buf);
15791             }
15792             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15793             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15794         } else {
15795             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15796                     _(cps->which), cps->program);
15797             RemoveInputSource(cps->isr);
15798
15799             /* [AS] Program is misbehaving badly... kill it */
15800             if( count == -2 ) {
15801                 DestroyChildProcess( cps->pr, 9 );
15802                 cps->pr = NoProc;
15803             }
15804
15805             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15806         }
15807         return;
15808     }
15809
15810     if ((end_str = strchr(message, '\r')) != NULL)
15811       *end_str = NULLCHAR;
15812     if ((end_str = strchr(message, '\n')) != NULL)
15813       *end_str = NULLCHAR;
15814
15815     if (appData.debugMode) {
15816         TimeMark now; int print = 1;
15817         char *quote = ""; char c; int i;
15818
15819         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15820                 char start = message[0];
15821                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15822                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15823                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15824                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15825                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15826                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15827                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15828                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15829                    sscanf(message, "hint: %c", &c)!=1 &&
15830                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15831                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15832                     print = (appData.engineComments >= 2);
15833                 }
15834                 message[0] = start; // restore original message
15835         }
15836         if(print) {
15837                 GetTimeMark(&now);
15838                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15839                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15840                         quote,
15841                         message);
15842                 if(serverFP)
15843                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15844                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15845                         quote,
15846                         message), fflush(serverFP);
15847         }
15848     }
15849
15850     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15851     if (appData.icsEngineAnalyze) {
15852         if (strstr(message, "whisper") != NULL ||
15853              strstr(message, "kibitz") != NULL ||
15854             strstr(message, "tellics") != NULL) return;
15855     }
15856
15857     HandleMachineMove(message, cps);
15858 }
15859
15860
15861 void
15862 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15863 {
15864     char buf[MSG_SIZ];
15865     int seconds;
15866
15867     if( timeControl_2 > 0 ) {
15868         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15869             tc = timeControl_2;
15870         }
15871     }
15872     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15873     inc /= cps->timeOdds;
15874     st  /= cps->timeOdds;
15875
15876     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15877
15878     if (st > 0) {
15879       /* Set exact time per move, normally using st command */
15880       if (cps->stKludge) {
15881         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15882         seconds = st % 60;
15883         if (seconds == 0) {
15884           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15885         } else {
15886           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15887         }
15888       } else {
15889         snprintf(buf, MSG_SIZ, "st %d\n", st);
15890       }
15891     } else {
15892       /* Set conventional or incremental time control, using level command */
15893       if (seconds == 0) {
15894         /* Note old gnuchess bug -- minutes:seconds used to not work.
15895            Fixed in later versions, but still avoid :seconds
15896            when seconds is 0. */
15897         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15898       } else {
15899         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15900                  seconds, inc/1000.);
15901       }
15902     }
15903     SendToProgram(buf, cps);
15904
15905     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15906     /* Orthogonally, limit search to given depth */
15907     if (sd > 0) {
15908       if (cps->sdKludge) {
15909         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15910       } else {
15911         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15912       }
15913       SendToProgram(buf, cps);
15914     }
15915
15916     if(cps->nps >= 0) { /* [HGM] nps */
15917         if(cps->supportsNPS == FALSE)
15918           cps->nps = -1; // don't use if engine explicitly says not supported!
15919         else {
15920           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15921           SendToProgram(buf, cps);
15922         }
15923     }
15924 }
15925
15926 ChessProgramState *
15927 WhitePlayer ()
15928 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15929 {
15930     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15931        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15932         return &second;
15933     return &first;
15934 }
15935
15936 void
15937 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15938 {
15939     char message[MSG_SIZ];
15940     long time, otime;
15941
15942     /* Note: this routine must be called when the clocks are stopped
15943        or when they have *just* been set or switched; otherwise
15944        it will be off by the time since the current tick started.
15945     */
15946     if (machineWhite) {
15947         time = whiteTimeRemaining / 10;
15948         otime = blackTimeRemaining / 10;
15949     } else {
15950         time = blackTimeRemaining / 10;
15951         otime = whiteTimeRemaining / 10;
15952     }
15953     /* [HGM] translate opponent's time by time-odds factor */
15954     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15955
15956     if (time <= 0) time = 1;
15957     if (otime <= 0) otime = 1;
15958
15959     snprintf(message, MSG_SIZ, "time %ld\n", time);
15960     SendToProgram(message, cps);
15961
15962     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15963     SendToProgram(message, cps);
15964 }
15965
15966 int
15967 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15968 {
15969   char buf[MSG_SIZ];
15970   int len = strlen(name);
15971   int val;
15972
15973   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15974     (*p) += len + 1;
15975     sscanf(*p, "%d", &val);
15976     *loc = (val != 0);
15977     while (**p && **p != ' ')
15978       (*p)++;
15979     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15980     SendToProgram(buf, cps);
15981     return TRUE;
15982   }
15983   return FALSE;
15984 }
15985
15986 int
15987 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15988 {
15989   char buf[MSG_SIZ];
15990   int len = strlen(name);
15991   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15992     (*p) += len + 1;
15993     sscanf(*p, "%d", loc);
15994     while (**p && **p != ' ') (*p)++;
15995     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15996     SendToProgram(buf, cps);
15997     return TRUE;
15998   }
15999   return FALSE;
16000 }
16001
16002 int
16003 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16004 {
16005   char buf[MSG_SIZ];
16006   int len = strlen(name);
16007   if (strncmp((*p), name, len) == 0
16008       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16009     (*p) += len + 2;
16010     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16011     sscanf(*p, "%[^\"]", *loc);
16012     while (**p && **p != '\"') (*p)++;
16013     if (**p == '\"') (*p)++;
16014     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16015     SendToProgram(buf, cps);
16016     return TRUE;
16017   }
16018   return FALSE;
16019 }
16020
16021 int
16022 ParseOption (Option *opt, ChessProgramState *cps)
16023 // [HGM] options: process the string that defines an engine option, and determine
16024 // name, type, default value, and allowed value range
16025 {
16026         char *p, *q, buf[MSG_SIZ];
16027         int n, min = (-1)<<31, max = 1<<31, def;
16028
16029         if(p = strstr(opt->name, " -spin ")) {
16030             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16031             if(max < min) max = min; // enforce consistency
16032             if(def < min) def = min;
16033             if(def > max) def = max;
16034             opt->value = def;
16035             opt->min = min;
16036             opt->max = max;
16037             opt->type = Spin;
16038         } else if((p = strstr(opt->name, " -slider "))) {
16039             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16040             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16041             if(max < min) max = min; // enforce consistency
16042             if(def < min) def = min;
16043             if(def > max) def = max;
16044             opt->value = def;
16045             opt->min = min;
16046             opt->max = max;
16047             opt->type = Spin; // Slider;
16048         } else if((p = strstr(opt->name, " -string "))) {
16049             opt->textValue = p+9;
16050             opt->type = TextBox;
16051         } else if((p = strstr(opt->name, " -file "))) {
16052             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16053             opt->textValue = p+7;
16054             opt->type = FileName; // FileName;
16055         } else if((p = strstr(opt->name, " -path "))) {
16056             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16057             opt->textValue = p+7;
16058             opt->type = PathName; // PathName;
16059         } else if(p = strstr(opt->name, " -check ")) {
16060             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16061             opt->value = (def != 0);
16062             opt->type = CheckBox;
16063         } else if(p = strstr(opt->name, " -combo ")) {
16064             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16065             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16066             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16067             opt->value = n = 0;
16068             while(q = StrStr(q, " /// ")) {
16069                 n++; *q = 0;    // count choices, and null-terminate each of them
16070                 q += 5;
16071                 if(*q == '*') { // remember default, which is marked with * prefix
16072                     q++;
16073                     opt->value = n;
16074                 }
16075                 cps->comboList[cps->comboCnt++] = q;
16076             }
16077             cps->comboList[cps->comboCnt++] = NULL;
16078             opt->max = n + 1;
16079             opt->type = ComboBox;
16080         } else if(p = strstr(opt->name, " -button")) {
16081             opt->type = Button;
16082         } else if(p = strstr(opt->name, " -save")) {
16083             opt->type = SaveButton;
16084         } else return FALSE;
16085         *p = 0; // terminate option name
16086         // now look if the command-line options define a setting for this engine option.
16087         if(cps->optionSettings && cps->optionSettings[0])
16088             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16089         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16090           snprintf(buf, MSG_SIZ, "option %s", p);
16091                 if(p = strstr(buf, ",")) *p = 0;
16092                 if(q = strchr(buf, '=')) switch(opt->type) {
16093                     case ComboBox:
16094                         for(n=0; n<opt->max; n++)
16095                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16096                         break;
16097                     case TextBox:
16098                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16099                         break;
16100                     case Spin:
16101                     case CheckBox:
16102                         opt->value = atoi(q+1);
16103                     default:
16104                         break;
16105                 }
16106                 strcat(buf, "\n");
16107                 SendToProgram(buf, cps);
16108         }
16109         return TRUE;
16110 }
16111
16112 void
16113 FeatureDone (ChessProgramState *cps, int val)
16114 {
16115   DelayedEventCallback cb = GetDelayedEvent();
16116   if ((cb == InitBackEnd3 && cps == &first) ||
16117       (cb == SettingsMenuIfReady && cps == &second) ||
16118       (cb == LoadEngine) ||
16119       (cb == TwoMachinesEventIfReady)) {
16120     CancelDelayedEvent();
16121     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16122   }
16123   cps->initDone = val;
16124   if(val) cps->reload = FALSE;
16125 }
16126
16127 /* Parse feature command from engine */
16128 void
16129 ParseFeatures (char *args, ChessProgramState *cps)
16130 {
16131   char *p = args;
16132   char *q = NULL;
16133   int val;
16134   char buf[MSG_SIZ];
16135
16136   for (;;) {
16137     while (*p == ' ') p++;
16138     if (*p == NULLCHAR) return;
16139
16140     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16141     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16142     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16143     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16144     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16145     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16146     if (BoolFeature(&p, "reuse", &val, cps)) {
16147       /* Engine can disable reuse, but can't enable it if user said no */
16148       if (!val) cps->reuse = FALSE;
16149       continue;
16150     }
16151     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16152     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16153       if (gameMode == TwoMachinesPlay) {
16154         DisplayTwoMachinesTitle();
16155       } else {
16156         DisplayTitle("");
16157       }
16158       continue;
16159     }
16160     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16161     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16162     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16163     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16164     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16165     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16166     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16167     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16168     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16169     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16170     if (IntFeature(&p, "done", &val, cps)) {
16171       FeatureDone(cps, val);
16172       continue;
16173     }
16174     /* Added by Tord: */
16175     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16176     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16177     /* End of additions by Tord */
16178
16179     /* [HGM] added features: */
16180     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16181     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16182     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16183     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16184     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16185     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16186     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16187         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16188         FREE(cps->option[cps->nrOptions].name);
16189         cps->option[cps->nrOptions].name = q; q = NULL;
16190         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16191           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16192             SendToProgram(buf, cps);
16193             continue;
16194         }
16195         if(cps->nrOptions >= MAX_OPTIONS) {
16196             cps->nrOptions--;
16197             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16198             DisplayError(buf, 0);
16199         }
16200         continue;
16201     }
16202     /* End of additions by HGM */
16203
16204     /* unknown feature: complain and skip */
16205     q = p;
16206     while (*q && *q != '=') q++;
16207     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16208     SendToProgram(buf, cps);
16209     p = q;
16210     if (*p == '=') {
16211       p++;
16212       if (*p == '\"') {
16213         p++;
16214         while (*p && *p != '\"') p++;
16215         if (*p == '\"') p++;
16216       } else {
16217         while (*p && *p != ' ') p++;
16218       }
16219     }
16220   }
16221
16222 }
16223
16224 void
16225 PeriodicUpdatesEvent (int newState)
16226 {
16227     if (newState == appData.periodicUpdates)
16228       return;
16229
16230     appData.periodicUpdates=newState;
16231
16232     /* Display type changes, so update it now */
16233 //    DisplayAnalysis();
16234
16235     /* Get the ball rolling again... */
16236     if (newState) {
16237         AnalysisPeriodicEvent(1);
16238         StartAnalysisClock();
16239     }
16240 }
16241
16242 void
16243 PonderNextMoveEvent (int newState)
16244 {
16245     if (newState == appData.ponderNextMove) return;
16246     if (gameMode == EditPosition) EditPositionDone(TRUE);
16247     if (newState) {
16248         SendToProgram("hard\n", &first);
16249         if (gameMode == TwoMachinesPlay) {
16250             SendToProgram("hard\n", &second);
16251         }
16252     } else {
16253         SendToProgram("easy\n", &first);
16254         thinkOutput[0] = NULLCHAR;
16255         if (gameMode == TwoMachinesPlay) {
16256             SendToProgram("easy\n", &second);
16257         }
16258     }
16259     appData.ponderNextMove = newState;
16260 }
16261
16262 void
16263 NewSettingEvent (int option, int *feature, char *command, int value)
16264 {
16265     char buf[MSG_SIZ];
16266
16267     if (gameMode == EditPosition) EditPositionDone(TRUE);
16268     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16269     if(feature == NULL || *feature) SendToProgram(buf, &first);
16270     if (gameMode == TwoMachinesPlay) {
16271         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16272     }
16273 }
16274
16275 void
16276 ShowThinkingEvent ()
16277 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16278 {
16279     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16280     int newState = appData.showThinking
16281         // [HGM] thinking: other features now need thinking output as well
16282         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16283
16284     if (oldState == newState) return;
16285     oldState = newState;
16286     if (gameMode == EditPosition) EditPositionDone(TRUE);
16287     if (oldState) {
16288         SendToProgram("post\n", &first);
16289         if (gameMode == TwoMachinesPlay) {
16290             SendToProgram("post\n", &second);
16291         }
16292     } else {
16293         SendToProgram("nopost\n", &first);
16294         thinkOutput[0] = NULLCHAR;
16295         if (gameMode == TwoMachinesPlay) {
16296             SendToProgram("nopost\n", &second);
16297         }
16298     }
16299 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16300 }
16301
16302 void
16303 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16304 {
16305   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16306   if (pr == NoProc) return;
16307   AskQuestion(title, question, replyPrefix, pr);
16308 }
16309
16310 void
16311 TypeInEvent (char firstChar)
16312 {
16313     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16314         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16315         gameMode == AnalyzeMode || gameMode == EditGame ||
16316         gameMode == EditPosition || gameMode == IcsExamining ||
16317         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16318         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16319                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16320                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16321         gameMode == Training) PopUpMoveDialog(firstChar);
16322 }
16323
16324 void
16325 TypeInDoneEvent (char *move)
16326 {
16327         Board board;
16328         int n, fromX, fromY, toX, toY;
16329         char promoChar;
16330         ChessMove moveType;
16331
16332         // [HGM] FENedit
16333         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16334                 EditPositionPasteFEN(move);
16335                 return;
16336         }
16337         // [HGM] movenum: allow move number to be typed in any mode
16338         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16339           ToNrEvent(2*n-1);
16340           return;
16341         }
16342         // undocumented kludge: allow command-line option to be typed in!
16343         // (potentially fatal, and does not implement the effect of the option.)
16344         // should only be used for options that are values on which future decisions will be made,
16345         // and definitely not on options that would be used during initialization.
16346         if(strstr(move, "!!! -") == move) {
16347             ParseArgsFromString(move+4);
16348             return;
16349         }
16350
16351       if (gameMode != EditGame && currentMove != forwardMostMove &&
16352         gameMode != Training) {
16353         DisplayMoveError(_("Displayed move is not current"));
16354       } else {
16355         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16356           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16357         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16358         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16359           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16360           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16361         } else {
16362           DisplayMoveError(_("Could not parse move"));
16363         }
16364       }
16365 }
16366
16367 void
16368 DisplayMove (int moveNumber)
16369 {
16370     char message[MSG_SIZ];
16371     char res[MSG_SIZ];
16372     char cpThinkOutput[MSG_SIZ];
16373
16374     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16375
16376     if (moveNumber == forwardMostMove - 1 ||
16377         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16378
16379         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16380
16381         if (strchr(cpThinkOutput, '\n')) {
16382             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16383         }
16384     } else {
16385         *cpThinkOutput = NULLCHAR;
16386     }
16387
16388     /* [AS] Hide thinking from human user */
16389     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16390         *cpThinkOutput = NULLCHAR;
16391         if( thinkOutput[0] != NULLCHAR ) {
16392             int i;
16393
16394             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16395                 cpThinkOutput[i] = '.';
16396             }
16397             cpThinkOutput[i] = NULLCHAR;
16398             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16399         }
16400     }
16401
16402     if (moveNumber == forwardMostMove - 1 &&
16403         gameInfo.resultDetails != NULL) {
16404         if (gameInfo.resultDetails[0] == NULLCHAR) {
16405           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16406         } else {
16407           snprintf(res, MSG_SIZ, " {%s} %s",
16408                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16409         }
16410     } else {
16411         res[0] = NULLCHAR;
16412     }
16413
16414     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16415         DisplayMessage(res, cpThinkOutput);
16416     } else {
16417       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16418                 WhiteOnMove(moveNumber) ? " " : ".. ",
16419                 parseList[moveNumber], res);
16420         DisplayMessage(message, cpThinkOutput);
16421     }
16422 }
16423
16424 void
16425 DisplayComment (int moveNumber, char *text)
16426 {
16427     char title[MSG_SIZ];
16428
16429     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16430       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16431     } else {
16432       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16433               WhiteOnMove(moveNumber) ? " " : ".. ",
16434               parseList[moveNumber]);
16435     }
16436     if (text != NULL && (appData.autoDisplayComment || commentUp))
16437         CommentPopUp(title, text);
16438 }
16439
16440 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16441  * might be busy thinking or pondering.  It can be omitted if your
16442  * gnuchess is configured to stop thinking immediately on any user
16443  * input.  However, that gnuchess feature depends on the FIONREAD
16444  * ioctl, which does not work properly on some flavors of Unix.
16445  */
16446 void
16447 Attention (ChessProgramState *cps)
16448 {
16449 #if ATTENTION
16450     if (!cps->useSigint) return;
16451     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16452     switch (gameMode) {
16453       case MachinePlaysWhite:
16454       case MachinePlaysBlack:
16455       case TwoMachinesPlay:
16456       case IcsPlayingWhite:
16457       case IcsPlayingBlack:
16458       case AnalyzeMode:
16459       case AnalyzeFile:
16460         /* Skip if we know it isn't thinking */
16461         if (!cps->maybeThinking) return;
16462         if (appData.debugMode)
16463           fprintf(debugFP, "Interrupting %s\n", cps->which);
16464         InterruptChildProcess(cps->pr);
16465         cps->maybeThinking = FALSE;
16466         break;
16467       default:
16468         break;
16469     }
16470 #endif /*ATTENTION*/
16471 }
16472
16473 int
16474 CheckFlags ()
16475 {
16476     if (whiteTimeRemaining <= 0) {
16477         if (!whiteFlag) {
16478             whiteFlag = TRUE;
16479             if (appData.icsActive) {
16480                 if (appData.autoCallFlag &&
16481                     gameMode == IcsPlayingBlack && !blackFlag) {
16482                   SendToICS(ics_prefix);
16483                   SendToICS("flag\n");
16484                 }
16485             } else {
16486                 if (blackFlag) {
16487                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16488                 } else {
16489                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16490                     if (appData.autoCallFlag) {
16491                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16492                         return TRUE;
16493                     }
16494                 }
16495             }
16496         }
16497     }
16498     if (blackTimeRemaining <= 0) {
16499         if (!blackFlag) {
16500             blackFlag = TRUE;
16501             if (appData.icsActive) {
16502                 if (appData.autoCallFlag &&
16503                     gameMode == IcsPlayingWhite && !whiteFlag) {
16504                   SendToICS(ics_prefix);
16505                   SendToICS("flag\n");
16506                 }
16507             } else {
16508                 if (whiteFlag) {
16509                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16510                 } else {
16511                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16512                     if (appData.autoCallFlag) {
16513                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16514                         return TRUE;
16515                     }
16516                 }
16517             }
16518         }
16519     }
16520     return FALSE;
16521 }
16522
16523 void
16524 CheckTimeControl ()
16525 {
16526     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16527         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16528
16529     /*
16530      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16531      */
16532     if ( !WhiteOnMove(forwardMostMove) ) {
16533         /* White made time control */
16534         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16535         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16536         /* [HGM] time odds: correct new time quota for time odds! */
16537                                             / WhitePlayer()->timeOdds;
16538         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16539     } else {
16540         lastBlack -= blackTimeRemaining;
16541         /* Black made time control */
16542         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16543                                             / WhitePlayer()->other->timeOdds;
16544         lastWhite = whiteTimeRemaining;
16545     }
16546 }
16547
16548 void
16549 DisplayBothClocks ()
16550 {
16551     int wom = gameMode == EditPosition ?
16552       !blackPlaysFirst : WhiteOnMove(currentMove);
16553     DisplayWhiteClock(whiteTimeRemaining, wom);
16554     DisplayBlackClock(blackTimeRemaining, !wom);
16555 }
16556
16557
16558 /* Timekeeping seems to be a portability nightmare.  I think everyone
16559    has ftime(), but I'm really not sure, so I'm including some ifdefs
16560    to use other calls if you don't.  Clocks will be less accurate if
16561    you have neither ftime nor gettimeofday.
16562 */
16563
16564 /* VS 2008 requires the #include outside of the function */
16565 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16566 #include <sys/timeb.h>
16567 #endif
16568
16569 /* Get the current time as a TimeMark */
16570 void
16571 GetTimeMark (TimeMark *tm)
16572 {
16573 #if HAVE_GETTIMEOFDAY
16574
16575     struct timeval timeVal;
16576     struct timezone timeZone;
16577
16578     gettimeofday(&timeVal, &timeZone);
16579     tm->sec = (long) timeVal.tv_sec;
16580     tm->ms = (int) (timeVal.tv_usec / 1000L);
16581
16582 #else /*!HAVE_GETTIMEOFDAY*/
16583 #if HAVE_FTIME
16584
16585 // include <sys/timeb.h> / moved to just above start of function
16586     struct timeb timeB;
16587
16588     ftime(&timeB);
16589     tm->sec = (long) timeB.time;
16590     tm->ms = (int) timeB.millitm;
16591
16592 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16593     tm->sec = (long) time(NULL);
16594     tm->ms = 0;
16595 #endif
16596 #endif
16597 }
16598
16599 /* Return the difference in milliseconds between two
16600    time marks.  We assume the difference will fit in a long!
16601 */
16602 long
16603 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16604 {
16605     return 1000L*(tm2->sec - tm1->sec) +
16606            (long) (tm2->ms - tm1->ms);
16607 }
16608
16609
16610 /*
16611  * Code to manage the game clocks.
16612  *
16613  * In tournament play, black starts the clock and then white makes a move.
16614  * We give the human user a slight advantage if he is playing white---the
16615  * clocks don't run until he makes his first move, so it takes zero time.
16616  * Also, we don't account for network lag, so we could get out of sync
16617  * with GNU Chess's clock -- but then, referees are always right.
16618  */
16619
16620 static TimeMark tickStartTM;
16621 static long intendedTickLength;
16622
16623 long
16624 NextTickLength (long timeRemaining)
16625 {
16626     long nominalTickLength, nextTickLength;
16627
16628     if (timeRemaining > 0L && timeRemaining <= 10000L)
16629       nominalTickLength = 100L;
16630     else
16631       nominalTickLength = 1000L;
16632     nextTickLength = timeRemaining % nominalTickLength;
16633     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16634
16635     return nextTickLength;
16636 }
16637
16638 /* Adjust clock one minute up or down */
16639 void
16640 AdjustClock (Boolean which, int dir)
16641 {
16642     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16643     if(which) blackTimeRemaining += 60000*dir;
16644     else      whiteTimeRemaining += 60000*dir;
16645     DisplayBothClocks();
16646     adjustedClock = TRUE;
16647 }
16648
16649 /* Stop clocks and reset to a fresh time control */
16650 void
16651 ResetClocks ()
16652 {
16653     (void) StopClockTimer();
16654     if (appData.icsActive) {
16655         whiteTimeRemaining = blackTimeRemaining = 0;
16656     } else if (searchTime) {
16657         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16658         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16659     } else { /* [HGM] correct new time quote for time odds */
16660         whiteTC = blackTC = fullTimeControlString;
16661         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16662         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16663     }
16664     if (whiteFlag || blackFlag) {
16665         DisplayTitle("");
16666         whiteFlag = blackFlag = FALSE;
16667     }
16668     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16669     DisplayBothClocks();
16670     adjustedClock = FALSE;
16671 }
16672
16673 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16674
16675 /* Decrement running clock by amount of time that has passed */
16676 void
16677 DecrementClocks ()
16678 {
16679     long timeRemaining;
16680     long lastTickLength, fudge;
16681     TimeMark now;
16682
16683     if (!appData.clockMode) return;
16684     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16685
16686     GetTimeMark(&now);
16687
16688     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16689
16690     /* Fudge if we woke up a little too soon */
16691     fudge = intendedTickLength - lastTickLength;
16692     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16693
16694     if (WhiteOnMove(forwardMostMove)) {
16695         if(whiteNPS >= 0) lastTickLength = 0;
16696         timeRemaining = whiteTimeRemaining -= lastTickLength;
16697         if(timeRemaining < 0 && !appData.icsActive) {
16698             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16699             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16700                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16701                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16702             }
16703         }
16704         DisplayWhiteClock(whiteTimeRemaining - fudge,
16705                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16706     } else {
16707         if(blackNPS >= 0) lastTickLength = 0;
16708         timeRemaining = blackTimeRemaining -= lastTickLength;
16709         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16710             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16711             if(suddenDeath) {
16712                 blackStartMove = forwardMostMove;
16713                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16714             }
16715         }
16716         DisplayBlackClock(blackTimeRemaining - fudge,
16717                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16718     }
16719     if (CheckFlags()) return;
16720
16721     if(twoBoards) { // count down secondary board's clocks as well
16722         activePartnerTime -= lastTickLength;
16723         partnerUp = 1;
16724         if(activePartner == 'W')
16725             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16726         else
16727             DisplayBlackClock(activePartnerTime, TRUE);
16728         partnerUp = 0;
16729     }
16730
16731     tickStartTM = now;
16732     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16733     StartClockTimer(intendedTickLength);
16734
16735     /* if the time remaining has fallen below the alarm threshold, sound the
16736      * alarm. if the alarm has sounded and (due to a takeback or time control
16737      * with increment) the time remaining has increased to a level above the
16738      * threshold, reset the alarm so it can sound again.
16739      */
16740
16741     if (appData.icsActive && appData.icsAlarm) {
16742
16743         /* make sure we are dealing with the user's clock */
16744         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16745                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16746            )) return;
16747
16748         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16749             alarmSounded = FALSE;
16750         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16751             PlayAlarmSound();
16752             alarmSounded = TRUE;
16753         }
16754     }
16755 }
16756
16757
16758 /* A player has just moved, so stop the previously running
16759    clock and (if in clock mode) start the other one.
16760    We redisplay both clocks in case we're in ICS mode, because
16761    ICS gives us an update to both clocks after every move.
16762    Note that this routine is called *after* forwardMostMove
16763    is updated, so the last fractional tick must be subtracted
16764    from the color that is *not* on move now.
16765 */
16766 void
16767 SwitchClocks (int newMoveNr)
16768 {
16769     long lastTickLength;
16770     TimeMark now;
16771     int flagged = FALSE;
16772
16773     GetTimeMark(&now);
16774
16775     if (StopClockTimer() && appData.clockMode) {
16776         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16777         if (!WhiteOnMove(forwardMostMove)) {
16778             if(blackNPS >= 0) lastTickLength = 0;
16779             blackTimeRemaining -= lastTickLength;
16780            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16781 //         if(pvInfoList[forwardMostMove].time == -1)
16782                  pvInfoList[forwardMostMove].time =               // use GUI time
16783                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16784         } else {
16785            if(whiteNPS >= 0) lastTickLength = 0;
16786            whiteTimeRemaining -= lastTickLength;
16787            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16788 //         if(pvInfoList[forwardMostMove].time == -1)
16789                  pvInfoList[forwardMostMove].time =
16790                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16791         }
16792         flagged = CheckFlags();
16793     }
16794     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16795     CheckTimeControl();
16796
16797     if (flagged || !appData.clockMode) return;
16798
16799     switch (gameMode) {
16800       case MachinePlaysBlack:
16801       case MachinePlaysWhite:
16802       case BeginningOfGame:
16803         if (pausing) return;
16804         break;
16805
16806       case EditGame:
16807       case PlayFromGameFile:
16808       case IcsExamining:
16809         return;
16810
16811       default:
16812         break;
16813     }
16814
16815     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16816         if(WhiteOnMove(forwardMostMove))
16817              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16818         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16819     }
16820
16821     tickStartTM = now;
16822     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16823       whiteTimeRemaining : blackTimeRemaining);
16824     StartClockTimer(intendedTickLength);
16825 }
16826
16827
16828 /* Stop both clocks */
16829 void
16830 StopClocks ()
16831 {
16832     long lastTickLength;
16833     TimeMark now;
16834
16835     if (!StopClockTimer()) return;
16836     if (!appData.clockMode) return;
16837
16838     GetTimeMark(&now);
16839
16840     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16841     if (WhiteOnMove(forwardMostMove)) {
16842         if(whiteNPS >= 0) lastTickLength = 0;
16843         whiteTimeRemaining -= lastTickLength;
16844         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16845     } else {
16846         if(blackNPS >= 0) lastTickLength = 0;
16847         blackTimeRemaining -= lastTickLength;
16848         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16849     }
16850     CheckFlags();
16851 }
16852
16853 /* Start clock of player on move.  Time may have been reset, so
16854    if clock is already running, stop and restart it. */
16855 void
16856 StartClocks ()
16857 {
16858     (void) StopClockTimer(); /* in case it was running already */
16859     DisplayBothClocks();
16860     if (CheckFlags()) return;
16861
16862     if (!appData.clockMode) return;
16863     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16864
16865     GetTimeMark(&tickStartTM);
16866     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16867       whiteTimeRemaining : blackTimeRemaining);
16868
16869    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16870     whiteNPS = blackNPS = -1;
16871     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16872        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16873         whiteNPS = first.nps;
16874     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16875        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16876         blackNPS = first.nps;
16877     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16878         whiteNPS = second.nps;
16879     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16880         blackNPS = second.nps;
16881     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16882
16883     StartClockTimer(intendedTickLength);
16884 }
16885
16886 char *
16887 TimeString (long ms)
16888 {
16889     long second, minute, hour, day;
16890     char *sign = "";
16891     static char buf[32];
16892
16893     if (ms > 0 && ms <= 9900) {
16894       /* convert milliseconds to tenths, rounding up */
16895       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16896
16897       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16898       return buf;
16899     }
16900
16901     /* convert milliseconds to seconds, rounding up */
16902     /* use floating point to avoid strangeness of integer division
16903        with negative dividends on many machines */
16904     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16905
16906     if (second < 0) {
16907         sign = "-";
16908         second = -second;
16909     }
16910
16911     day = second / (60 * 60 * 24);
16912     second = second % (60 * 60 * 24);
16913     hour = second / (60 * 60);
16914     second = second % (60 * 60);
16915     minute = second / 60;
16916     second = second % 60;
16917
16918     if (day > 0)
16919       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16920               sign, day, hour, minute, second);
16921     else if (hour > 0)
16922       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16923     else
16924       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16925
16926     return buf;
16927 }
16928
16929
16930 /*
16931  * This is necessary because some C libraries aren't ANSI C compliant yet.
16932  */
16933 char *
16934 StrStr (char *string, char *match)
16935 {
16936     int i, length;
16937
16938     length = strlen(match);
16939
16940     for (i = strlen(string) - length; i >= 0; i--, string++)
16941       if (!strncmp(match, string, length))
16942         return string;
16943
16944     return NULL;
16945 }
16946
16947 char *
16948 StrCaseStr (char *string, char *match)
16949 {
16950     int i, j, length;
16951
16952     length = strlen(match);
16953
16954     for (i = strlen(string) - length; i >= 0; i--, string++) {
16955         for (j = 0; j < length; j++) {
16956             if (ToLower(match[j]) != ToLower(string[j]))
16957               break;
16958         }
16959         if (j == length) return string;
16960     }
16961
16962     return NULL;
16963 }
16964
16965 #ifndef _amigados
16966 int
16967 StrCaseCmp (char *s1, char *s2)
16968 {
16969     char c1, c2;
16970
16971     for (;;) {
16972         c1 = ToLower(*s1++);
16973         c2 = ToLower(*s2++);
16974         if (c1 > c2) return 1;
16975         if (c1 < c2) return -1;
16976         if (c1 == NULLCHAR) return 0;
16977     }
16978 }
16979
16980
16981 int
16982 ToLower (int c)
16983 {
16984     return isupper(c) ? tolower(c) : c;
16985 }
16986
16987
16988 int
16989 ToUpper (int c)
16990 {
16991     return islower(c) ? toupper(c) : c;
16992 }
16993 #endif /* !_amigados    */
16994
16995 char *
16996 StrSave (char *s)
16997 {
16998   char *ret;
16999
17000   if ((ret = (char *) malloc(strlen(s) + 1)))
17001     {
17002       safeStrCpy(ret, s, strlen(s)+1);
17003     }
17004   return ret;
17005 }
17006
17007 char *
17008 StrSavePtr (char *s, char **savePtr)
17009 {
17010     if (*savePtr) {
17011         free(*savePtr);
17012     }
17013     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17014       safeStrCpy(*savePtr, s, strlen(s)+1);
17015     }
17016     return(*savePtr);
17017 }
17018
17019 char *
17020 PGNDate ()
17021 {
17022     time_t clock;
17023     struct tm *tm;
17024     char buf[MSG_SIZ];
17025
17026     clock = time((time_t *)NULL);
17027     tm = localtime(&clock);
17028     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17029             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17030     return StrSave(buf);
17031 }
17032
17033
17034 char *
17035 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17036 {
17037     int i, j, fromX, fromY, toX, toY;
17038     int whiteToPlay;
17039     char buf[MSG_SIZ];
17040     char *p, *q;
17041     int emptycount;
17042     ChessSquare piece;
17043
17044     whiteToPlay = (gameMode == EditPosition) ?
17045       !blackPlaysFirst : (move % 2 == 0);
17046     p = buf;
17047
17048     /* Piece placement data */
17049     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17050         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17051         emptycount = 0;
17052         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17053             if (boards[move][i][j] == EmptySquare) {
17054                 emptycount++;
17055             } else { ChessSquare piece = boards[move][i][j];
17056                 if (emptycount > 0) {
17057                     if(emptycount<10) /* [HGM] can be >= 10 */
17058                         *p++ = '0' + emptycount;
17059                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17060                     emptycount = 0;
17061                 }
17062                 if(PieceToChar(piece) == '+') {
17063                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17064                     *p++ = '+';
17065                     piece = (ChessSquare)(DEMOTED piece);
17066                 }
17067                 *p++ = PieceToChar(piece);
17068                 if(p[-1] == '~') {
17069                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17070                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17071                     *p++ = '~';
17072                 }
17073             }
17074         }
17075         if (emptycount > 0) {
17076             if(emptycount<10) /* [HGM] can be >= 10 */
17077                 *p++ = '0' + emptycount;
17078             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17079             emptycount = 0;
17080         }
17081         *p++ = '/';
17082     }
17083     *(p - 1) = ' ';
17084
17085     /* [HGM] print Crazyhouse or Shogi holdings */
17086     if( gameInfo.holdingsWidth ) {
17087         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17088         q = p;
17089         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17090             piece = boards[move][i][BOARD_WIDTH-1];
17091             if( piece != EmptySquare )
17092               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17093                   *p++ = PieceToChar(piece);
17094         }
17095         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17096             piece = boards[move][BOARD_HEIGHT-i-1][0];
17097             if( piece != EmptySquare )
17098               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17099                   *p++ = PieceToChar(piece);
17100         }
17101
17102         if( q == p ) *p++ = '-';
17103         *p++ = ']';
17104         *p++ = ' ';
17105     }
17106
17107     /* Active color */
17108     *p++ = whiteToPlay ? 'w' : 'b';
17109     *p++ = ' ';
17110
17111   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17112     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17113   } else {
17114   if(nrCastlingRights) {
17115      q = p;
17116      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17117        /* [HGM] write directly from rights */
17118            if(boards[move][CASTLING][2] != NoRights &&
17119               boards[move][CASTLING][0] != NoRights   )
17120                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17121            if(boards[move][CASTLING][2] != NoRights &&
17122               boards[move][CASTLING][1] != NoRights   )
17123                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17124            if(boards[move][CASTLING][5] != NoRights &&
17125               boards[move][CASTLING][3] != NoRights   )
17126                 *p++ = boards[move][CASTLING][3] + AAA;
17127            if(boards[move][CASTLING][5] != NoRights &&
17128               boards[move][CASTLING][4] != NoRights   )
17129                 *p++ = boards[move][CASTLING][4] + AAA;
17130      } else {
17131
17132         /* [HGM] write true castling rights */
17133         if( nrCastlingRights == 6 ) {
17134             int q, k=0;
17135             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17136                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17137             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17138                  boards[move][CASTLING][2] != NoRights  );
17139             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17140                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17141                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17142                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17143                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17144             }
17145             if(q) *p++ = 'Q';
17146             k = 0;
17147             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17148                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17149             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17150                  boards[move][CASTLING][5] != NoRights  );
17151             if(gameInfo.variant == VariantSChess) {
17152                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17153                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17154                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17155                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17156             }
17157             if(q) *p++ = 'q';
17158         }
17159      }
17160      if (q == p) *p++ = '-'; /* No castling rights */
17161      *p++ = ' ';
17162   }
17163
17164   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17165      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17166      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17167     /* En passant target square */
17168     if (move > backwardMostMove) {
17169         fromX = moveList[move - 1][0] - AAA;
17170         fromY = moveList[move - 1][1] - ONE;
17171         toX = moveList[move - 1][2] - AAA;
17172         toY = moveList[move - 1][3] - ONE;
17173         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17174             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17175             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17176             fromX == toX) {
17177             /* 2-square pawn move just happened */
17178             *p++ = toX + AAA;
17179             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17180         } else {
17181             *p++ = '-';
17182         }
17183     } else if(move == backwardMostMove) {
17184         // [HGM] perhaps we should always do it like this, and forget the above?
17185         if((signed char)boards[move][EP_STATUS] >= 0) {
17186             *p++ = boards[move][EP_STATUS] + AAA;
17187             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17188         } else {
17189             *p++ = '-';
17190         }
17191     } else {
17192         *p++ = '-';
17193     }
17194     *p++ = ' ';
17195   }
17196   }
17197
17198     if(moveCounts)
17199     {   int i = 0, j=move;
17200
17201         /* [HGM] find reversible plies */
17202         if (appData.debugMode) { int k;
17203             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17204             for(k=backwardMostMove; k<=forwardMostMove; k++)
17205                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17206
17207         }
17208
17209         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17210         if( j == backwardMostMove ) i += initialRulePlies;
17211         sprintf(p, "%d ", i);
17212         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17213
17214         /* Fullmove number */
17215         sprintf(p, "%d", (move / 2) + 1);
17216     } else *--p = NULLCHAR;
17217
17218     return StrSave(buf);
17219 }
17220
17221 Boolean
17222 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17223 {
17224     int i, j;
17225     char *p, c;
17226     int emptycount, virgin[BOARD_FILES];
17227     ChessSquare piece;
17228
17229     p = fen;
17230
17231     /* [HGM] by default clear Crazyhouse holdings, if present */
17232     if(gameInfo.holdingsWidth) {
17233        for(i=0; i<BOARD_HEIGHT; i++) {
17234            board[i][0]             = EmptySquare; /* black holdings */
17235            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17236            board[i][1]             = (ChessSquare) 0; /* black counts */
17237            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17238        }
17239     }
17240
17241     /* Piece placement data */
17242     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17243         j = 0;
17244         for (;;) {
17245             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17246                 if (*p == '/') p++;
17247                 emptycount = gameInfo.boardWidth - j;
17248                 while (emptycount--)
17249                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17250                 break;
17251 #if(BOARD_FILES >= 10)
17252             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17253                 p++; emptycount=10;
17254                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17255                 while (emptycount--)
17256                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17257 #endif
17258             } else if (isdigit(*p)) {
17259                 emptycount = *p++ - '0';
17260                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17261                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17262                 while (emptycount--)
17263                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17264             } else if (*p == '+' || isalpha(*p)) {
17265                 if (j >= gameInfo.boardWidth) return FALSE;
17266                 if(*p=='+') {
17267                     piece = CharToPiece(*++p);
17268                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17269                     piece = (ChessSquare) (PROMOTED piece ); p++;
17270                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17271                 } else piece = CharToPiece(*p++);
17272
17273                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17274                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17275                     piece = (ChessSquare) (PROMOTED piece);
17276                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17277                     p++;
17278                 }
17279                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17280             } else {
17281                 return FALSE;
17282             }
17283         }
17284     }
17285     while (*p == '/' || *p == ' ') p++;
17286
17287     /* [HGM] look for Crazyhouse holdings here */
17288     while(*p==' ') p++;
17289     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17290         if(*p == '[') p++;
17291         if(*p == '-' ) p++; /* empty holdings */ else {
17292             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17293             /* if we would allow FEN reading to set board size, we would   */
17294             /* have to add holdings and shift the board read so far here   */
17295             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17296                 p++;
17297                 if((int) piece >= (int) BlackPawn ) {
17298                     i = (int)piece - (int)BlackPawn;
17299                     i = PieceToNumber((ChessSquare)i);
17300                     if( i >= gameInfo.holdingsSize ) return FALSE;
17301                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17302                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17303                 } else {
17304                     i = (int)piece - (int)WhitePawn;
17305                     i = PieceToNumber((ChessSquare)i);
17306                     if( i >= gameInfo.holdingsSize ) return FALSE;
17307                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17308                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17309                 }
17310             }
17311         }
17312         if(*p == ']') p++;
17313     }
17314
17315     while(*p == ' ') p++;
17316
17317     /* Active color */
17318     c = *p++;
17319     if(appData.colorNickNames) {
17320       if( c == appData.colorNickNames[0] ) c = 'w'; else
17321       if( c == appData.colorNickNames[1] ) c = 'b';
17322     }
17323     switch (c) {
17324       case 'w':
17325         *blackPlaysFirst = FALSE;
17326         break;
17327       case 'b':
17328         *blackPlaysFirst = TRUE;
17329         break;
17330       default:
17331         return FALSE;
17332     }
17333
17334     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17335     /* return the extra info in global variiables             */
17336
17337     /* set defaults in case FEN is incomplete */
17338     board[EP_STATUS] = EP_UNKNOWN;
17339     for(i=0; i<nrCastlingRights; i++ ) {
17340         board[CASTLING][i] =
17341             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17342     }   /* assume possible unless obviously impossible */
17343     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17344     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17345     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17346                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17347     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17348     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17349     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17350                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17351     FENrulePlies = 0;
17352
17353     while(*p==' ') p++;
17354     if(nrCastlingRights) {
17355       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17356       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17357           /* castling indicator present, so default becomes no castlings */
17358           for(i=0; i<nrCastlingRights; i++ ) {
17359                  board[CASTLING][i] = NoRights;
17360           }
17361       }
17362       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17363              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17364              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17365              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17366         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17367
17368         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17369             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17370             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17371         }
17372         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17373             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17374         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17375                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17376         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17377                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17378         switch(c) {
17379           case'K':
17380               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17381               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17382               board[CASTLING][2] = whiteKingFile;
17383               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17384               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17385               break;
17386           case'Q':
17387               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17388               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17389               board[CASTLING][2] = whiteKingFile;
17390               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17391               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17392               break;
17393           case'k':
17394               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17395               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17396               board[CASTLING][5] = blackKingFile;
17397               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17398               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17399               break;
17400           case'q':
17401               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17402               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17403               board[CASTLING][5] = blackKingFile;
17404               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17405               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17406           case '-':
17407               break;
17408           default: /* FRC castlings */
17409               if(c >= 'a') { /* black rights */
17410                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17411                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17412                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17413                   if(i == BOARD_RGHT) break;
17414                   board[CASTLING][5] = i;
17415                   c -= AAA;
17416                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17417                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17418                   if(c > i)
17419                       board[CASTLING][3] = c;
17420                   else
17421                       board[CASTLING][4] = c;
17422               } else { /* white rights */
17423                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17424                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17425                     if(board[0][i] == WhiteKing) break;
17426                   if(i == BOARD_RGHT) break;
17427                   board[CASTLING][2] = i;
17428                   c -= AAA - 'a' + 'A';
17429                   if(board[0][c] >= WhiteKing) break;
17430                   if(c > i)
17431                       board[CASTLING][0] = c;
17432                   else
17433                       board[CASTLING][1] = c;
17434               }
17435         }
17436       }
17437       for(i=0; i<nrCastlingRights; i++)
17438         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17439       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17440     if (appData.debugMode) {
17441         fprintf(debugFP, "FEN castling rights:");
17442         for(i=0; i<nrCastlingRights; i++)
17443         fprintf(debugFP, " %d", board[CASTLING][i]);
17444         fprintf(debugFP, "\n");
17445     }
17446
17447       while(*p==' ') p++;
17448     }
17449
17450     /* read e.p. field in games that know e.p. capture */
17451     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17452        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17453        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17454       if(*p=='-') {
17455         p++; board[EP_STATUS] = EP_NONE;
17456       } else {
17457          char c = *p++ - AAA;
17458
17459          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17460          if(*p >= '0' && *p <='9') p++;
17461          board[EP_STATUS] = c;
17462       }
17463     }
17464
17465
17466     if(sscanf(p, "%d", &i) == 1) {
17467         FENrulePlies = i; /* 50-move ply counter */
17468         /* (The move number is still ignored)    */
17469     }
17470
17471     return TRUE;
17472 }
17473
17474 void
17475 EditPositionPasteFEN (char *fen)
17476 {
17477   if (fen != NULL) {
17478     Board initial_position;
17479
17480     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17481       DisplayError(_("Bad FEN position in clipboard"), 0);
17482       return ;
17483     } else {
17484       int savedBlackPlaysFirst = blackPlaysFirst;
17485       EditPositionEvent();
17486       blackPlaysFirst = savedBlackPlaysFirst;
17487       CopyBoard(boards[0], initial_position);
17488       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17489       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17490       DisplayBothClocks();
17491       DrawPosition(FALSE, boards[currentMove]);
17492     }
17493   }
17494 }
17495
17496 static char cseq[12] = "\\   ";
17497
17498 Boolean
17499 set_cont_sequence (char *new_seq)
17500 {
17501     int len;
17502     Boolean ret;
17503
17504     // handle bad attempts to set the sequence
17505         if (!new_seq)
17506                 return 0; // acceptable error - no debug
17507
17508     len = strlen(new_seq);
17509     ret = (len > 0) && (len < sizeof(cseq));
17510     if (ret)
17511       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17512     else if (appData.debugMode)
17513       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17514     return ret;
17515 }
17516
17517 /*
17518     reformat a source message so words don't cross the width boundary.  internal
17519     newlines are not removed.  returns the wrapped size (no null character unless
17520     included in source message).  If dest is NULL, only calculate the size required
17521     for the dest buffer.  lp argument indicats line position upon entry, and it's
17522     passed back upon exit.
17523 */
17524 int
17525 wrap (char *dest, char *src, int count, int width, int *lp)
17526 {
17527     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17528
17529     cseq_len = strlen(cseq);
17530     old_line = line = *lp;
17531     ansi = len = clen = 0;
17532
17533     for (i=0; i < count; i++)
17534     {
17535         if (src[i] == '\033')
17536             ansi = 1;
17537
17538         // if we hit the width, back up
17539         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17540         {
17541             // store i & len in case the word is too long
17542             old_i = i, old_len = len;
17543
17544             // find the end of the last word
17545             while (i && src[i] != ' ' && src[i] != '\n')
17546             {
17547                 i--;
17548                 len--;
17549             }
17550
17551             // word too long?  restore i & len before splitting it
17552             if ((old_i-i+clen) >= width)
17553             {
17554                 i = old_i;
17555                 len = old_len;
17556             }
17557
17558             // extra space?
17559             if (i && src[i-1] == ' ')
17560                 len--;
17561
17562             if (src[i] != ' ' && src[i] != '\n')
17563             {
17564                 i--;
17565                 if (len)
17566                     len--;
17567             }
17568
17569             // now append the newline and continuation sequence
17570             if (dest)
17571                 dest[len] = '\n';
17572             len++;
17573             if (dest)
17574                 strncpy(dest+len, cseq, cseq_len);
17575             len += cseq_len;
17576             line = cseq_len;
17577             clen = cseq_len;
17578             continue;
17579         }
17580
17581         if (dest)
17582             dest[len] = src[i];
17583         len++;
17584         if (!ansi)
17585             line++;
17586         if (src[i] == '\n')
17587             line = 0;
17588         if (src[i] == 'm')
17589             ansi = 0;
17590     }
17591     if (dest && appData.debugMode)
17592     {
17593         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17594             count, width, line, len, *lp);
17595         show_bytes(debugFP, src, count);
17596         fprintf(debugFP, "\ndest: ");
17597         show_bytes(debugFP, dest, len);
17598         fprintf(debugFP, "\n");
17599     }
17600     *lp = dest ? line : old_line;
17601
17602     return len;
17603 }
17604
17605 // [HGM] vari: routines for shelving variations
17606 Boolean modeRestore = FALSE;
17607
17608 void
17609 PushInner (int firstMove, int lastMove)
17610 {
17611         int i, j, nrMoves = lastMove - firstMove;
17612
17613         // push current tail of game on stack
17614         savedResult[storedGames] = gameInfo.result;
17615         savedDetails[storedGames] = gameInfo.resultDetails;
17616         gameInfo.resultDetails = NULL;
17617         savedFirst[storedGames] = firstMove;
17618         savedLast [storedGames] = lastMove;
17619         savedFramePtr[storedGames] = framePtr;
17620         framePtr -= nrMoves; // reserve space for the boards
17621         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17622             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17623             for(j=0; j<MOVE_LEN; j++)
17624                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17625             for(j=0; j<2*MOVE_LEN; j++)
17626                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17627             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17628             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17629             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17630             pvInfoList[firstMove+i-1].depth = 0;
17631             commentList[framePtr+i] = commentList[firstMove+i];
17632             commentList[firstMove+i] = NULL;
17633         }
17634
17635         storedGames++;
17636         forwardMostMove = firstMove; // truncate game so we can start variation
17637 }
17638
17639 void
17640 PushTail (int firstMove, int lastMove)
17641 {
17642         if(appData.icsActive) { // only in local mode
17643                 forwardMostMove = currentMove; // mimic old ICS behavior
17644                 return;
17645         }
17646         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17647
17648         PushInner(firstMove, lastMove);
17649         if(storedGames == 1) GreyRevert(FALSE);
17650         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17651 }
17652
17653 void
17654 PopInner (Boolean annotate)
17655 {
17656         int i, j, nrMoves;
17657         char buf[8000], moveBuf[20];
17658
17659         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17660         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17661         nrMoves = savedLast[storedGames] - currentMove;
17662         if(annotate) {
17663                 int cnt = 10;
17664                 if(!WhiteOnMove(currentMove))
17665                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17666                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17667                 for(i=currentMove; i<forwardMostMove; i++) {
17668                         if(WhiteOnMove(i))
17669                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17670                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17671                         strcat(buf, moveBuf);
17672                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17673                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17674                 }
17675                 strcat(buf, ")");
17676         }
17677         for(i=1; i<=nrMoves; i++) { // copy last variation back
17678             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17679             for(j=0; j<MOVE_LEN; j++)
17680                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17681             for(j=0; j<2*MOVE_LEN; j++)
17682                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17683             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17684             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17685             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17686             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17687             commentList[currentMove+i] = commentList[framePtr+i];
17688             commentList[framePtr+i] = NULL;
17689         }
17690         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17691         framePtr = savedFramePtr[storedGames];
17692         gameInfo.result = savedResult[storedGames];
17693         if(gameInfo.resultDetails != NULL) {
17694             free(gameInfo.resultDetails);
17695       }
17696         gameInfo.resultDetails = savedDetails[storedGames];
17697         forwardMostMove = currentMove + nrMoves;
17698 }
17699
17700 Boolean
17701 PopTail (Boolean annotate)
17702 {
17703         if(appData.icsActive) return FALSE; // only in local mode
17704         if(!storedGames) return FALSE; // sanity
17705         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17706
17707         PopInner(annotate);
17708         if(currentMove < forwardMostMove) ForwardEvent(); else
17709         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17710
17711         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17712         return TRUE;
17713 }
17714
17715 void
17716 CleanupTail ()
17717 {       // remove all shelved variations
17718         int i;
17719         for(i=0; i<storedGames; i++) {
17720             if(savedDetails[i])
17721                 free(savedDetails[i]);
17722             savedDetails[i] = NULL;
17723         }
17724         for(i=framePtr; i<MAX_MOVES; i++) {
17725                 if(commentList[i]) free(commentList[i]);
17726                 commentList[i] = NULL;
17727         }
17728         framePtr = MAX_MOVES-1;
17729         storedGames = 0;
17730 }
17731
17732 void
17733 LoadVariation (int index, char *text)
17734 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17735         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17736         int level = 0, move;
17737
17738         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17739         // first find outermost bracketing variation
17740         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17741             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17742                 if(*p == '{') wait = '}'; else
17743                 if(*p == '[') wait = ']'; else
17744                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17745                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17746             }
17747             if(*p == wait) wait = NULLCHAR; // closing ]} found
17748             p++;
17749         }
17750         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17751         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17752         end[1] = NULLCHAR; // clip off comment beyond variation
17753         ToNrEvent(currentMove-1);
17754         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17755         // kludge: use ParsePV() to append variation to game
17756         move = currentMove;
17757         ParsePV(start, TRUE, TRUE);
17758         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17759         ClearPremoveHighlights();
17760         CommentPopDown();
17761         ToNrEvent(currentMove+1);
17762 }
17763
17764 void
17765 LoadTheme ()
17766 {
17767     char *p, *q, buf[MSG_SIZ];
17768     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17769         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17770         ParseArgsFromString(buf);
17771         ActivateTheme(TRUE); // also redo colors
17772         return;
17773     }
17774     p = nickName;
17775     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17776     {
17777         int len;
17778         q = appData.themeNames;
17779         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17780       if(appData.useBitmaps) {
17781         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17782                 appData.liteBackTextureFile, appData.darkBackTextureFile,
17783                 appData.liteBackTextureMode,
17784                 appData.darkBackTextureMode );
17785       } else {
17786         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17787                 Col2Text(2),   // lightSquareColor
17788                 Col2Text(3) ); // darkSquareColor
17789       }
17790       if(appData.useBorder) {
17791         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17792                 appData.border);
17793       } else {
17794         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17795       }
17796       if(appData.useFont) {
17797         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17798                 appData.renderPiecesWithFont,
17799                 appData.fontToPieceTable,
17800                 Col2Text(9),    // appData.fontBackColorWhite
17801                 Col2Text(10) ); // appData.fontForeColorBlack
17802       } else {
17803         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17804                 appData.pieceDirectory);
17805         if(!appData.pieceDirectory[0])
17806           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17807                 Col2Text(0),   // whitePieceColor
17808                 Col2Text(1) ); // blackPieceColor
17809       }
17810       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17811                 Col2Text(4),   // highlightSquareColor
17812                 Col2Text(5) ); // premoveHighlightColor
17813         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17814         if(insert != q) insert[-1] = NULLCHAR;
17815         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17816         if(q)   free(q);
17817     }
17818     ActivateTheme(FALSE);
17819 }